NeqSim Reference Manual

Complete Technical Documentation

Version 3.0.0

Generated: January 2026

Table of Contents

Chapter 1: Introduction

Overview

NeqSim Documentation

NeqSim (Non-Equilibrium Simulator) is a comprehensive Java library for thermodynamic, physical property, and process simulation. This documentation covers all major packages and provides detailed guides for developing applications.


Quick Start

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create a natural gas fluid
SystemInterface gas = new SystemSrkEos(298.15, 50.0);
gas.addComponent("methane", 0.90);
gas.addComponent("ethane", 0.05);
gas.addComponent("propane", 0.03);
gas.addComponent("CO2", 0.02);
gas.setMixingRule("classic");

// Perform flash calculation
ThermodynamicOperations ops = new ThermodynamicOperations(gas);
ops.TPflash();

// Get properties
System.out.println("Density: " + gas.getDensity("kg/m3") + " kg/m³");
System.out.println("Compressibility: " + gas.getZ());

Package Documentation

Core Thermodynamics

Package Documentation Description
neqsim.thermo thermo/ Thermodynamic systems, phases, components, equations of state, mixing rules, fluid characterization
neqsim.thermodynamicoperations thermodynamicoperations/ Flash calculations, phase envelopes, saturation operations
neqsim.physicalproperties physical_properties/ Transport properties: viscosity, thermal conductivity, diffusivity, interfacial tension

Process Simulation

Package Documentation Description
neqsim.process process/ Process equipment, unit operations, controllers, process systems, safety systems
neqsim.fluidmechanics fluidmechanics/ Pipeline flow, pressure drop, two-phase flow, flow nodes

PVT and Reservoir

Package Documentation Description
neqsim.pvtsimulation pvtsimulation/ PVT experiments: CME, CVD, DL, separator tests, swelling tests
neqsim.blackoil blackoil/ Black oil model, PVT tables, Rs, Bo, Bg correlations

Flow Assurance

Package Documentation Description
neqsim.pvtsimulation.flowassurance pvtsimulation/flowassurance/ Asphaltene stability, De Boer screening, CPA-based onset calculations

Chemical Reactions

Package Documentation Description
neqsim.chemicalreactions chemicalreactions/ Chemical equilibrium, reaction kinetics

Quality Standards

Package Documentation Description
neqsim.standards standards/ ISO 6976, ISO 6578, ISO 15403, ASTM D6377, sales contracts
neqsim.statistics statistics/ Parameter fitting, Monte Carlo simulation, data analysis

Utilities

Package Documentation Description
neqsim.util util/ Database access, unit conversion, serialization, exceptions
neqsim.mathlib mathlib/ Mathematical utilities, nonlinear solvers

Documentation Structure

docs/
├── README.md                      # This file - main index
├── modules.md                     # Module overview
│
├── thermo/                        # Thermodynamic package
│   ├── README.md                  # Package overview
│   ├── system/                    # EoS implementations
│   ├── phase/                     # Phase modeling
│   ├── component/                 # Component properties
│   ├── mixingrule/                # Mixing rules
│   └── characterization/          # Plus fraction handling
│
├── thermodynamicoperations/       # Flash operations
│   └── README.md
│
├── physical_properties/           # Transport properties
│   └── README.md
│
├── process/                       # Process simulation
│   ├── README.md                  # Package overview
│   ├── equipment/                 # Equipment documentation
│   ├── processmodel/              # ProcessSystem, modules
│   └── safety/                    # Safety systems
│
├── fluidmechanics/               # Pipe flow
│   └── README.md
│
├── pvtsimulation/                # PVT experiments
│   ├── README.md
│   └── flowassurance/            # Flow assurance (asphaltene, wax, hydrates)
│       ├── README.md
│       ├── asphaltene_modeling.md
│       ├── asphaltene_cpa_calculations.md
│       ├── asphaltene_deboer_screening.md
│       ├── asphaltene_parameter_fitting.md
│       ├── asphaltene_method_comparison.md
│       └── asphaltene_validation.md
│
├── blackoil/                     # Black oil model
│   └── README.md
│
├── chemicalreactions/            # Reactions
│   └── README.md
│
├── standards/                    # Quality standards
│   └── README.md
│
├── statistics/                   # Statistics package
│   └── README.md
│
├── util/                         # Utilities
│   └── README.md
│
├── mathlib/                      # Math utilities
│   └── README.md
│
├── safety/                       # Safety system guides
│   ├── ESD_BLOWDOWN_SYSTEM.md
│   ├── HIPPS_SUMMARY.md
│   ├── hipps_implementation.md
│   ├── sis_logic_implementation.md
│   ├── fire_blowdown_capabilities.md
│   ├── psv_dynamic_sizing_example.md
│   └── alarm_system_guide.md
│
├── simulation/                   # Process simulation guides
│   ├── advanced_process_logic.md
│   ├── graph_based_process_simulation.md
│   ├── parallel_process_simulation.md
│   ├── recycle_acceleration_guide.md
│   ├── well_simulation_guide.md
│   └── turboexpander_compressor_model.md
│
├── integration/                  # Integration guides
│   ├── ai_platform_integration.md
│   ├── ml_integration.md
│   ├── mpc_integration.md
│   ├── REAL_TIME_INTEGRATION_GUIDE.md
│   └── dexpi-reader.md
│
├── development/                  # Developer guides
│   ├── DEVELOPER_SETUP.md
│   └── contributing-structure.md
│
├── examples/                     # Code examples
│   └── ...
│
└── wiki/                         # Additional wiki pages
    └── ...

Topic Guides

Specialized guides for advanced features and use cases:

Safety and Emergency Systems

Guide Description
ESD_BLOWDOWN_SYSTEM.md Emergency shutdown and blowdown systems
HIPPS_SUMMARY.md High Integrity Pressure Protection Systems
hipps_implementation.md HIPPS implementation details
hipps_safety_logic.md HIPPS safety logic
INTEGRATED_SAFETY_SYSTEMS.md Integrated safety systems overview
layered_safety_architecture.md Layered safety architecture
sis_logic_implementation.md SIS logic implementation
SAFETY_SIMULATION_ROADMAP.md Safety simulation roadmap

Process Logic and Control

Guide Description
process_logic_framework.md Process logic framework
ProcessLogicEnhancements.md Logic enhancements
advanced_process_logic.md Advanced process logic
alarm_system_guide.md Alarm system guide
alarm_triggered_logic_example.md Alarm-triggered logic
mpc_integration.md MPC integration

Dynamic Simulation

Guide Description
fire_blowdown_capabilities.md Fire and blowdown simulation
fire_heat_transfer_enhancements.md Fire heat transfer
psv_dynamic_sizing_example.md PSV dynamic sizing
rupture_disk_dynamic_behavior.md Rupture disk behavior
turboexpander_compressor_model.md Turboexpander modeling

Well and Reservoir

Guide Description
well_simulation_guide.md Well simulation guide
well_and_choke_simulation.md Choke simulation
field_development_engine.md Field development

PVT and Characterization

Guide Description
pvt_workflow.md PVT workflow
blackoil_pvt_export.md Black oil PVT export
whitson_pvt_reader.md Whitson PVT reader
fluid_characterization_mathematics.md Characterization math

Advanced Features

Guide Description
parallel_process_simulation.md Parallel simulation
recycle_acceleration_guide.md Recycle convergence
graph_based_process_simulation.md Graph-based simulation
differentiable_thermodynamics.md Auto-differentiation
equipment_factory.md Equipment factory
dexpi-reader.md DEXPI P&ID reader

Integration

Guide Description
ai_platform_integration.md AI/ML integration
ml_integration.md Machine learning
REAL_TIME_INTEGRATION_GUIDE.md Real-time systems
QRA_INTEGRATION_GUIDE.md QRA integration

Development

Guide Description
DEVELOPER_SETUP.md Development environment setup
contributing-structure.md Contributing guidelines

Equations of State Quick Reference

EoS Class Application
SRK SystemSrkEos General hydrocarbon systems
PR SystemPrEos General hydrocarbon systems
PR-1978 SystemPrEos1978 Improved liquid densities
SRK-CPA SystemSrkCPAstatoil Associating fluids (water, alcohols, glycols)
PC-SAFT SystemPCSAFT Polymers, associating fluids
GERG-2008 SystemGERG2008Eos Natural gas reference
EOS-CG SystemEOSCGEos CO₂-rich systems (CCS)
UMR-PRU SystemUMRPRUMCEos Wide-range hydrocarbon systems

Process Equipment Quick Reference

Category Equipment Class
Separation 2-phase separator Separator
3-phase separator ThreePhaseSeparator
Distillation column DistillationColumn
Heat Transfer Heater Heater
Cooler Cooler
Heat exchanger HeatExchanger
Compression Compressor Compressor
Pump Pump
Expander Expander
Flow Control Valve ThrottlingValve
Mixer Mixer, StaticMixer
Splitter Splitter
Well/Reservoir Well SimpleWell
Choke ChokeValve

Getting Help


Version Compatibility


Modules

NeqSim Base Modules

This document provides an overview of the seven foundational modules that make up NeqSim. Each module resides under src/main/java/neqsim and works together to support fluid characterization and process design.

Thermodynamic routines

Physical properties routines

Fluid mechanic routines

Unit operations

Chemical reactions routines

Parameter fitting routines

Process simulation routines

Process safety simulation

Chapter 2: Installation & Setup

Getting Started

Getting Started with NeqSim

Use this page as a launchpad into the NeqSim documentation. It mirrors the high-level structure from the Colab introduction notebook and links directly to reference guides and examples.

Table of Contents


Quick Start

Add NeqSim as a dependency in your pom.xml:

<dependency>
    <groupId>com.equinor.neqsim</groupId>
    <artifactId>neqsim</artifactId>
    <version>3.0.0</version>
</dependency>

Direct JAR Download

Download the shaded JAR from the releases page and add to your classpath.


Set up NeqSim locally

Clone the repository and build with the Maven wrapper:

git clone https://github.com/equinor/neqsim.git
cd neqsim
./mvnw install

On Windows:

mvnw.cmd install

The command downloads dependencies, compiles the project, and runs the test suite. For environment notes and troubleshooting tips, see the README and developer setup guide.

Requirements


Your First Calculation

Simple Flash Calculation

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class FirstCalculation {
    public static void main(String[] args) {
        // 1. Create a natural gas system at 25°C and 50 bar
        SystemSrkEos gas = new SystemSrkEos(298.15, 50.0);
        gas.addComponent("methane", 0.90);
        gas.addComponent("ethane", 0.06);
        gas.addComponent("propane", 0.03);
        gas.addComponent("n-butane", 0.01);
        gas.setMixingRule("classic");

        // 2. Perform flash calculation
        ThermodynamicOperations ops = new ThermodynamicOperations(gas);
        ops.TPflash();

        // 3. Print results
        System.out.println("Number of phases: " + gas.getNumberOfPhases());
        System.out.println("Density: " + gas.getDensity("kg/m3") + " kg/m³");
        System.out.println("Z-factor: " + gas.getPhase("gas").getZ());
        System.out.println("Molecular weight: " + gas.getMolarMass() * 1000 + " g/mol");
    }
}

Simple Process Simulation

import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.valve.ThrottlingValve;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;

public class FirstProcess {
    public static void main(String[] args) {
        // 1. Create fluid
        SystemSrkEos fluid = new SystemSrkEos(320.0, 100.0);
        fluid.addComponent("methane", 0.80);
        fluid.addComponent("ethane", 0.10);
        fluid.addComponent("propane", 0.05);
        fluid.addComponent("n-pentane", 0.05);
        fluid.setMixingRule("classic");

        // 2. Create stream
        Stream feed = new Stream("Feed", fluid);
        feed.setFlowRate(10000.0, "kg/hr");
        feed.setTemperature(50.0, "C");
        feed.setPressure(100.0, "bara");

        // 3. Add equipment
        ThrottlingValve valve = new ThrottlingValve("Valve", feed);
        valve.setOutletPressure(20.0, "bara");

        Separator separator = new Separator("Separator", valve.getOutletStream());

        // 4. Build and run process
        ProcessSystem process = new ProcessSystem();
        process.add(feed);
        process.add(valve);
        process.add(separator);

        // Use runOptimized() for best performance (auto-selects strategy)
        process.runOptimized();

        // 5. Results
        System.out.println("Gas rate: " + separator.getGasOutStream().getFlowRate("kg/hr") + " kg/hr");
        System.out.println("Liquid rate: " + separator.getLiquidOutStream().getFlowRate("kg/hr") + " kg/hr");
    }
}

Execution Strategies

NeqSim provides optimized execution strategies for complex process simulations:

Method Best For Speedup
run() Simple processes baseline
runOptimized() Recommended 28-40%
runParallel() Feed-forward (no recycles) 40-57%
runHybrid() Complex recycle processes 38%
// Recommended - auto-selects best strategy based on process topology
process.runOptimized();

// Analyze process structure
System.out.println(process.getExecutionPartitionInfo());

See ProcessSystem documentation for details.


Fundamentals and thermodynamics

Equations of State

NeqSim supports multiple equations of state for different applications:

EOS Class Best For
SRK SystemSrkEos General hydrocarbon systems
Peng-Robinson SystemPrEos Reservoir/liquid density
SRK-CPA SystemSrkCPAstatoil Water, glycols, alcohols
GERG-2008 SystemGERG2008Eos Natural gas custody transfer

Fluid characterization and PVT workflows

Heavy Fraction Handling

For oils with C7+ fractions:

SystemSrkEos oil = new SystemSrkEos(350.0, 100.0);
oil.addComponent("methane", 10.0);
oil.addComponent("ethane", 5.0);
// ... light components ...

// Add TBP fractions
oil.addTBPfraction("C7", 5.0, 0.092, 730.0);
oil.addTBPfraction("C8", 4.0, 0.104, 750.0);
oil.addPlusFraction("C10+", 20.0, 0.200, 820.0);

// Characterize
oil.getCharacterization().setTBPModel("PedersenSRK");
oil.getCharacterization().characterisePlusFraction();

Process simulation

Available Equipment

NeqSim includes 50+ unit operations:

Category Equipment
Separation Separator, ThreePhaseSeparator, DistillationColumn, MembraneSeparator
Compression Compressor, Pump, Expander, Ejector
Heat Transfer Heater, Cooler, HeatExchanger
Flow Control ThrottlingValve, FlowRateController
Pipelines PipeBeggsAndBrills, AdiabaticPipe, WaterHammerPipe
Specialty Electrolyzer, WindTurbine, SolarPanel, Battery

Pipeline and multiphase flow

Pipeline Models

PipeBeggsAndBrills pipeline = new PipeBeggsAndBrills("Pipeline", inletStream);
pipeline.setLength(10000.0);           // 10 km
pipeline.setDiameter(0.2);             // 8 inch
pipeline.setElevation(50.0);           // 50m elevation gain
pipeline.setPipeWallRoughness(4.5e-5); // Steel
pipeline.run();

Dynamic behavior and process safety

Safety Systems

NeqSim provides comprehensive safety simulation:

// PSV sizing example
ValveController psv = new ValveController("PSV-001");
psv.setMaxPressure(50.0, "bara");
psv.setReliefPressure(55.0, "bara");

Unit operations and equipment models

Equipment Categories


Integration, control, and automation

PID Control Example

ControllerDeviceBaseClass controller = new ControllerDeviceBaseClass();
controller.setControllerSetPoint(50.0);
controller.setControllerParameters(0.5, 100.0, 0.0); // Kp, Ti, Td
valve.setController(controller);

Examples and tutorials

Jupyter Notebooks

External Resources

GitHub Setup

Getting started with NeqSim and Github

The NeqSim library as a jar files can be downloaded from the NeqSim release pages. A shaded library is distributed including all dependent libraries used by NeqSim. Use of NeqSim in a Java program is done by adding NeqSim.jar to the classpath.

Building NeqSim from source is done by cloning the project to a local directory on the developer computer. Building the code is done using JDK8+. NeqSim uses a number of libraries (jar files) for various calculations. These libraries must be available as part of the compilation process. The required libraries are listed in the pom.xml file. NeqSim can be built using the Maven build system (https://maven.apache.org/). All NeqSim build dependencies are given in the pom.xml file.

An interactive demonstration of how to get started as a NeqSim developer is presented in this NeqSim Colab demo.

Also see NeqSim JavaDoc.

And also see the Java tests where a lot of the functionality is demonstrated.


📚 NeqSim Java Documentation - Table of Contents

Navigate through the documentation organized from introductory concepts to advanced topics.


🚀 Part I: Getting Started (Beginner)

# Topic Description
1 Getting started with NeqSim and GitHub Installation and quick start
2 Getting started as a NeqSim developer Development environment setup
3 The NeqSim parameter database Component database and parameters

🧪 Part II: Fundamentals - Fluids & Thermodynamics (Beginner-Intermediate)

# Topic Description
4 Example of setting up a fluid and running simple flash calculations Basic fluid creation and flash
5 Select thermodynamic model and mixing rule EOS selection and configuration
6 Flash calculations and phase envelope calculations using NeqSim Phase equilibria calculations
7 Calculation of thermodynamic and physical properties using NeqSim Property calculation methods

🛢️ Part III: Fluid Characterization (Intermediate)

# Topic Description
8 Oil Characterization in NeqSim Crude oil and condensate characterization
9 Aqueous fluids and NeqSim Water and brine systems
10 Electrolytes and NeqSim Electrolyte thermodynamics

⚙️ Part IV: Process Simulation (Intermediate)

# Topic Description
11 Process Calculations in NeqSim Process simulation fundamentals

Equipment-Specific Guides

Topic Description
Compressor calculations Compressor modeling
Compressor curves Performance curves and maps

🔧 Part V: Extending NeqSim (Advanced)

# Topic Description
12 Adding a thermodynamic model in NeqSim Implement custom EOS models
13 Adding a viscosity model in NeqSim Implement custom viscosity correlations
14 Adding an unit operation in NeqSim Create custom process equipment

🖥️ Part VI: Integration & Deployment (Advanced)

# Topic Description
15 How to make a NeqSim API Building REST APIs with NeqSim
16 Create native image using GraalVM Native compilation for performance

📈 Part VII: Performance & Debugging (Advanced)

# Topic Description
17 Profiling calculations Performance analysis and optimization
18 Dynamic process simulations Transient and dynamic modeling

📖 Additional Documentation Resources

In-Repository Documentation (docs/ folder)

The repository contains extensive documentation organized by module:

Core Modules

Thermodynamics

Process Simulation

Pipeline & Multiphase Flow

Safety & Dynamics

PVT & Characterization

Integration

Development


Resource Link
Source Code github.com/equinor/neqsim
JavaDoc NeqSim JavaDoc
Java Tests Test Examples
Colab Demo Interactive Tutorial
Releases Download JAR
Discussions GitHub Discussions

Developer Setup

Developer Setup

This document summarizes the basic steps from the NeqSim wiki for setting up a local development environment. For additional details see the Getting started as a NeqSim developer wiki page.

Clone the repository

git clone https://github.com/equinor/neqsim.git
cd neqsim

Build the project

NeqSim requires JDK 8 or newer and uses the Maven build system. Use the provided Maven wrapper to build the code:

./mvnw install

(Windows users can run mvnw.cmd.)

Run the test suite

Execute all unit tests with:

./mvnw test

To generate a code coverage report:

./mvnw jacoco:prepare-agent test install jacoco:report

Static analysis

Checkstyle, SpotBugs, and PMD plugins are included in the Maven build and run during the verify phase. Run them locally with:

./mvnw checkstyle:check spotbugs:check pmd:check

The checks do not fail the build by default, but fixing any reported issues is encouraged.

Chapter 3: Quick Start Examples

Usage Examples

Usage Examples

Comprehensive examples demonstrating NeqSim capabilities for thermodynamic calculations and process simulation.

Table of Contents


Thermodynamic Calculations

Basic TP Flash

The most common thermodynamic calculation - determining phase equilibrium at fixed temperature and pressure.

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create a natural gas system
SystemSrkEos gas = new SystemSrkEos(298.15, 50.0);  // 25°C, 50 bar
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.08);
gas.addComponent("propane", 0.04);
gas.addComponent("n-butane", 0.02);
gas.addComponent("n-pentane", 0.01);
gas.setMixingRule("classic");

// Perform TP flash
ThermodynamicOperations ops = new ThermodynamicOperations(gas);
ops.TPflash();

// Print results
System.out.println("Number of phases: " + gas.getNumberOfPhases());
System.out.println("Gas fraction: " + gas.getPhaseFraction("gas", "mole"));
System.out.println("Liquid fraction: " + gas.getPhaseFraction("oil", "mole"));
System.out.println("Gas density: " + gas.getPhase("gas").getDensity("kg/m3") + " kg/m³");

Phase Envelope

Calculate the complete phase envelope (bubble and dew point curves).

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.80);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.07);
fluid.addComponent("n-heptane", 0.03);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.calcPTphaseEnvelope();

// Get cricondenbar (maximum pressure point)
double cricondenbarP = ops.get("cricondenbar")[0];
double cricondenbarT = ops.get("cricondenbar")[1];
System.out.println("Cricondenbar: " + cricondenbarP + " bar at " + cricondenbarT + " K");

// Get cricondentherm (maximum temperature point)
double cricondentT = ops.get("cricondentherm")[1];
double cricondentP = ops.get("cricondentherm")[0];
System.out.println("Cricondentherm: " + cricondentT + " K at " + cricondentP + " bar");

Dew Point Calculations

Calculate hydrocarbon and water dew points.

import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Use CPA for accurate water modeling
SystemSrkCPAstatoil gas = new SystemSrkCPAstatoil(298.15, 70.0);
gas.addComponent("methane", 0.90);
gas.addComponent("ethane", 0.05);
gas.addComponent("propane", 0.03);
gas.addComponent("water", 0.02);
gas.setMixingRule(10);  // CPA mixing rule

ThermodynamicOperations ops = new ThermodynamicOperations(gas);

// Hydrocarbon dew point at fixed pressure
ops.dewPointTemperatureFlash();
System.out.println("HC Dew Point at 70 bar: " + (gas.getTemperature() - 273.15) + " °C");

// Water dew point
ops.waterDewPointTemperatureFlash();
System.out.println("Water Dew Point: " + (gas.getTemperature() - 273.15) + " °C");

Physical Properties

Calculate comprehensive physical properties after flash.

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

SystemSrkEos fluid = new SystemSrkEos(300.0, 30.0);
fluid.addComponent("methane", 0.95);
fluid.addComponent("CO2", 0.05);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Gas phase properties
System.out.println("=== Gas Phase Properties ===");
System.out.println("Density: " + fluid.getPhase("gas").getDensity("kg/m3") + " kg/m³");
System.out.println("Viscosity: " + fluid.getPhase("gas").getViscosity("cP") + " cP");
System.out.println("Thermal Conductivity: " + fluid.getPhase("gas").getThermalConductivity("W/mK") + " W/m·K");
System.out.println("Cp: " + fluid.getPhase("gas").getCp("kJ/kgK") + " kJ/kg·K");
System.out.println("Cv: " + fluid.getPhase("gas").getCv("kJ/kgK") + " kJ/kg·K");
System.out.println("Z-factor: " + fluid.getPhase("gas").getZ());
System.out.println("Speed of Sound: " + fluid.getPhase("gas").getSoundSpeed() + " m/s");
System.out.println("Molecular Weight: " + fluid.getPhase("gas").getMolarMass() * 1000 + " g/mol");
System.out.println("Enthalpy: " + fluid.getPhase("gas").getEnthalpy("kJ/kg") + " kJ/kg");
System.out.println("Entropy: " + fluid.getPhase("gas").getEntropy("kJ/kgK") + " kJ/kg·K");

Fluid Creation

Simple Gas Mixture

import neqsim.thermo.system.SystemSrkEos;

// Standard SRK for natural gas
SystemSrkEos gas = new SystemSrkEos(298.15, 50.0);
gas.addComponent("nitrogen", 0.02);
gas.addComponent("CO2", 0.03);
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.06);
gas.addComponent("propane", 0.03);
gas.addComponent("i-butane", 0.005);
gas.addComponent("n-butane", 0.005);
gas.setMixingRule("classic");

Oil with Heavy Fractions

import neqsim.thermo.system.SystemSrkEos;

SystemSrkEos oil = new SystemSrkEos(350.0, 100.0);

// Light components
oil.addComponent("methane", 10.0);
oil.addComponent("ethane", 5.0);
oil.addComponent("propane", 4.0);
oil.addComponent("n-butane", 3.0);
oil.addComponent("n-pentane", 2.0);
oil.addComponent("n-hexane", 2.0);

// Heavy fractions (TBP cuts)
// addTBPfraction(name, moles, molarMass_kg/mol, density_kg/m3)
oil.addTBPfraction("C7", 5.0, 0.092, 730.0);
oil.addTBPfraction("C8", 4.0, 0.104, 750.0);
oil.addTBPfraction("C9", 3.0, 0.117, 770.0);

// Plus fraction
oil.addPlusFraction("C10+", 20.0, 0.200, 820.0);

// Set mixing rule and characterize
oil.setMixingRule("classic");

// Run characterization to split plus fraction
oil.getCharacterization().setTBPModel("PedersenSRK");
oil.getCharacterization().setPlusFractionModel("Pedersen");
oil.getCharacterization().characterisePlusFraction();

Aqueous Systems with CPA

For systems with water, glycols, or alcohols, use CPA equation of state.

import neqsim.thermo.system.SystemSrkCPAstatoil;

// Gas with MEG injection for hydrate inhibition
SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(280.0, 100.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.04);
fluid.addComponent("water", 0.02);
fluid.addComponent("MEG", 0.01);
fluid.setMixingRule(10);  // CPA mixing rule

// Calculate hydrate equilibrium
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.hydrateFormationTemperature();
System.out.println("Hydrate formation temperature: " + (fluid.getTemperature() - 273.15) + " °C");

Process Simulation

Simple Separation

Two-stage separation with pressure reduction.

import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.valve.ThrottlingValve;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;

// Create feed fluid
SystemSrkEos fluid = new SystemSrkEos(350.0, 150.0);
fluid.addComponent("methane", 70.0);
fluid.addComponent("ethane", 10.0);
fluid.addComponent("propane", 8.0);
fluid.addComponent("n-butane", 5.0);
fluid.addComponent("n-pentane", 4.0);
fluid.addComponent("n-heptane", 3.0);
fluid.setMixingRule("classic");

// Create feed stream
Stream wellStream = new Stream("Well Stream", fluid);
wellStream.setFlowRate(10000.0, "kg/hr");
wellStream.setTemperature(80.0, "C");
wellStream.setPressure(150.0, "bara");

// First stage separation
ThrottlingValve chokeValve = new ThrottlingValve("Choke Valve", wellStream);
chokeValve.setOutletPressure(50.0, "bara");

Separator hpSeparator = new Separator("HP Separator", chokeValve.getOutletStream());

// Second stage separation
ThrottlingValve lpValve = new ThrottlingValve("LP Valve", hpSeparator.getLiquidOutStream());
lpValve.setOutletPressure(5.0, "bara");

Separator lpSeparator = new Separator("LP Separator", lpValve.getOutletStream());

// Build and run process
ProcessSystem process = new ProcessSystem();
process.add(wellStream);
process.add(chokeValve);
process.add(hpSeparator);
process.add(lpValve);
process.add(lpSeparator);
process.run();

// Results
System.out.println("=== HP Separator ===");
System.out.println("Gas rate: " + hpSeparator.getGasOutStream().getFlowRate("kg/hr") + " kg/hr");
System.out.println("Oil rate: " + hpSeparator.getLiquidOutStream().getFlowRate("kg/hr") + " kg/hr");

System.out.println("=== LP Separator ===");
System.out.println("Flash gas: " + lpSeparator.getGasOutStream().getFlowRate("kg/hr") + " kg/hr");
System.out.println("Stabilized oil: " + lpSeparator.getLiquidOutStream().getFlowRate("kg/hr") + " kg/hr");

Compression System

Multi-stage compression with intercooling.

import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.equipment.heatexchanger.Cooler;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;

// Create gas feed
SystemSrkEos gas = new SystemSrkEos(298.15, 5.0);
gas.addComponent("methane", 0.95);
gas.addComponent("ethane", 0.03);
gas.addComponent("propane", 0.02);
gas.setMixingRule("classic");

Stream feed = new Stream("Feed Gas", gas);
feed.setFlowRate(50000.0, "Sm3/day");
feed.setTemperature(30.0, "C");
feed.setPressure(5.0, "bara");

// Stage 1: 5 -> 20 bar
Compressor stage1 = new Compressor("Stage 1", feed);
stage1.setOutletPressure(20.0, "bara");
stage1.setPolytropicEfficiency(0.78);
stage1.setUsePolytropicCalc(true);

Cooler intercooler1 = new Cooler("Intercooler 1", stage1.getOutletStream());
intercooler1.setOutTemperature(35.0, "C");

Separator scrubber1 = new Separator("Scrubber 1", intercooler1.getOutletStream());

// Stage 2: 20 -> 80 bar
Compressor stage2 = new Compressor("Stage 2", scrubber1.getGasOutStream());
stage2.setOutletPressure(80.0, "bara");
stage2.setPolytropicEfficiency(0.78);
stage2.setUsePolytropicCalc(true);

Cooler aftercooler = new Cooler("Aftercooler", stage2.getOutletStream());
aftercooler.setOutTemperature(40.0, "C");

// Build process
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(stage1);
process.add(intercooler1);
process.add(scrubber1);
process.add(stage2);
process.add(aftercooler);
process.run();

// Results
System.out.println("Stage 1 power: " + stage1.getPower("kW") + " kW");
System.out.println("Stage 1 outlet T: " + stage1.getOutletStream().getTemperature("C") + " °C");
System.out.println("Stage 2 power: " + stage2.getPower("kW") + " kW");
System.out.println("Stage 2 outlet T: " + stage2.getOutletStream().getTemperature("C") + " °C");
System.out.println("Total power: " + (stage1.getPower("kW") + stage2.getPower("kW")) + " kW");

Heat Exchanger

Shell and tube heat exchanger with two streams.

import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.heatexchanger.HeatExchanger;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;

// Hot stream (gas to be cooled)
SystemSrkEos hotFluid = new SystemSrkEos(373.15, 50.0);
hotFluid.addComponent("methane", 0.9);
hotFluid.addComponent("ethane", 0.1);
hotFluid.setMixingRule("classic");

Stream hotStream = new Stream("Hot Gas", hotFluid);
hotStream.setFlowRate(5000.0, "kg/hr");
hotStream.setTemperature(100.0, "C");
hotStream.setPressure(50.0, "bara");

// Cold stream (cooling water)
SystemSrkEos coldFluid = new SystemSrkEos(293.15, 3.0);
coldFluid.addComponent("water", 1.0);
coldFluid.setMixingRule("classic");

Stream coldStream = new Stream("Cooling Water", coldFluid);
coldStream.setFlowRate(20000.0, "kg/hr");
coldStream.setTemperature(20.0, "C");
coldStream.setPressure(3.0, "bara");

// Heat exchanger
HeatExchanger hx = new HeatExchanger("Gas Cooler");
hx.setFeedStream(0, hotStream);
hx.setFeedStream(1, coldStream);
hx.setUAvalue(5000.0);  // W/K

ProcessSystem process = new ProcessSystem();
process.add(hotStream);
process.add(coldStream);
process.add(hx);
process.run();

System.out.println("Hot stream outlet T: " + hx.getOutStream(0).getTemperature("C") + " °C");
System.out.println("Cold stream outlet T: " + hx.getOutStream(1).getTemperature("C") + " °C");
System.out.println("Duty: " + hx.getDuty() / 1000.0 + " kW");

Complete Gas Processing Plant

A more complete example with multiple unit operations.

import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.valve.ThrottlingValve;
import neqsim.process.equipment.separator.*;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.equipment.heatexchanger.*;
import neqsim.process.equipment.mixer.StaticMixer;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;

// Rich gas feed
SystemSrkEos richGas = new SystemSrkEos(310.0, 70.0);
richGas.addComponent("nitrogen", 0.01);
richGas.addComponent("CO2", 0.02);
richGas.addComponent("methane", 0.75);
richGas.addComponent("ethane", 0.10);
richGas.addComponent("propane", 0.06);
richGas.addComponent("i-butane", 0.02);
richGas.addComponent("n-butane", 0.02);
richGas.addComponent("i-pentane", 0.01);
richGas.addComponent("n-pentane", 0.01);
richGas.setMixingRule("classic");

Stream feed = new Stream("Rich Gas Feed", richGas);
feed.setFlowRate(100000.0, "Sm3/day");
feed.setTemperature(35.0, "C");
feed.setPressure(70.0, "bara");

// Inlet scrubber
Separator inletScrubber = new Separator("Inlet Scrubber", feed);

// Gas cooling
Cooler gasCooler = new Cooler("Gas Cooler", inletScrubber.getGasOutStream());
gasCooler.setOutTemperature(-20.0, "C");

// Cold separator (NGL recovery)
Separator coldSeparator = new Separator("Cold Separator", gasCooler.getOutletStream());

// Sales gas compression
Compressor salesCompressor = new Compressor("Sales Gas Compressor", coldSeparator.getGasOutStream());
salesCompressor.setOutletPressure(150.0, "bara");
salesCompressor.setIsentropicEfficiency(0.75);

// Build and run
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(inletScrubber);
process.add(gasCooler);
process.add(coldSeparator);
process.add(salesCompressor);
process.run();

// Results
System.out.println("=== Gas Processing Results ===");
System.out.println("Feed rate: " + feed.getFlowRate("MSm3/day") + " MSm³/day");
System.out.println("Sales gas rate: " + salesCompressor.getOutletStream().getFlowRate("MSm3/day") + " MSm³/day");
System.out.println("NGL rate: " + coldSeparator.getLiquidOutStream().getFlowRate("kg/hr") + " kg/hr");
System.out.println("Compressor power: " + salesCompressor.getPower("MW") + " MW");

Pipeline Calculations

Multiphase pipeline pressure drop calculation.

import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.pipeline.PipeBeggsAndBrills;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;

// Multiphase fluid
SystemSrkEos fluid = new SystemSrkEos(320.0, 80.0);
fluid.addComponent("methane", 0.75);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.08);
fluid.addComponent("n-pentane", 0.05);
fluid.addComponent("n-heptane", 0.02);
fluid.setMixingRule("classic");

Stream inlet = new Stream("Pipeline Inlet", fluid);
inlet.setFlowRate(50000.0, "kg/hr");
inlet.setTemperature(45.0, "C");
inlet.setPressure(80.0, "bara");

// Pipeline
PipeBeggsAndBrills pipeline = new PipeBeggsAndBrills("Export Pipeline", inlet);
pipeline.setLength(50000.0);           // 50 km
pipeline.setDiameter(0.3048);          // 12 inch
pipeline.setElevation(100.0);          // 100m elevation gain
pipeline.setPipeWallRoughness(4.5e-5); // Steel pipe
pipeline.setNumberOfIncrements(20);

ProcessSystem process = new ProcessSystem();
process.add(inlet);
process.add(pipeline);
process.run();

System.out.println("=== Pipeline Results ===");
System.out.println("Inlet pressure: " + inlet.getPressure("bara") + " bara");
System.out.println("Outlet pressure: " + pipeline.getOutletStream().getPressure("bara") + " bara");
System.out.println("Pressure drop: " + (inlet.getPressure("bara") - pipeline.getOutletStream().getPressure("bara")) + " bar");
System.out.println("Outlet temperature: " + pipeline.getOutletStream().getTemperature("C") + " °C");
System.out.println("Flow regime: " + pipeline.getFlowRegime());
System.out.println("Liquid holdup: " + pipeline.getLiquidHoldup());

Specialized Equipment

Wind Turbine

The WindTurbine unit converts kinetic energy in wind into electrical power using a simple actuator-disk formulation. Air density is assumed constant at 1.225 kg/m³ and all inefficiencies are lumped into the power coefficient.

import neqsim.process.equipment.powergeneration.WindTurbine;

WindTurbine turbine = new WindTurbine("turbine");
turbine.setWindSpeed(12.0);    // m/s
turbine.setRotorArea(50.0);    // m²
turbine.setPowerCoefficient(0.4);
turbine.run();

System.out.println("Power produced: " + turbine.getPower() + " W");

Electrolyzer

Water electrolysis for hydrogen production.

import neqsim.process.equipment.electrolyzer.Electrolyzer;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Water feed
SystemSrkEos water = new SystemSrkEos(298.15, 1.0);
water.addComponent("water", 1.0);
water.setMixingRule("classic");

Stream waterFeed = new Stream("Water Feed", water);
waterFeed.setFlowRate(100.0, "kg/hr");

Electrolyzer electrolyzer = new Electrolyzer("PEM Electrolyzer", waterFeed);
electrolyzer.setEfficiency(0.70);
electrolyzer.run();

System.out.println("H2 production rate: " + electrolyzer.getHydrogenOutStream().getFlowRate("kg/hr") + " kg/hr");
System.out.println("O2 production rate: " + electrolyzer.getOxygenOutStream().getFlowRate("kg/hr") + " kg/hr");
System.out.println("Power consumption: " + electrolyzer.getPower("kW") + " kW");

Membrane Separator

Gas separation using membrane technology.

import neqsim.process.equipment.separator.MembraneSeparator;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

SystemSrkEos gas = new SystemSrkEos(298.15, 50.0);
gas.addComponent("CO2", 0.30);
gas.addComponent("methane", 0.70);
gas.setMixingRule("classic");

Stream feed = new Stream("Feed", gas);
feed.setFlowRate(1000.0, "Sm3/hr");

MembraneSeparator membrane = new MembraneSeparator("CO2 Membrane", feed);
membrane.setRelativePermability("CO2", 20.0);
membrane.setRelativePermability("methane", 1.0);
membrane.setMembranePressureDrop(30.0, "bara");
membrane.run();

System.out.println("Permeate CO2: " + membrane.getPermeateStream().getFluid().getComponent("CO2").getx() * 100 + " mol%");
System.out.println("Retentate CO2: " + membrane.getRetentateStream().getFluid().getComponent("CO2").getx() * 100 + " mol%");

Additional Resources

For more examples, see:

FAQ

Frequently Asked Questions (FAQ)

Table of Contents


General

What is NeqSim?

NeqSim (Non-Equilibrium Simulator) is a Java library for thermodynamic calculations and process simulation, specializing in oil and gas applications. It provides:

Where can I find the API documentation?

The full JavaDoc is available at https://htmlpreview.github.io/?https://github.com/equinor/neqsimhome/blob/master/javadoc/site/apidocs/index.html.

Is NeqSim open source?

Yes, NeqSim is open source under the Apache 2.0 license. You can freely use, modify, and distribute it.

Who maintains NeqSim?

The project is developed by Equinor with contributions from the community. Contact Even Solbraa (esolbraa@gmail.com) for questions.

What Java version is required?

NeqSim requires Java 8 or higher. Java 11+ is recommended for best performance.


Installation & Setup

How do I add NeqSim to my project?

Maven:

<dependency>
    <groupId>com.equinor.neqsim</groupId>
    <artifactId>neqsim</artifactId>
    <version>3.0.0</version>
</dependency>

Gradle:

implementation 'com.equinor.neqsim:neqsim:3.0.0'

Direct Download: Download the shaded JAR from GitHub releases.

How do I build NeqSim from source?

git clone https://github.com/equinor/neqsim.git
cd neqsim
./mvnw install

On Windows, use mvnw.cmd install.

How do I run the tests?

After cloning the repository, execute:

./mvnw test

To run a specific test:

./mvnw test -Dtest=YourTestClassName

Thermodynamics

Which equation of state should I use?

Application Recommended EOS
General natural gas SystemSrkEos
Reservoir/PVT SystemPrEos
Water + hydrocarbons SystemSrkCPAstatoil
Glycol dehydration SystemSrkCPAstatoil
High-accuracy natural gas SystemGERG2008Eos
CO2 capture/CCS SystemSrkCPAstatoil or SystemSpanWagnerEos
Electrolytes/brine SystemElectrolyteCPAstatoil

How do I set up a mixing rule?

// For SRK/PR - use classic van der Waals mixing rule
system.setMixingRule("classic");  // or system.setMixingRule(2);

// For CPA - use CPA mixing rule
system.setMixingRule(10);

// For advanced mixing (Huron-Vidal with NRTL)
system.setMixingRule("HV", "NRTL");

Why does my flash calculation not converge?

Common causes and solutions:

  1. Near critical conditions: Reduce step size or try different initial conditions
  2. Very heavy fractions: Check that TBP characterization is correct
  3. Extreme T/P conditions: Verify values are within model validity range
  4. Missing components: Ensure all components have database parameters

How do I calculate a phase envelope?

ThermodynamicOperations ops = new ThermodynamicOperations(system);
ops.calcPTphaseEnvelope();

double[] cricondenbar = ops.get("cricondenbar");
double[] cricondentherm = ops.get("cricondentherm");

How do I handle plus fractions?

// Add plus fraction with average properties
system.addPlusFraction("C10+", 10.0, 0.200, 820.0);

// Set characterization model
system.getCharacterization().setTBPModel("PedersenSRK");
system.getCharacterization().setPlusFractionModel("Pedersen");
system.getCharacterization().characterisePlusFraction();

Process Simulation

How do I create a simple process?

ProcessSystem process = new ProcessSystem();

// Add feed stream
Stream feed = new Stream("Feed", fluid);
process.add(feed);

// Add equipment
Separator sep = new Separator("Sep", feed);
process.add(sep);

// Run
process.run();

Equipment names must be unique - why?

ProcessSystem uses equipment names as identifiers. Use unique names:

Separator sep1 = new Separator("HP Separator", stream1);
Separator sep2 = new Separator("LP Separator", stream2);  // Different name

How do I implement a recycle?

Recycle recycle = new Recycle("Recycle");
recycle.addStream(separator.getLiquidOutStream());
recycle.setTolerance(1e-6);
process.add(recycle);

// Connect output to upstream mixer
mixer.addStream(recycle.getOutletStream());

How do I set compressor efficiency?

// Isentropic efficiency
compressor.setIsentropicEfficiency(0.75);
compressor.setUsePolytropicCalc(false);

// OR polytropic efficiency
compressor.setPolytropicEfficiency(0.80);
compressor.setUsePolytropicCalc(true);

Physical Properties

How do I get viscosity?

// After running flash
ops.TPflash();

// Gas viscosity
double gasVisc = system.getPhase("gas").getViscosity("cP");

// Liquid viscosity
double liqVisc = system.getPhase("oil").getViscosity("cP");

What viscosity models are available?

See Viscosity Models for details.

How do I get the speed of sound?

double soundSpeed = system.getPhase("gas").getSoundSpeed();  // m/s

How do I get interfacial tension?

double ift = system.getInterfacialTension(0, 1);  // Phase indices

Troubleshooting

NullPointerException after creating system

Always run flash before accessing properties:

ThermodynamicOperations ops = new ThermodynamicOperations(system);
ops.TPflash();  // REQUIRED before accessing properties

"Component not found" error

Check component name spelling. Use exact names from the database:

// Correct
system.addComponent("methane", 1.0);
system.addComponent("n-butane", 1.0);  // Note: n-butane, not nbutane

// Find available components
// Check database or use system.getComponentNames()

Very slow calculations

  1. Reduce number of pseudo-components in characterization
  2. Use simpler EOS (SRK instead of CPA) if polar components not critical
  3. Reduce number of calculation increments in pipelines
  4. Check for convergence issues causing many iterations

Process doesn't converge

  1. Check for reasonable initial conditions
  2. Verify equipment is connected properly
  3. For recycles, provide good initial guess and check tolerance
  4. Try running units individually to isolate issue

Contributing

How do I contribute to NeqSim?

  1. Fork the repository
  2. Create a feature branch
  3. Make changes with tests
  4. Run ./mvnw verify to check code style
  5. Submit a pull request

See Contributing Structure for details.

How do I report a bug?

Open an issue at https://github.com/equinor/neqsim/issues with:

Where can I ask questions?

Wiki Index

NeqSim Wiki

Welcome to the NeqSim documentation. This comprehensive wiki provides guides, tutorials, and reference materials for using the library and contributing to development.


About NeqSim

NeqSim (Non-Equilibrium Simulator) is a Java library for estimating fluid properties and process design. The library contains models for:

Development was initiated at the Norwegian University of Science and Technology (NTNU). NeqSim is part of the NeqSim project.


Quick Start

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create a fluid
SystemSrkEos fluid = new SystemSrkEos(298.15, 10.0);  // T(K), P(bara)
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.07);
fluid.addComponent("propane", 0.03);
fluid.setMixingRule("classic");

// Run flash calculation
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Get results
System.out.println("Z-factor: " + fluid.getZ());
System.out.println("Density: " + fluid.getDensity("kg/m3") + " kg/m3");

🚀 Getting Started

Guide Description
Getting Started Installation, first calculations, and basic concepts
Usage Examples Comprehensive code examples
FAQ Frequently asked questions
GitHub Guide Complete documentation index

🧪 Thermodynamics & Phase Behavior

Guide Description
Thermodynamics Guide Equations of state, flash calculations, mixing rules
Fluid Characterization Plus fractions, pseudo-components, TBP modeling
Flash Equations & Tests Flash calculations validated by tests
Property Flash Workflows PH, PS, UV flash calculations

⚙️ Process Simulation

Guide Description
Process Simulation Guide Building flowsheets, running simulations
Advanced Process Simulation Recycles, adjusters, complex systems
Logical Unit Operations Controllers, splitters, recycles
Transient Simulation Guide Dynamic process modeling
Process Control Framework PID controllers, automation

🔧 Equipment Models

Equipment Documentation
Distillation Column Sequential, damped, inside-out solvers
Gibbs Reactor Chemical equilibrium reactor
Flow Meter Models Orifice, venturi, ultrasonic meters
Air Cooler Air-cooled heat exchanger
Heat Exchanger Design Mechanical design methods
Water Cooler Water-cooled systems
Steam Heater Steam heating systems
Battery Storage Energy storage unit
Solar Panel Solar power generation

📊 PVT & Reservoir

Guide Description
PVT Simulation Workflows CVD, CCE, DL simulations
Black-Oil Flash Playbook Black-oil modeling techniques
Humid Air Mathematics Psychrometric calculations

📏 Standards & Quality

Guide Description
Gas Quality Standards ISO 6976, GPA standards

🔌 Integration & Tools

Guide Description
Java from Colab Running NeqSim in Google Colab
JUnit Test Overview Test suite structure

Installation

Maven:

<dependency>
    <groupId>com.equinor.neqsim</groupId>
    <artifactId>neqsim</artifactId>
    <version>3.0.0</version>
</dependency>

Download: GitHub Releases


Resources

Chapter 4: Fundamentals

Thermo Overview

Thermodynamic Documentation Set

This folder collects topic-specific documentation for using NeqSim's thermodynamic, PVT, and physical property capabilities. Each page is intended to be self-contained while pointing to related guides so you can jump directly to the workflows you need.


Package Structure

thermo/
├── system/       # Fluid system implementations (58 EoS classes)
├── phase/        # Phase types and calculations (62 classes)
├── component/    # Component properties (65 classes)
├── mixingrule/   # Mixing rules for EoS
└── characterization/  # Plus fraction characterization

Subpackage Documentation

Subpackage Description Documentation
system Equations of state implementations system/README.md
phase Phase modeling (gas, liquid, solid, asphaltene) phase/README.md
component Component property calculations component/README.md
mixingrule Binary interaction parameters mixingrule/README.md
characterization Plus fraction and asphaltene characterization characterization/README.md

Guide Contents

Core Guides

Database Documentation

Reference Documentation

Application Guides


Each document favors short, reproducible code snippets using the Java API so the same ideas transfer to other supported languages (Python/Matlab) with minor syntax changes.

Thermodynamics Guide

NeqSim Thermodynamics Guide

NeqSim (Non-Equilibrium Simulator) is a comprehensive library for thermodynamic calculations, specializing in oil and gas fluids, CO2 systems, and aqueous electrolytes. This guide provides an overview of the available models, methods, and how to use them.

1. Thermodynamic Models (Equations of State)

The core of any simulation is the Equation of State (EOS). NeqSim supports a wide range of EOSs tailored for different applications.

1.1 Cubic Equations of State

Standard models for oil and gas processing.

1.2 Cubic Plus Association (CPA)

Essential for systems containing polar molecules (water, methanol, glycol) and hydrocarbons. It combines a cubic EOS (SRK or PR) with an association term (Wertheim).

1.3 Reference Equations

High-precision multiparameter equations for specific fluids or mixtures.

1.4 Electrolyte Models

For systems containing salts and ions.

2. Mixing Rules

Mixing rules define how pure component parameters are combined for mixtures.

3. Flash Calculations

NeqSim performs various types of equilibrium calculations (flashes) via the ThermodynamicOperations class.

3.1 Standard Flashes

3.2 Saturation Points

3.3 Solid Formation

4. Physical Properties

Once a flash is performed, physical properties are available from the Phase objects.

5. Code Examples

Java Example: Natural Gas Dew Point

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class DewPointExample {
    public static void main(String[] args) {
        // 1. Create System
        SystemSrkEos gas = new SystemSrkEos(298.15, 50.0);
        gas.addComponent("methane", 90.0);
        gas.addComponent("ethane", 5.0);
        gas.addComponent("propane", 3.0);
        gas.addComponent("water", 0.1); // Saturated water

        // 2. Set Mixing Rule
        gas.setMixingRule("classic");

        // 3. Initialize Operations
        ThermodynamicOperations ops = new ThermodynamicOperations(gas);

        // 4. Calculate Hydrocarbon Dew Point
        try {
            ops.dewPointTemperatureFlash();
            System.out.println("HC Dew Point: " + gas.getTemperature("C") + " C");
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 5. Calculate Water Dew Point
        try {
            ops.waterDewPointTemperatureFlash();
            System.out.println("Water Dew Point: " + gas.getTemperature("C") + " C");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Python Example: CO2 Density with GERG-2008

from neqsim.thermo import SystemGERG2008Eos
from neqsim.thermodynamicoperations import ThermodynamicOperations

# 1. Create System
co2 = SystemGERG2008Eos(300.0, 100.0) # 300 K, 100 bar
co2.addComponent("CO2", 1.0)

# 2. Flash
ops = ThermodynamicOperations(co2)
ops.TPflash()

# 3. Get Properties
rho = co2.getPhase(0).getDensity("kg/m3")
print(f"CO2 Density at 100 bar/300 K: {rho} kg/m3")

6. Fluid Characterization

For real reservoir fluids containing heavy fractions (C7+), NeqSim provides tools to characterize the fluid based on specific gravity and molecular weight.

Advanced Options

See Fluid Characterization for details.

System Types

Thermo System Package

Documentation for fluid system implementations in NeqSim.

Table of Contents


Overview

Location: neqsim.thermo.system

The system package contains 58+ implementations of thermodynamic models, from simple ideal gas to complex associating equations of state.


System Hierarchy

SystemInterface
└── SystemThermo (abstract base)
    ├── SystemEos (cubic EoS base)
    │   ├── SystemSrkEos
    │   ├── SystemPrEos
    │   └── ...
    ├── SystemSrkCPA (CPA base)
    │   ├── SystemSrkCPAstatoil
    │   └── ...
    └── SystemPCSAFT (SAFT base)
        └── ...

Cubic Equations of State

Soave-Redlich-Kwong (SRK)

import neqsim.thermo.system.SystemSrkEos;

SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 1.0);
fluid.setMixingRule("classic");

$$P = \frac{RT}{V-b} - \frac{a\alpha(T)}{V(V+b)}$$

Peng-Robinson (PR)

import neqsim.thermo.system.SystemPrEos;

SystemPrEos fluid = new SystemPrEos(298.15, 50.0);
fluid.addComponent("methane", 1.0);
fluid.setMixingRule("classic");

$$P = \frac{RT}{V-b} - \frac{a\alpha(T)}{V(V+b)+b(V-b)}$$

Available Cubic EoS Classes

Class Description
SystemSrkEos Standard SRK
SystemPrEos Standard PR
SystemSrkMathiasCopeman SRK with Mathias-Copeman alpha
SystemPrMathiasCopeman PR with Mathias-Copeman alpha
SystemSrkSchwartzentruberRenon SRK with GE mixing
SystemPrSchwartzentruberRenon PR with GE mixing
SystemSrkTwuCoon SRK with Twu-Coon alpha
SystemPrTwuCoon PR with Twu-Coon alpha
SystemPrDanesh PR with Danesh modifications

Alpha Function Options

// Standard Soave alpha
SystemSrkEos std = new SystemSrkEos(T, P);

// Mathias-Copeman alpha (better for polar)
SystemSrkMathiasCopeman mc = new SystemSrkMathiasCopeman(T, P);

// Twu-Coon alpha
SystemSrkTwuCoon tc = new SystemSrkTwuCoon(T, P);

Association Equations

CPA (Cubic-Plus-Association)

For systems with hydrogen bonding (water, alcohols, glycols, amines).

import neqsim.thermo.system.SystemSrkCPAstatoil;

SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(298.15, 10.0);
fluid.addComponent("water", 1.0);
fluid.addComponent("methane", 2.0);
fluid.setMixingRule(10);  // CPA mixing rule

Available CPA Classes

Class Description
SystemSrkCPAstatoil SRK-CPA (Equinor parameters)
SystemPrCPA PR-CPA
SystemSrkCPA SRK-CPA (generic)
SystemElectrolyteCPA CPA with electrolytes

CPA Mixing Rules

// Standard CPA mixing rule
fluid.setMixingRule(10);

// With cross-association
fluid.setMixingRule(10);

SAFT-Based EoS

PC-SAFT

Statistical Associating Fluid Theory with perturbed chain.

import neqsim.thermo.system.SystemPCSAFT;

SystemPCSAFT fluid = new SystemPCSAFT(298.15, 10.0);
fluid.addComponent("methane", 1.0);
fluid.addComponent("n-hexane", 0.5);
fluid.setMixingRule("classic");

ePCSAFT (Electrolyte PC-SAFT)

import neqsim.thermo.system.SystemElectrolytePCSAFT;

SystemElectrolytePCSAFT brine = new SystemElectrolytePCSAFT(298.15, 1.0);
brine.addComponent("water", 1.0);
brine.addComponent("Na+", 0.1);
brine.addComponent("Cl-", 0.1);

Activity Models

NRTL

import neqsim.thermo.system.SystemNRTL;

SystemNRTL liquid = new SystemNRTL(298.15, 1.0);
liquid.addComponent("ethanol", 0.5);
liquid.addComponent("water", 0.5);

UNIFAC

import neqsim.thermo.system.SystemUNIFAC;

SystemUNIFAC liquid = new SystemUNIFAC(298.15, 1.0);
liquid.addComponent("acetone", 0.5);
liquid.addComponent("water", 0.5);

EoS/GE Combinations

// SRK with UNIFAC for liquid
SystemSrkSchwartzentruberRenon fluid = new SystemSrkSchwartzentruberRenon(T, P);

Reference EoS

GERG-2008

High-accuracy reference equation for natural gas.

import neqsim.thermo.system.SystemGERG2008;

SystemGERG2008 gas = new SystemGERG2008(288.15, 50.0);
gas.addComponent("nitrogen", 0.02);
gas.addComponent("CO2", 0.01);
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.08);
gas.addComponent("propane", 0.04);

Accuracy:

UMR-PRU

Universal Mixing Rule with PR EoS.

import neqsim.thermo.system.SystemUMRPRU;

SystemUMRPRU lng = new SystemUMRPRU(110.0, 1.0);
lng.addComponent("methane", 0.92);
lng.addComponent("ethane", 0.05);
lng.addComponent("propane", 0.03);

Creating Custom Systems

Temperature and Pressure

// Create at specific T, P
SystemSrkEos fluid = new SystemSrkEos(300.0, 10.0);  // K, bar

// Change conditions later
fluid.setTemperature(350.0);
fluid.setPressure(50.0);

// With units
fluid.setTemperature(25.0, "C");
fluid.setPressure(50.0, "bara");

Components

// By name and moles
fluid.addComponent("methane", 100.0);

// By index
fluid.addComponent(0, 100.0);

// TBP fraction (for plus fractions)
fluid.addTBPfraction("C7+", 10.0, 150.0, 0.78);  // name, moles, MW, SG

// Set mole fractions directly
double[] z = {0.85, 0.10, 0.05};
fluid.setMolarComposition(z);

Phase Specifications

// Force number of phases
fluid.setNumberOfPhases(2);

// Specify phase types
fluid.setPhaseType(0, "gas");
fluid.setPhaseType(1, "oil");

// Allow solid phases
fluid.setSolidPhaseCheck(true);

System Methods

Initialization

// Initialize thermodynamic properties
fluid.init(0);  // Molar volumes only
fluid.init(1);  // Plus fugacity coefficients
fluid.init(2);  // Plus all derivatives
fluid.init(3);  // Plus second derivatives

Property Access

// Bulk properties
double rho = fluid.getDensity("kg/m3");
double MW = fluid.getMolarMass("kg/mol");
double H = fluid.getEnthalpy("kJ/kg");
double S = fluid.getEntropy("kJ/kgK");
double Cp = fluid.getCp("J/molK");
double Cv = fluid.getCv("J/molK");
double Z = fluid.getZ();
double kappa = fluid.getKappa();  // Cp/Cv

// Transport properties
double visc = fluid.getViscosity("cP");
double k = fluid.getThermalConductivity("W/mK");

// Phase properties
double gasZ = fluid.getGasPhase().getZ();
double liqRho = fluid.getLiquidPhase().getDensity("kg/m3");

Cloning

// Deep copy
SystemInterface copy = fluid.clone();

// Modify copy without affecting original
copy.setTemperature(400.0);

Chapter 5: Fluid Creation & Components

Fluid Creation Guide

Creating Fluids in NeqSim

This guide provides comprehensive documentation on how to create and configure thermodynamic fluids in NeqSim, including available equations of state, mixing rules, and best practices.

Table of Contents

  1. Basic Fluid Creation
  2. Equations of State Overview
  3. Cubic Equations of State
  4. Advanced Equations of State
  5. Reference Equations (Helmholtz-Based)
  6. Activity Coefficient Models
  7. Electrolyte Models
  8. Mixing Rules
  9. Adding Components
  10. Heavy Fraction Characterization
  11. Complete Examples
  12. Model Selection Guidelines

1. Basic Fluid Creation

Creating a fluid in NeqSim follows a consistent pattern:

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermo.system.SystemInterface;

// 1. Create the fluid with initial temperature (K) and pressure (bara)
SystemInterface fluid = new SystemSrkEos(298.15, 10.0);

// 2. Add components
fluid.addComponent("methane", 0.90);    // name, moles
fluid.addComponent("ethane", 0.05);
fluid.addComponent("propane", 0.05);

// 3. Set up the mixing rule
fluid.setMixingRule("classic");

// 4. Initialize the fluid
fluid.init(0);

Constructor Parameters

All fluid system classes accept these constructor signatures:

Constructor Description
SystemXXX() Default: 298.15 K, 1.0 bara
SystemXXX(T, P) Temperature (K), Pressure (bara)
SystemXXX(T, P, checkForSolids) With solid phase checking enabled

2. Equations of State Overview

NeqSim provides a wide range of thermodynamic models organized into categories:

Category Use Cases Examples
Cubic EoS General hydrocarbon processing SRK, PR, PR-1978
CPA (Cubic Plus Association) Polar/associating fluids (water, glycols, alcohols) SRK-CPA, PR-CPA
Reference EoS High-accuracy natural gas, CCS GERG-2008, EOS-CG
SAFT-based Complex molecular interactions PC-SAFT
Activity Coefficient Non-ideal liquid mixtures UNIFAC, NRTL
Electrolyte Aqueous salt solutions Electrolyte-CPA, Pitzer
Specialized Specific applications Soreide-Whitson (sour gas/brine)

3. Cubic Equations of State

3.1 Soave-Redlich-Kwong (SRK) Family

SystemSrkEos

The standard SRK equation of state. Best for general gas and light hydrocarbon applications.

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.8);
fluid.addComponent("CO2", 0.2);
fluid.setMixingRule("classic");

SystemSrkPenelouxEos

SRK with Peneloux volume correction for improved liquid density predictions.

SystemInterface fluid = new SystemSrkPenelouxEos(300.0, 50.0);

SystemSrkMathiasCopeman

SRK with Mathias-Copeman alpha function for better vapor pressure predictions.

SystemInterface fluid = new SystemSrkMathiasCopeman(300.0, 50.0);

SystemSrkTwuCoonEos

SRK with Twu-Coon alpha function.

SystemInterface fluid = new SystemSrkTwuCoonEos(300.0, 50.0);

3.2 Peng-Robinson (PR) Family

SystemPrEos

Standard Peng-Robinson equation. Widely used for oil and gas applications.

SystemInterface fluid = new SystemPrEos(300.0, 50.0);
fluid.addComponent("methane", 0.7);
fluid.addComponent("n-heptane", 0.3);
fluid.setMixingRule("classic");

The PR equation is expressed as: $$ P = \frac{RT}{v - b} - \frac{a \alpha}{v(v + b) + b(v - b)} $$

SystemPrEos1978

Original 1978 Peng-Robinson formulation with modified alpha function.

SystemInterface fluid = new SystemPrEos1978(300.0, 50.0);

SystemPrMathiasCopeman

PR with Mathias-Copeman alpha function for polar components.

SystemInterface fluid = new SystemPrMathiasCopeman(300.0, 50.0);

3.3 Other Cubic EoS

SystemRKEos

Original Redlich-Kwong equation (historical interest, less accurate).

SystemInterface fluid = new SystemRKEos(300.0, 50.0);

SystemTSTEos

Twu-Sim-Tassone equation of state.

SystemInterface fluid = new SystemTSTEos(300.0, 50.0);

4. Advanced Equations of State

4.1 CPA (Cubic Plus Association)

CPA models add an association term to handle hydrogen bonding in polar molecules like water, alcohols, and glycols.

SystemSrkCPAstatoil

The Equinor (formerly Statoil) implementation of SRK-CPA. Recommended for water-hydrocarbon systems.

SystemInterface fluid = new SystemSrkCPAstatoil(300.0, 50.0);
fluid.addComponent("water", 0.1);
fluid.addComponent("methane", 0.85);
fluid.addComponent("MEG", 0.05);  // Mono-ethylene glycol
fluid.setMixingRule(10);  // CPA mixing rule with temperature/composition dependency

SystemSrkCPA / SystemSrkCPAs

Alternative CPA implementations.

SystemInterface fluid = new SystemSrkCPA(300.0, 50.0);
fluid.setMixingRule(7);  // CPA mixing rule

SystemPrCPA

Peng-Robinson with CPA association term.

SystemInterface fluid = new SystemPrCPA(300.0, 50.0);

4.2 PC-SAFT

Perturbed Chain Statistical Associating Fluid Theory. Good for polymers and complex molecules.

SystemInterface fluid = new SystemPCSAFT(300.0, 50.0);
fluid.addComponent("methane", 0.5);
fluid.addComponent("ethane", 0.5);

4.3 UMR-PRU (Universal Mixing Rule)

Peng-Robinson with UNIFAC-based mixing rules for improved predictions.

SystemInterface fluid = new SystemUMRPRUEos(300.0, 50.0);

5. Reference Equations (Helmholtz-Based)

For high-accuracy applications, NeqSim provides reference equations of state based on the Helmholtz free energy:

$$ \alpha(\delta, \tau, \bar{x}) = \alpha^0(\delta, \tau, \bar{x}) + \alpha^r(\delta, \tau, \bar{x}) $$

5.1 GERG-2008

The ISO 20765-2 standard for natural gas. Highest accuracy for custody transfer and fiscal metering.

Supported components (21): Methane, Nitrogen, CO2, Ethane, Propane, n-Butane, i-Butane, n-Pentane, i-Pentane, n-Hexane, n-Heptane, n-Octane, n-Nonane, n-Decane, Hydrogen, Oxygen, CO, Water, Helium, Argon.

import neqsim.thermo.system.SystemGERG2008Eos;

SystemInterface fluid = new SystemGERG2008Eos(288.15, 50.0);
fluid.addComponent("methane", 0.90);
fluid.addComponent("ethane", 0.05);
fluid.addComponent("propane", 0.03);
fluid.addComponent("nitrogen", 0.02);
fluid.createDatabase(true);

// Access GERG-specific properties
double density = fluid.getPhase(0).getDensity_GERG2008();

5.2 EOS-CG

Extension of GERG-2008 for CCS (Carbon Capture and Storage) applications. Includes combustion gas components.

Additional components: SO2, NO, NO2, and others relevant to flue gas.

import neqsim.thermo.system.SystemEOSCGEos;

SystemInterface fluid = new SystemEOSCGEos(300.0, 100.0);
fluid.addComponent("CO2", 0.95);
fluid.addComponent("nitrogen", 0.03);
fluid.addComponent("oxygen", 0.02);

5.3 Other Reference Equations

Class Description
SystemSpanWagnerEos Span-Wagner equation for CO2
SystemLeachmanEos Leachman equation for hydrogen
SystemBWRSEos Benedict-Webb-Rubin-Starling
SystemBnsEos BNS equation of state

6. Activity Coefficient Models

For non-ideal liquid mixtures, especially polar and chemical systems:

6.1 UNIFAC

Group contribution method for activity coefficients.

import neqsim.thermo.system.SystemUNIFAC;

SystemInterface fluid = new SystemUNIFAC(300.0, 1.0);
fluid.addComponent("methanol", 0.3);
fluid.addComponent("water", 0.7);

6.2 NRTL

Non-Random Two-Liquid model.

import neqsim.thermo.system.SystemNRTL;

SystemInterface fluid = new SystemNRTL(300.0, 1.0);
fluid.addComponent("ethanol", 0.4);
fluid.addComponent("water", 0.6);

6.3 GE-Wilson

Wilson equation for activity coefficients.

import neqsim.thermo.system.SystemGEWilson;

SystemInterface fluid = new SystemGEWilson(300.0, 1.0);

7. Electrolyte Models

For systems containing salts and ions in aqueous solutions:

7.1 Electrolyte-CPA (Equinor)

import neqsim.thermo.system.SystemElectrolyteCPAstatoil;

SystemInterface fluid = new SystemElectrolyteCPAstatoil(298.15, 1.0);
fluid.addComponent("water", 1.0);
fluid.addComponent("Na+", 0.1);
fluid.addComponent("Cl-", 0.1);

7.2 Søreide-Whitson

Modified PR for sour gas systems and brine.

import neqsim.thermo.system.SystemSoreideWhitson;

SystemSoreideWhitson fluid = new SystemSoreideWhitson(350.0, 200.0);
fluid.addComponent("methane", 0.7);
fluid.addComponent("CO2", 0.15);
fluid.addComponent("H2S", 0.05);
fluid.addComponent("water", 0.1);
fluid.addSalinity(2.0, "mole/sec");  // Add salinity
fluid.setMixingRule(11);  // Soreide-Whitson mixing rule

7.3 Pitzer Model

For concentrated electrolyte solutions.

import neqsim.thermo.system.SystemPitzer;

SystemInterface fluid = new SystemPitzer(298.15, 1.0);

8. Mixing Rules

Mixing rules determine how pure-component parameters are combined for mixtures. Set via setMixingRule():

8.1 Available Mixing Rules

Value Name Description
1 NO Classic with all kij = 0 (no interaction)
2 CLASSIC Classic van der Waals with kij from database
3 CLASSIC_HV Huron-Vidal with database parameters
4 HV Huron-Vidal including temperature-dependent HVDijT
5 WS Wong-Sandler (NRTL-based coupling)
7 CPA_MIX Classic with CPA kij from database
8 CLASSIC_T Classic with temperature-dependent kij
9 CLASSIC_T_CPA Classic T-dependent kij for CPA
10 CLASSIC_TX_CPA Classic T and composition dependent kij for CPA
11 SOREIDE_WHITSON Søreide-Whitson mixing rule
12 CLASSIC_T2 Alternative temperature-dependent classic

8.2 Setting Mixing Rules

// By integer value
fluid.setMixingRule(2);

// By name (string)
fluid.setMixingRule("classic");
fluid.setMixingRule("HV");
fluid.setMixingRule("WS");

8.3 Mixing Rule Recommendations

Application Recommended Mixing Rule
Light hydrocarbons classic (2)
CO2-hydrocarbon classic (2) with tuned kij
Polar mixtures HV (4) or WS (5)
Water-hydrocarbon (CPA) CPA_MIX (7) or CLASSIC_TX_CPA (10)
Sour gas with brine SOREIDE_WHITSON (11)

9. Adding Components

9.1 Basic Component Addition

// Add by name and moles
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);

// Add with flow rate and unit
fluid.addComponent("methane", 100.0, "kg/hr");
fluid.addComponent("ethane", 50.0, "Sm3/day");

// Add multiple components at once
String[] names = {"methane", "ethane", "propane"};
double[] moles = {0.85, 0.10, 0.05};
fluid.addComponents(names, moles);

9.2 Supported Units

For addComponent(name, value, unit):

9.3 Common Component Names

NeqSim's database includes hundreds of components. Common names:

Hydrocarbons: methane, ethane, propane, i-butane, n-butane, i-pentane, n-pentane, n-hexane, n-heptane, n-octane, n-nonane, n-decane

Inorganics: nitrogen, oxygen, CO2, H2S, water, hydrogen, helium, argon

Polar/Associating: methanol, ethanol, MEG (mono-ethylene glycol), TEG (tri-ethylene glycol), DEG

Ions: Na+, K+, Ca++, Mg++, Cl-, SO4--, HCO3-


10. Heavy Fraction Characterization

For petroleum fluids, NeqSim supports TBP (True Boiling Point) and plus-fraction characterization.

10.1 TBP Fractions

SystemInterface oil = new SystemSrkEos(350.0, 100.0);
oil.createDatabase(true);  // Required before adding TBP fractions

// addTBPfraction(name, moles, molarMass [g/mol], density [g/cm3])
oil.addTBPfraction("C7", 0.05, 96.0, 0.738);
oil.addTBPfraction("C8", 0.04, 107.0, 0.765);
oil.addTBPfraction("C9", 0.03, 121.0, 0.781);
oil.addTBPfraction("C10", 0.02, 134.0, 0.792);

oil.setMixingRule("classic");

10.2 Plus Fractions

// addPlusFraction(name, moles, molarMass [g/mol], density [g/cm3])
oil.addPlusFraction("C20+", 0.10, 350.0, 0.88);

10.3 TBP Characterization Models

NeqSim provides several models for estimating critical properties from TBP data:

// Set TBP model before adding fractions
fluid.getCharacterization().setTBPModel("PedersenSRK");  // Default for SRK
fluid.getCharacterization().setTBPModel("PedersenPR");   // Default for PR
fluid.getCharacterization().setTBPModel("Lee-Kesler");
fluid.getCharacterization().setTBPModel("Twu");
fluid.getCharacterization().setTBPModel("RiaziDaubert");

11. Complete Examples

11.1 Natural Gas Processing

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class NaturalGasExample {
    public static void main(String[] args) {
        // Create SRK fluid at pipeline conditions
        SystemInterface gas = new SystemSrkEos(283.15, 70.0);

        // Typical natural gas composition
        gas.addComponent("nitrogen", 0.02);
        gas.addComponent("CO2", 0.01);
        gas.addComponent("methane", 0.85);
        gas.addComponent("ethane", 0.06);
        gas.addComponent("propane", 0.03);
        gas.addComponent("i-butane", 0.01);
        gas.addComponent("n-butane", 0.01);
        gas.addComponent("i-pentane", 0.005);
        gas.addComponent("n-pentane", 0.005);

        gas.setMixingRule("classic");

        // Flash calculation
        ThermodynamicOperations ops = new ThermodynamicOperations(gas);
        ops.TPflash();
        gas.initProperties();

        // Display results
        System.out.println("Density: " + gas.getDensity("kg/m3") + " kg/m3");
        System.out.println("Z-factor: " + gas.getZ());
        System.out.println("Molecular weight: " + gas.getMolarMass() * 1000 + " g/mol");
    }
}

11.2 Water-Hydrocarbon System with CPA

import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class WaterHydrocarbonExample {
    public static void main(String[] args) {
        // CPA for associating systems
        SystemInterface fluid = new SystemSrkCPAstatoil(323.15, 50.0);

        fluid.addComponent("methane", 0.70);
        fluid.addComponent("ethane", 0.10);
        fluid.addComponent("propane", 0.05);
        fluid.addComponent("water", 0.10);
        fluid.addComponent("MEG", 0.05);

        fluid.setMixingRule(10);  // Temperature and composition dependent CPA

        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        ops.TPflash();

        System.out.println("Number of phases: " + fluid.getNumberOfPhases());
        fluid.prettyPrint();
    }
}

11.3 High-Accuracy Fiscal Metering (GERG-2008)

import neqsim.thermo.system.SystemGERG2008Eos;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class FiscalMeteringExample {
    public static void main(String[] args) {
        // GERG-2008 for custody transfer accuracy
        SystemInterface gas = new SystemGERG2008Eos(288.15, 40.0);

        gas.addComponent("methane", 0.92);
        gas.addComponent("ethane", 0.04);
        gas.addComponent("propane", 0.02);
        gas.addComponent("nitrogen", 0.01);
        gas.addComponent("CO2", 0.01);

        gas.createDatabase(true);

        ThermodynamicOperations ops = new ThermodynamicOperations(gas);
        ops.TPflash();

        // GERG-specific high-accuracy density
        double gergDensity = gas.getPhase(0).getDensity_GERG2008();
        System.out.println("GERG-2008 Density: " + gergDensity + " kg/m3");
    }
}

11.4 Oil Characterization

import neqsim.thermo.system.SystemPrEos;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class OilCharacterizationExample {
    public static void main(String[] args) {
        SystemInterface oil = new SystemPrEos(350.0, 150.0);
        oil.createDatabase(true);

        // Light ends
        oil.addComponent("nitrogen", 0.005);
        oil.addComponent("CO2", 0.02);
        oil.addComponent("methane", 0.35);
        oil.addComponent("ethane", 0.08);
        oil.addComponent("propane", 0.06);
        oil.addComponent("i-butane", 0.02);
        oil.addComponent("n-butane", 0.03);
        oil.addComponent("i-pentane", 0.02);
        oil.addComponent("n-pentane", 0.02);
        oil.addComponent("n-hexane", 0.03);

        // TBP fractions (moles, MW g/mol, density g/cm3)
        oil.addTBPfraction("C7", 0.05, 96.0, 0.738);
        oil.addTBPfraction("C8", 0.04, 107.0, 0.765);
        oil.addTBPfraction("C9", 0.03, 121.0, 0.781);
        oil.addTBPfraction("C10", 0.02, 134.0, 0.792);

        // Plus fraction
        oil.addPlusFraction("C11+", 0.18, 250.0, 0.85);

        oil.setMixingRule("classic");

        ThermodynamicOperations ops = new ThermodynamicOperations(oil);
        ops.TPflash();
        oil.initProperties();

        oil.prettyPrint();
    }
}

12. Model Selection Guidelines

Quick Reference Table

System Type Recommended Model Mixing Rule
Dry natural gas SystemSrkEos or SystemPrEos classic (2)
Wet gas / condensate SystemPrEos classic (2)
Black oil SystemPrEos with TBP classic (2)
Water-hydrocarbon SystemSrkCPAstatoil CLASSIC_TX_CPA (10)
Glycol dehydration SystemSrkCPAstatoil CPA_MIX (7)
Sour gas / brine SystemSoreideWhitson SOREIDE_WHITSON (11)
Fiscal metering SystemGERG2008Eos N/A
CCS / CO2 transport SystemEOSCGEos N/A
Electrolyte solutions SystemElectrolyteCPAstatoil N/A
Polar organics SystemUNIFAC or SystemNRTL N/A

Decision Flow

  1. Is high accuracy required for custody transfer? → Use GERG-2008
  2. Does the system contain water, glycols, or alcohols? → Use CPA models
  3. Is it a sour gas system with brine? → Use Søreide-Whitson
  4. Is it a standard hydrocarbon system? → Use SRK or PR
  5. Does it contain electrolytes? → Use Electrolyte-CPA or Pitzer
  6. Is it a non-ideal organic mixture? → Use UNIFAC or NRTL

Performance vs. Accuracy Trade-offs

Model Type Speed Accuracy Best For
Cubic (SRK/PR) Fast Good General process simulation
CPA Medium Very Good Polar/associating systems
GERG-2008 Slow Excellent Fiscal metering, calibration
UNIFAC Medium Good Chemical process design

See Also

Component Database

Pure Component Parameters Database (COMP)

This guide provides detailed documentation of the COMP database, which stores pure component parameters used by NeqSim's thermodynamic models. Understanding these parameters is essential for model selection, debugging, and extending NeqSim with new components.

Table of Contents


Database Overview

The COMP table is the primary pure component property database in NeqSim. It contains over 150 parameters per component, organized into functional groups that support different thermodynamic models and property calculations.

Key characteristics:


Database Location and Format

Item Value
File Path src/main/resources/data/COMP.csv
Runtime Path data/COMP.csv (in JAR)
Format CSV with header row
Encoding UTF-8
Primary Key ID (integer)
Lookup Key NAME (string, case-sensitive)

Parameter Categories

Basic Properties

Column Description Unit Model Usage
ID Unique component identifier - Internal indexing
NAME Component name for lookup - addComponent("methane", ...)
CASnumber CAS Registry Number - Component identification
COMPTYPE Component type classification - Model selection (see Component Types)
COMPINDEX Component index in database - Internal ordering
FORMULA Chemical formula - Element calculations
MOLARMASS Molar mass g/mol All models (stored internally as kg/mol)

Critical Properties

These parameters are fundamental to all cubic equations of state (SRK, PR, etc.).

Column Description Unit Model Usage
TC Critical temperature °C Converted to K internally: $T_c = T_{C,db} + 273.15$
PC Critical pressure bara SRK, PR, CPA EoS parameter a and b
ACSFACT Acentric factor (ω) - Alpha function: $m = f(\omega)$
CRITVOL Critical molar volume cm³/mol Critical compressibility: $Z_c = \frac{P_c V_c}{R T_c}$
NORMBOIL Normal boiling point °C Stored as K internally

Model linkage:

Vapor Pressure Parameters

Parameters for Antoine-type vapor pressure correlations.

Column Description Unit Model Usage
AntoineVapPresLiqType Equation type - pow10, log, exp, loglog
ANTOINEA Antoine A coefficient - Vapor pressure calculation
ANTOINEB Antoine B coefficient - Vapor pressure calculation
ANTOINEC Antoine C coefficient - Vapor pressure calculation
ANTOINED Antoine D coefficient - Extended Antoine
ANTOINEE Antoine E coefficient - Extended Antoine
ANTOINESolidA Solid vapor pressure A - Sublimation pressure
ANTOINESolidB Solid vapor pressure B - Sublimation pressure
ANTOINESolidC Solid vapor pressure C - Sublimation pressure

Antoine equation forms:

Ideal Gas Heat Capacity

Polynomial coefficients for ideal gas heat capacity: $C_p^{ig} = A + BT + CT^2 + DT^3 + ET^4$

Column Description Unit
CPA Coefficient A J/(mol·K)
CPB Coefficient B J/(mol·K²)
CPC Coefficient C J/(mol·K³)
CPD Coefficient D J/(mol·K⁴)
CPE Coefficient E J/(mol·K⁵)
CPsolid1-5 Solid phase Cp coefficients J/(mol·K)
CPliquid1-5 Liquid phase Cp coefficients J/(mol·K)

Usage: Enthalpy, entropy, and Gibbs energy departure functions for all EoS models.

Liquid Phase Properties

Column Description Unit Model Usage
LIQDENS Liquid density at standard conditions g/cm³ Density correlations
RACKETZ Rackett compressibility factor - Rackett liquid density: $V = V_c Z_{RA}^{[1+(1-T_r)^{2/7}]}$
racketZCPA Rackett Z for CPA model - CPA volume correction
volcorrSRK_T SRK volume translation - Péneloux correction: $V_{corr} = V_{EoS} - c$
volcorrCPA_T CPA volume translation - CPA Péneloux correction
STDDENS Standard density g/cm³ Reference conditions
LIQUIDDENSITYCOEFS1-5 Liquid density correlation coefficients - Temperature-dependent density

Transport Properties

Column Description Unit Model Usage
DIPOLEMOMENT Dipole moment Debye Polar corrections
VISCFACT Viscosity correction factor - Corresponding states
LIQVISCMODEL Liquid viscosity model type - Model selection (1-4)
LIQVISC1-4 Liquid viscosity parameters - Andrade equation: $\ln(\eta) = A + B/T + C\ln(T) + DT$
LIQUIDCONDUCTIVITY1-3 Liquid thermal conductivity - $k = A + BT + CT^2$
PARACHOR Parachor - Surface tension: $\sigma^{1/4} = P[\rho_L - \rho_V]$
PARACHOR_CPA Parachor for CPA model - CPA surface tension
criticalViscosity Critical viscosity Pa·s Transport correlations

Equation of State Parameters

Attractive Term Parameters

Column Description Unit Model Usage
PVMODEL PV model type - Classic for standard EoS
MC1, MC2, MC3 Mathias-Copeman parameters (SRK) - Enhanced alpha function
MCPR1, MCPR2, MCPR3 Mathias-Copeman parameters (PR) - PR alpha function
TwuCoon1-3 Twu-Coon alpha function parameters - Twu-Coon attractive term
SCHWARTZENTRUBER1-3 Schwartzentruber parameters - Schwartzentruber EoS
MC1Solid-MC3Solid Solid phase Mathias-Copeman - Solid fugacity

Mathias-Copeman alpha function: $$\alpha = [1 + c_1(1-\sqrt{T_r}) + c_2(1-\sqrt{T_r})^2 + c_3(1-\sqrt{T_r})^3]^2$$

Lennard-Jones Parameters

Column Description Unit Model Usage
LJDIAMETER LJ molecular diameter Å Gas viscosity, diffusion
LJEPS LJ energy parameter K ε/k_B
SphericalCoreRadius Hard-core radius - LJ potential
LJDIAMETERHYDRATE LJ diameter for hydrates Å Hydrate equilibrium
LJEPSHYDRATE LJ energy for hydrates K Hydrate cage interaction

Association Parameters (CPA/SAFT)

Parameters for Cubic-Plus-Association (CPA) and PC-SAFT models.

Column Description Unit Model Usage
associationsites Number of association sites - 0, 1, 2, 3, or 4
associationscheme Association scheme - 0, 1A, 2A, 2B, 3B, 4C
associationenergy Association energy (ε^AB) J/mol CPA association term
associationboundingvolume_SRK Association volume (β) for SRK-CPA - SRK-CPA
associationboundingvolume_PR Association volume (β) for PR-CPA - PR-CPA
aCPA_SRK CPA a parameter (SRK base) Pa·m⁶/mol² SRK-CPA
bCPA_SRK CPA b parameter (SRK base) m³/mol SRK-CPA
mCPA_SRK CPA m parameter (SRK base) - SRK-CPA
aCPA_PR CPA a parameter (PR base) Pa·m⁶/mol² PR-CPA
bCPA_PR CPA b parameter (PR base) m³/mol PR-CPA
mCPA_PR CPA m parameter (PR base) - PR-CPA

PC-SAFT Parameters

Column Description Unit Model Usage
mSAFT Number of segments - Chain length
sigmaSAFT Segment diameter Å Hard-sphere term
epsikSAFT Segment energy K ε/k_B
associationboundingvolume_PCSAFT Association volume - PC-SAFT association
associationenergy_PCSAFT Association energy K PC-SAFT association

Association schemes:

Scheme Sites Example Molecules
0 0 Non-associating (hydrocarbons)
1A 1 HCl, aromatic compounds
2A 2 CO₂ (electron donor/acceptor)
2B 2 Alcohols (1 proton donor, 1 acceptor)
3B 3 Amines
4C 4 Water, glycols (2 donors, 2 acceptors)

Hydrate Parameters

Parameters for gas hydrate equilibrium calculations.

Column Description Unit Model Usage
HydrateFormer Hydrate-forming capability - yes or no
HydrateA1Small, HydrateB1Small Type I small cage (512) - Langmuir constants
HydrateA1Large, HydrateB1Large Type I large cage (51262) - Langmuir constants
HydrateA2Small, HydrateB2Small Type II small cage (512) - Langmuir constants
HydrateA2Large, HydrateB2Large Type II large cage (51264) - Langmuir constants
A1_smallGF-B2_largeGF Graffis parameters - Alternative parameterization
SphericalCoreRadiusHYDRATE Core radius for hydrates - Cavity occupation

Thermodynamic Reference Data

Column Description Unit Model Usage
Href Reference enthalpy J/mol Enthalpy calculations
GIBBSENERGYOFFORMATION Gibbs energy of formation J/mol Chemical equilibrium
ENTHALPYOFFORMATION Standard enthalpy of formation J/mol Reaction thermodynamics
ABSOLUTEENTROPY Absolute entropy J/(mol·K) Entropy calculations
HEATOFFUSION Heat of fusion J/mol Solid-liquid equilibrium
Hsub Heat of sublimation J/mol Solid-vapor equilibrium
TRIPLEPOINTTEMPERATURE Triple point temperature K Phase boundaries
TRIPLEPOINTPRESSURE Triple point pressure bar Phase boundaries
TRIPLEPOINTDENSITY Triple point density kg/m³ Reference state
MELTINGPOINTTEMPERATURE Melting point K Solid calculations

Ionic and Electrolyte Parameters

Column Description Unit Model Usage
IONICCHARGE Ionic charge - Electrolyte models
REFERENCESTATETYPE Reference state - solvent or solute
DIELECTRICPARAMETER1-5 Dielectric parameters - Electrolyte activity
DeshMatIonicDiameter Debye-Hückel diameter Å Electrolyte models
calcActivity Activity calculation flag - 0 or 1

Henry's Law Parameters

Column Description Unit
HenryCoef1 Henry constant A -
HenryCoef2 Henry constant B -
HenryCoef3 Henry constant C -
HenryCoef4 Henry constant D -

Henry's law correlation: $$\ln(H) = A + \frac{B}{T} + C\ln(T) + DT$$

Solid Phase Parameters

Column Description Unit
SOLIDDENSITYCOEFS1-5 Solid density coefficients -
HEATOFVAPORIZATIONCOEFS1-5 Heat of vaporization coefficients -
waxformer Wax-forming component -

Parameter Reference Table

Complete parameter list with units and typical values:

Parameter Unit Example (methane) Example (water)
MOLARMASS g/mol 16.043 18.015
TC °C -82.59 374.15
PC bara 45.99 220.89
ACSFACT - 0.0115 0.344
CRITVOL cm³/mol 99.0 56.0
NORMBOIL °C -161.55 100.0
LIQDENS g/cm³ 0.422 0.999
RACKETZ - 0.0 0.0
DIPOLEMOMENT Debye 0.0 1.8
associationsites - 0 4
HydrateFormer - yes no

How Parameters Feed into Models

┌─────────────────────────────────────────────────────────────────┐
│                      COMP Database                              │
├─────────────────────────────────────────────────────────────────┤
│  TC, PC, ACSFACT  ──────────────> Cubic EoS (SRK, PR)          │
│  MC1, MC2, MC3    ──────────────> Mathias-Copeman α(T)         │
│  TwuCoon1-3       ──────────────> Twu-Coon α(T)                │
│  aCPA, bCPA, mCPA ──────────────> CPA EoS                      │
│  associationsites ──────────────> CPA/SAFT Association         │
│  mSAFT, σSAFT, εSAFT ──────────> PC-SAFT                       │
│  UNIFAC groups    ──────────────> Activity models              │
│  LJDIAMETER, LJEPS ─────────────> Transport properties         │
│  HydrateA/B params ─────────────> Hydrate equilibrium          │
│  CPA, CPB, CPC... ──────────────> Enthalpy/Entropy             │
│  ANTOINEA-E       ──────────────> Vapor pressure               │
└─────────────────────────────────────────────────────────────────┘

Model-Parameter Mapping

Model Class Key Parameters
SystemSrkEos TC, PC, ACSFACT, MC1-3, RACKETZ
SystemPrEos TC, PC, ACSFACT, MCPR1-3
SystemSrkCPA aCPA_SRK, bCPA_SRK, mCPA_SRK, associationsites, associationenergy, associationboundingvolume_SRK
SystemPrCPA aCPA_PR, bCPA_PR, mCPA_PR, associationboundingvolume_PR
SystemPCSAFT mSAFT, sigmaSAFT, epsikSAFT, associationboundingvolume_PCSAFT
SystemGERG2008Eos Uses internal GERG parameters, but TC/PC for initialization
SystemUNIFAC TC, PC (for vapor), UNIFAC groups from UNIFACcomp table

Component Types

The COMPTYPE field classifies components for model selection:

Type Description Examples
HC Hydrocarbon methane, ethane, propane, benzene
inert Inert gas nitrogen, CO2, oxygen, argon
ion Ionic species Na+, Cl-, HCO3-, Ca++
amine Amine compounds MDEA, MEA, DEA
alcohol Alcohols methanol, ethanol
glycol Glycol compounds MEG, DEG, TEG
ice Ice/solid water ice
TBP TBP pseudo-component Generated from characterization
plus Plus fraction C7+, C10+, etc.

Accessing Parameters in Code

Reading Component Properties

// Create a system and access component properties
SystemInterface fluid = new SystemSrkEos(298.15, 10.0);
fluid.addComponent("methane", 1.0);
fluid.init(0);

// Access pure component parameters
ComponentInterface comp = fluid.getPhase(0).getComponent("methane");
double Tc = comp.getTC();           // Critical temperature [K]
double Pc = comp.getPC();           // Critical pressure [bara]
double omega = comp.getAcentricFactor();  // Acentric factor [-]
double Mw = comp.getMolarMass();    // Molar mass [kg/mol]
double Tb = comp.getNormalBoilingPoint(); // Normal boiling point [K]

System.out.println("Methane Tc = " + Tc + " K");
System.out.println("Methane Pc = " + Pc + " bara");
System.out.println("Methane ω = " + omega);

Modifying Component Properties

// Modify properties for sensitivity analysis
comp.setTC(190.6);  // Set new Tc in Kelvin
comp.setPC(46.0);   // Set new Pc in bara
comp.setAcentricFactor(0.012);

// Re-initialize to apply changes
fluid.init(0);

Adding Custom Components

Method 1: Database Modification

Add a new row to COMP.csv with all required parameters.

Method 2: Runtime Addition

// Add a pseudo-component with custom properties
SystemInterface fluid = new SystemSrkEos(298.15, 50.0);

// Add TBP fraction with molar mass and density
fluid.addTBPfraction("C7_custom", 0.1, 95.0, 0.72);  // name, moles, MW, SG

// Or add component and modify properties
fluid.addComponent("n-heptane", 1.0);
ComponentInterface comp = fluid.getPhase(0).getComponent("n-heptane");
comp.setTC(540.0);
comp.setPC(27.4);
comp.setAcentricFactor(0.35);

// Initialize database for binary parameters
fluid.createDatabase(true);
fluid.setMixingRule(2);

Method 3: Temporary Tables

// Enable temporary tables for session-specific components
NeqSimDataBase.setCreateTemporaryTables(true);

// Components added to "comptemp" table
fluid.getPhase(0).getComponent(0).insertComponentIntoDatabase("comptemp");

// Remember to disable after use
NeqSimDataBase.setCreateTemporaryTables(false);

The COMP table works with several related tables:

Table Purpose Key Columns
INTER Binary interaction parameters (kij) comp1, comp2, kij, model
UNIFACcomp UNIFAC group assignments compname, group, count
UNIFACGroupParam UNIFAC group parameters groupid, R, Q
UNIFACInterParam* UNIFAC group interaction parameters group1, group2, aij
MBWR32param MBWR equation parameters comp, coefficients
AdsorptionParameters Adsorption isotherm parameters comp, adsorbent, params

See Also


References

  1. Soave, G. (1972). Equilibrium constants from a modified Redlich-Kwong equation of state. Chemical Engineering Science, 27(6), 1197-1203.
  2. Peng, D. Y., & Robinson, D. B. (1976). A new two-constant equation of state. Industrial & Engineering Chemistry Fundamentals, 15(1), 59-64.
  3. Kontogeorgis, G. M., et al. (1999). An equation of state for associating fluids. Industrial & Engineering Chemistry Research, 38(10), 4073-4082.
  4. Gross, J., & Sadowski, G. (2001). Perturbed-chain SAFT: An equation of state based on a perturbation theory for chain molecules. Industrial & Engineering Chemistry Research, 40(4), 1244-1260.

Component Package

Component Package

Documentation for component modeling in NeqSim.

Table of Contents


Overview

Location: neqsim.thermo.component

The component package contains 65+ classes for modeling pure component properties and their behavior in mixtures.


Component Interface

Accessing Components

// By name
ComponentInterface methane = fluid.getComponent("methane");

// By index
ComponentInterface comp = fluid.getComponent(0);

// In specific phase
ComponentInterface methaneInGas = fluid.getGasPhase().getComponent("methane");

Common Methods

ComponentInterface comp = fluid.getComponent("methane");

// Pure component properties
double Tc = comp.getTC();           // Critical temperature (K)
double Pc = comp.getPC();           // Critical pressure (bar)
double omega = comp.getAcentricFactor();
double MW = comp.getMolarMass();    // kg/mol

// Composition
double z = comp.getz();             // Overall mole fraction
double x = comp.getx();             // Phase mole fraction
double n = comp.getNumberOfMolesInPhase();

// Fugacity
double f = comp.getFugacity();
double phi = comp.getFugacityCoefficient();

Pure Component Properties

Critical Properties

ComponentInterface comp = fluid.getComponent("propane");

double Tc = comp.getTC();        // 369.83 K
double Pc = comp.getPC();        // 42.48 bar
double Vc = comp.getVc();        // Critical volume (m³/mol)
double Zc = comp.getZc();        // Critical Z-factor
double omega = comp.getAcentricFactor();  // 0.1523

Physical Properties

// Molecular properties
double MW = comp.getMolarMass();            // kg/mol
double Tb = comp.getNormalBoilingPoint();   // K
double Tf = comp.getTriplePointTemperature(); // K

// Reference properties
double dHf = comp.getEnthalpyOfFormation(); // kJ/mol
double dGf = comp.getGibbsEnergyOfFormation();
double Href = comp.getReferencePotential();

EoS Parameters

// SRK/PR parameters
double a = comp.geta();     // Attraction parameter
double b = comp.getb();     // Co-volume parameter

// CPA parameters (for associating)
double eps = comp.getAssociationEnergy();
double beta = comp.getAssociationVolume();

// PC-SAFT parameters
double m = comp.getmSAFTi();        // Segment number
double sigma = comp.getSigmaSAFTi(); // Segment diameter
double epsilon = comp.getEpsSAFTi(); // Dispersion energy

Component in Phase

Phase Composition

PhaseInterface gas = fluid.getGasPhase();
ComponentInterface methane = gas.getComponent("methane");

// Mole fraction in phase
double x_i = methane.getx();

// Fugacity coefficient
double phi_i = methane.getFugacityCoefficient();
double lnPhi = methane.getLogFugacityCoefficient();

// Fugacity
double f_i = methane.getFugacity();

// Chemical potential
double mu_i = methane.getChemicalPotential();

// Activity (for liquid phases)
double a_i = methane.getActivity();
double gamma = methane.getActivityCoefficient();

Partial Properties

// Partial molar volume
double Vbar_i = comp.getPartialMolarVolume();

// Partial molar enthalpy
double Hbar_i = comp.getPartialMolarEnthalpy();

// Partial molar entropy
double Sbar_i = comp.getPartialMolarEntropy();

Derivatives

// Fugacity coefficient derivatives
double dPhidT = comp.getdfugdT();        // d(ln φ)/dT
double dPhidP = comp.getdfugdP();        // d(ln φ)/dP
double dPhidx = comp.getdfugdx(j);       // d(ln φ_i)/dx_j

Component Types

EoS Components

// Standard EoS component
ComponentEos compEos = (ComponentEos) fluid.getComponent("methane");

// Access EoS-specific properties
double ai = compEos.getaT();  // Temperature-dependent a
double bi = compEos.getb();
double alphai = compEos.getAlpha();

CPA Components

// For associating components
ComponentCPA compCPA = (ComponentCPA) fluid.getComponent("water");

// Association properties
double eps = compCPA.getAssociationEnergy();
double beta = compCPA.getAssociationVolume();
int sites = compCPA.getNumberOfAssociationSites();

// Association fraction
double X_A = compCPA.getXsite(0);  // Fraction of site A unbonded

PC-SAFT Components

ComponentPCSAFT compSAFT = (ComponentPCSAFT) fluid.getComponent("n-hexane");

double m = compSAFT.getmSAFTi();      // Chain length
double sigma = compSAFT.getSigmaSAFTi(); // Segment diameter (Å)
double eps = compSAFT.getEpsSAFTi();  // Dispersion energy (K)

Electrolyte Components

// Ions
ComponentElectrolyte ion = (ComponentElectrolyte) fluid.getComponent("Na+");

double charge = ion.getIonicCharge();
double diameter = ion.getIonicDiameter();

Pseudo-Components

// Plus fraction components
ComponentTBP plus = (ComponentTBP) fluid.getComponent("C7+");

double Tb = plus.getNormalBoilingPoint();
double SG = plus.getSpecificGravity();
double MW = plus.getMolarMass();

Database Components

Available Components

NeqSim includes a database with 100+ components:

Category Examples
Light gases nitrogen, oxygen, CO2, H2S, hydrogen
Hydrocarbons methane through n-C20
Cyclic cyclohexane, benzene, toluene
Polar water, methanol, ethanol, MEG, DEG, TEG
Amines MDEA, MEA, DEA, piperazine
Refrigerants R-134a, R-32, ammonia

Adding Components

// Add from database
fluid.addComponent("methane", 1.0);

// Add with alias
fluid.addComponent("CO2", 0.5);  // Carbon dioxide

// Add pseudo-component (TBP method)
fluid.addTBPfraction("C10", 0.1, 140.0, 0.75);  // name, moles, MW, SG

// Add plus fraction
fluid.addPlusFraction("C7+", 0.05, 150.0, 0.78);

Component Name Lookup

// Check if component exists
boolean exists = fluid.hasComponent("methane");

// Get component index
int index = fluid.getComponentIndex("ethane");

Example: Component Properties Report

import neqsim.thermo.system.SystemSrkEos;

SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.setMixingRule("classic");
fluid.init(0);

System.out.println("Component Properties:");
System.out.println("-------------------------------------------");
System.out.printf("%-10s %8s %8s %8s %8s%n", 
    "Name", "Tc(K)", "Pc(bar)", "omega", "MW");
System.out.println("-------------------------------------------");

for (int i = 0; i < fluid.getNumberOfComponents(); i++) {
    ComponentInterface comp = fluid.getComponent(i);
    System.out.printf("%-10s %8.2f %8.2f %8.4f %8.4f%n",
        comp.getName(),
        comp.getTC(),
        comp.getPC(),
        comp.getAcentricFactor(),
        comp.getMolarMass() * 1000);  // g/mol
}

Mathematical Models

Mathematical Models in NeqSim

NeqSim bundles several thermodynamic and transport models so you can switch between correlations without rewriting system setup code. The sections below summarize the most commonly used options and when to consider them.

Equations of State (EoS)

NeqSim primarily uses cubic equations of state of the general form:

[ P = \frac{RT}{v - b} - \frac{a(T)}{(v + \epsilon b)(v + \sigma b)} ]

where $P$ is pressure, $T$ is temperature, $v$ is molar volume, $R$ is the gas constant, and $a(T), b$ are the energy and co-volume parameters.

Supported Families

Selecting an EoS

SystemInterface fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("n-heptane", 0.1);
fluid.setMixingRule(2); // 2 = Huron–Vidal; use 1 for classical van der Waals

Use SystemSrkCPAstatoil or SystemSrkCPAs when hydrogen bonding is important, and prefer PR variants for high-pressure gas processing.

Reference Equations of State (Helmholtz Energy)

For high-accuracy applications involving natural gas or CCS mixtures, NeqSim supports multi-parameter equations of state explicit in the Helmholtz free energy:

See the GERG-2008 and EOS-CG guide for details.

Activity-Coefficient Models

NeqSim provides NRTL/UNIQUAC/UNIFAC variants for non-ideal liquid mixtures. They can be used directly for gamma-phi flashes or combined with cubic EoS via Wong–Sandler mixing rules.

SystemInterface fluid = new SystemFurstElectrolyteEos(298.15, 1.0);
fluid.addComponent("water", 1.0);
fluid.addComponent("ethanol", 1.0);
fluid.setMixingRule(4); // enables Wong–Sandler (NRTL) coupling

Load binary-interaction parameters via mixingRuleName or by reading custom datasets to align with lab data.

Hydrate and Solid Models

For hydrate prediction, enable hydrateCheck(true) and select a hydrate model (CPA-based or classical van der Waals–Platteeuw) depending on accuracy and speed requirements. Wax precipitation can be modeled using solid-phase enabled systems (e.g., PR with solid checks) and tuned heavy-end characterizations.

Transport and Physical Property Correlations

Choose property packages that match the flow regime: cubic EoS with corresponding-state transport for gas processing, CPA with association corrections for aqueous systems, and heavy-oil tuned correlations for late-life reservoirs.

Chapter 6: Equations of State

Thermodynamic Models

Thermodynamic Models in NeqSim

This document provides a comprehensive overview of the thermodynamic models available in NeqSim, their theoretical foundations, and practical guidance on when and how to use each model. Models are classified into categories based on their mathematical formulation and application domain.

Table of Contents

  1. Introduction
  2. Model Categories Overview
  3. Cubic Equations of State
  4. CPA (Cubic Plus Association) Models
  5. Reference Equations (Helmholtz-Based)
  6. Activity Coefficient (GE) Models
  7. Electrolyte Models
  8. Mixing Rules
  9. Auto-Select Model Feature
  10. Model Selection Guidelines
  11. Complete Reference Tables

1. Introduction

NeqSim (Non-Equilibrium Simulator) provides a rich library of thermodynamic models for simulating phase equilibria, physical properties, and process operations. Choosing the appropriate thermodynamic model is crucial for obtaining accurate results in process simulation.

General Workflow

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermo.system.SystemInterface;

// 1. Choose and instantiate the thermodynamic model
SystemInterface fluid = new SystemSrkEos(298.15, 10.0);  // T [K], P [bara]

// 2. Add components
fluid.addComponent("methane", 0.90);
fluid.addComponent("ethane", 0.10);

// 3. Set an appropriate mixing rule
fluid.setMixingRule("classic");

// 4. Initialize and perform calculations
fluid.init(0);

2. Model Categories Overview

Category Mathematical Basis Key Use Cases Examples
Cubic EoS $P = \frac{RT}{v-b} - \frac{a(T)}{(v+\epsilon b)(v+\sigma b)}$ General hydrocarbons, gas processing SRK, PR
CPA Cubic EoS + Association term Polar/associating fluids (water, glycols, alcohols) SRK-CPA, PR-CPA
Reference EoS Helmholtz free energy explicit High-accuracy metering, CCS GERG-2008, EOS-CG
Activity Coefficient Excess Gibbs energy ($G^E$) Non-ideal liquid mixtures UNIFAC, NRTL
Electrolyte CPA + Electrostatic contributions Aqueous salt solutions Electrolyte-CPA, Pitzer
Specialized Modified equations Specific applications Søreide-Whitson

3. Cubic Equations of State

3.1 Theoretical Foundation

Cubic equations of state express pressure as a function of temperature and molar volume in a cubic polynomial form:

$$ P = \frac{RT}{v - b} - \frac{a(T)}{(v + \epsilon b)(v + \sigma b)} $$

Where:

The energy parameter typically uses an alpha function:

$$ a(T) = a_c \cdot \alpha(T_r, \omega) $$

Where $T_r = T/T_c$ is the reduced temperature and $\omega$ is the acentric factor.

3.2 Soave-Redlich-Kwong (SRK) Family

For SRK: $\epsilon = 0$, $\sigma = 1$

$$ P = \frac{RT}{v - b} - \frac{a(T)}{v(v + b)} $$

Class Description Best For
SystemSrkEos Standard SRK General gas/light hydrocarbon
SystemSrkPenelouxEos SRK with Peneloux volume correction Improved liquid density
SystemSrkMathiasCopeman SRK with Mathias-Copeman alpha function Better vapor pressure
SystemSrkTwuCoonEos SRK with Twu-Coon alpha function Polar components
SystemSrkSchwartzentruberEos SRK-Schwartzentruber Polar/associated fluids

Example: Standard SRK

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("CO2", 0.10);
fluid.addComponent("nitrogen", 0.05);
fluid.setMixingRule("classic");

3.3 Peng-Robinson (PR) Family

For PR: $\epsilon = 1 - \sqrt{2}$, $\sigma = 1 + \sqrt{2}$

$$ P = \frac{RT}{v - b} - \frac{a(T)}{v(v + b) + b(v - b)} $$

Class Description Best For
SystemPrEos Standard PR General oil & gas
SystemPrEos1978 Original 1978 formulation Classic applications
SystemPrMathiasCopeman PR with Mathias-Copeman alpha Polar components
SystemPrDanesh Modified PR Heavy oil systems
SystemPrEosvolcor PR with volume correction Liquid density
SystemPrEosDelft1998 Delft 1998 modification Gas condensates

Example: Peng-Robinson

SystemInterface fluid = new SystemPrEos(350.0, 150.0);
fluid.addComponent("methane", 0.70);
fluid.addComponent("n-heptane", 0.20);
fluid.addComponent("n-decane", 0.10);
fluid.setMixingRule("classic");

3.4 Other Cubic EoS

Class Description
SystemRKEos Original Redlich-Kwong (historical)
SystemTSTEos Twu-Sim-Tassone equation
SystemBWRSEos Benedict-Webb-Rubin-Starling (extended virial)

4. CPA (Cubic Plus Association) Models

4.1 Theoretical Foundation

CPA (Cubic Plus Association) extends cubic EoS to handle hydrogen bonding in polar molecules like water, alcohols, and glycols. The pressure is expressed as:

$$ P = P_{\text{cubic}} + P_{\text{association}} $$

The association term accounts for hydrogen bonding:

$$ P_{\text{association}} = -\frac{1}{2} RT \rho \sum_i x_i \sum_{A_i} \left( 1 - X_{A_i} \right) \frac{\partial \ln g}{\partial v} $$

Where:

The non-bonded site fraction $X_{A_i}$ is determined by solving:

$$ X_{A_i} = \frac{1}{1 + \rho \sum_j x_j \sum_{B_j} X_{B_j} \Delta^{A_i B_j}} $$

Where $\Delta^{A_i B_j}$ is the association strength between site A on molecule $i$ and site B on molecule $j$.

4.2 Available CPA Models

Class Description Mixing Rule
SystemSrkCPAstatoil Recommended Equinor SRK-CPA implementation 10
SystemSrkCPA Standard SRK-CPA 7
SystemSrkCPAs Alternative SRK-CPA 7
SystemPrCPA Peng-Robinson with CPA 7
SystemUMRCPAEoS UMR-CPA with UNIFAC -

4.3 Association Schemes

Scheme Sites Examples
4C 2 donors + 2 acceptors Water, glycols
2B 1 donor + 1 acceptor Alcohols
CR-1 Cross-association Water-MEG, Water-methanol

4.4 Example: Water-Hydrocarbon System

import neqsim.thermo.system.SystemSrkCPAstatoil;

SystemInterface fluid = new SystemSrkCPAstatoil(323.15, 50.0);
fluid.addComponent("methane", 0.70);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.addComponent("water", 0.10);
fluid.addComponent("MEG", 0.05);  // Mono-ethylene glycol

// Use mixing rule 10 (recommended for CPA)
fluid.setMixingRule(10);  // CLASSIC_TX_CPA

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

5. Reference Equations (Helmholtz-Based)

5.1 Theoretical Foundation

Reference equations of state are explicit in the dimensionless Helmholtz free energy $\alpha$:

$$ \alpha(\delta, \tau, \bar{x}) = \frac{a(\rho, T, \bar{x})}{RT} = \alpha^0(\delta, \tau, \bar{x}) + \alpha^r(\delta, \tau, \bar{x}) $$

Where:

The residual contribution is fitted to high-accuracy experimental data:

$$ \alpha^r(\delta, \tau, \bar{x}) = \sum_{i=1}^{N} x_i \alpha_{0i}^r(\delta, \tau) + \sum_{i=1}^{N-1} \sum_{j=i+1}^{N} x_i x_j F_{ij} \alpha_{ij}^r(\delta, \tau) $$

These models provide superior accuracy for density, speed of sound, and heat capacity compared to cubic EoS.

5.2 GERG-2008

Standard: ISO 20765-2
Application: Natural gas custody transfer, fiscal metering
Accuracy: ±0.1% in density for typical natural gas

Supported Components (21): Methane, Nitrogen, CO2, Ethane, Propane, n-Butane, i-Butane, n-Pentane, i-Pentane, n-Hexane, n-Heptane, n-Octane, n-Nonane, n-Decane, Hydrogen, Oxygen, CO, Water, Helium, Argon

import neqsim.thermo.system.SystemGERG2008Eos;

SystemInterface fluid = new SystemGERG2008Eos(288.15, 50.0);
fluid.addComponent("methane", 0.92);
fluid.addComponent("ethane", 0.04);
fluid.addComponent("propane", 0.02);
fluid.addComponent("nitrogen", 0.01);
fluid.addComponent("CO2", 0.01);
fluid.createDatabase(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Access GERG-specific high-accuracy density
double gergDensity = fluid.getPhase(0).getDensity_GERG2008();

5.3 GERG-2008-H2 (Hydrogen Enhanced)

Extension for hydrogen-rich blends with improved H2 binary parameters:

SystemGERG2008Eos fluid = new SystemGERG2008Eos(300.0, 50.0);
fluid.addComponent("methane", 0.7);
fluid.addComponent("hydrogen", 0.3);

// Enable hydrogen-enhanced model
fluid.useHydrogenEnhancedModel();

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

5.4 EOS-CG

Application: Carbon Capture and Storage (CCS), combustion gases
Extension: Includes SO2, NO, NO2, HCl, Cl2, COS in addition to GERG-2008 components

import neqsim.thermo.system.SystemEOSCGEos;

SystemInterface fluid = new SystemEOSCGEos(300.0, 100.0);
fluid.addComponent("CO2", 0.95);
fluid.addComponent("nitrogen", 0.03);
fluid.addComponent("SO2", 0.02);
fluid.createDatabase(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

double density = fluid.getPhase(0).getDensity_EOSCG();

5.5 Other Reference Equations

Class Description Application
SystemSpanWagnerEos Span-Wagner equation Pure CO2
SystemLeachmanEos Leachman equation Pure hydrogen
SystemVegaEos Vega equation Specialized applications
SystemAmmoniaEos Ammonia-specific Ammonia systems

6. Activity Coefficient (GE) Models

6.1 Theoretical Foundation

Activity coefficient models describe non-ideal liquid behavior through the excess Gibbs energy:

$$ G^E = RT \sum_i x_i \ln \gamma_i $$

Where $\gamma_i$ is the activity coefficient of component $i$.

6.2 UNIFAC (Universal Functional Activity Coefficient)

Group contribution method that predicts activity coefficients from molecular group interactions:

$$ \ln \gamma_i = \ln \gamma_i^C + \ln \gamma_i^R $$

Where the combinatorial ($C$) and residual ($R$) contributions are calculated from group properties.

import neqsim.thermo.system.SystemUNIFAC;

SystemInterface fluid = new SystemUNIFAC(300.0, 1.0);
fluid.addComponent("methanol", 0.3);
fluid.addComponent("water", 0.7);

6.3 NRTL (Non-Random Two-Liquid)

Local composition model with binary interaction parameters:

$$ \ln \gamma_i = \frac{\sum_j x_j \tau_{ji} G_{ji}}{\sum_k x_k G_{ki}} + \sum_j \frac{x_j G_{ij}}{\sum_k x_k G_{kj}} \left( \tau_{ij} - \frac{\sum_m x_m \tau_{mj} G_{mj}}{\sum_k x_k G_{kj}} \right) $$

import neqsim.thermo.system.SystemNRTL;

SystemInterface fluid = new SystemNRTL(300.0, 1.0);
fluid.addComponent("ethanol", 0.4);
fluid.addComponent("water", 0.6);

6.4 Other GE Models

Class Description
SystemGEWilson Wilson equation
SystemUNIFACpsrk UNIFAC with PSRK parameters
SystemUMRPRUEos Peng-Robinson with UNIFAC mixing
SystemUMRPRUMCEos UMR-PRU with Mathias-Copeman

7. Electrolyte Models

7.1 Theoretical Foundation

Electrolyte models extend CPA with electrostatic contributions for aqueous salt solutions:

$$ A^{res} = A^{CPA} + A^{elec} $$

The electrostatic contribution typically includes:

$$ A^{elec} = A^{MSA} + A^{Born} + A^{SR} $$

Where:

Born Solvation Term:

$$ \frac{A^{Born}}{RT} = -\frac{e^2 N_A}{8\pi\varepsilon_0 k_B T} \sum_i n_i \frac{z_i^2}{\sigma_i} \left(1 - \frac{1}{\varepsilon_r}\right) $$

7.2 Electrolyte-CPA (Equinor)

Recommended for: Aqueous electrolyte solutions with associating solvents

import neqsim.thermo.system.SystemElectrolyteCPAstatoil;

SystemInterface system = new SystemElectrolyteCPAstatoil(298.15, 1.01325);
system.addComponent("water", 55.5);
system.addComponent("Na+", 1.0);
system.addComponent("Cl-", 1.0);
system.chemicalReactionInit();  // Enable pH and speciation
system.createDatabase(true);
system.setMixingRule(10);  // Required for electrolyte CPA

ThermodynamicOperations ops = new ThermodynamicOperations(system);
ops.TPflash();

// Access activity coefficients
double meanGamma = system.getPhase(0).getMeanIonicActivityCoefficient("Na+", "Cl-");

7.3 Søreide-Whitson Model

Application: Sour gas systems with brine

Uses salinity-dependent binary interaction parameters for CO2-water, H2S-water, and hydrocarbon-water systems:

$$ k_{ij,\text{CO}_2-\text{water}} = -0.31092(1 + 0.156 S^{0.75}) + 0.236(1 + 0.178 S^{0.98}) T_r - 21.26 e^{-6.72^{T_r} - S} $$

import neqsim.thermo.system.SystemSoreideWhitson;

SystemSoreideWhitson fluid = new SystemSoreideWhitson(350.0, 200.0);
fluid.addComponent("methane", 0.70);
fluid.addComponent("CO2", 0.15);
fluid.addComponent("H2S", 0.05);
fluid.addComponent("water", 0.10);
fluid.addSalinity(2.0, "mole/sec");  // Add NaCl salinity
fluid.setMixingRule(11);  // Søreide-Whitson mixing rule

7.4 Other Electrolyte Models

Class Description Use Case
SystemPitzer Pitzer model Concentrated brines
SystemDesmukhMather Desmukh-Mather Amine systems
SystemKentEisenberg Kent-Eisenberg CO2/H2S in amines
SystemDuanSun Duan-Sun CO2 solubility in brine
SystemFurstElectrolyteEos Fürst electrolyte EoS General electrolytes

8. Mixing Rules

8.1 Overview

Mixing rules determine how pure-component parameters are combined for mixtures. The choice of mixing rule is as important as the choice of equation of state.

8.2 Classic Mixing Rules

van der Waals One-Fluid Mixing Rule:

$$ a_{mix} = \sum_i \sum_j x_i x_j a_{ij} $$

$$ b_{mix} = \sum_i x_i b_i $$

With combining rule:

$$ a_{ij} = \sqrt{a_i a_j}(1 - k_{ij}) $$

Type Name Description
1 NO All $k_{ij} = 0$ (no interactions)
2 CLASSIC Classic with database $k_{ij}$
8 CLASSIC_T Temperature-dependent: $k_{ij}(T) = k_{ij,0} + k_{ij,T} \cdot T$
12 CLASSIC_T2 Inverse T-dependency: $k_{ij}(T) = k_{ij,0} + k_{ij,T}/T$

8.3 Huron-Vidal Mixing Rules

Combines cubic EoS with activity coefficient models at infinite pressure:

$$ a_{mix} = b_{mix} \left( \sum_i x_i \frac{a_i}{b_i} - \frac{G^E}{\Lambda} \right) $$

Where $\Lambda \approx 0.693$ for SRK.

Type Name Description
3 CLASSIC_HV Huron-Vidal with database NRTL parameters
4 HV HV with temperature-dependent parameters

Usage with GE model:

fluid.setMixingRule("HV", "UNIFAC_UMRPRU");
fluid.setMixingRule("HV", "NRTL");

8.4 Wong-Sandler Mixing Rule

Matches both second virial coefficient and excess Gibbs energy:

$$ b_{mix} = \frac{\sum_i \sum_j x_i x_j \left( b - \frac{a}{RT} \right)_{ij}}{1 - \frac{A_\infty^E}{CRT} - \sum_i x_i \frac{a_i}{RTb_i}} $$

Type Name Description
5 WS Wong-Sandler (NRTL-based)
fluid.setMixingRule("WS", "NRTL");

8.5 CPA Mixing Rules

Type Name Description
7 CPA_MIX Classic for CPA systems
9 CLASSIC_T_CPA Temperature-dependent CPA
10 CLASSIC_TX_CPA Recommended T and x dependent for CPA

Mixing Rule 10 Auto-Selection:

Mixing rule 10 automatically selects the appropriate sub-type:

Condition Sub-Type Description
Symmetric $k_{ij}$, no T-dependency classic-CPA Simple symmetric
Symmetric $k_{ij}$, with T-dependency classic-CPA_T Temperature-dependent
Asymmetric $k_{ij}$ ($k_{ij} \neq k_{ji}$) classic-CPA_Tx Full asymmetric

8.6 Søreide-Whitson Mixing Rule

Type Name Description
11 SOREIDE_WHITSON Salinity-dependent for sour gas/brine

8.7 Setting Mixing Rules

// By integer value
fluid.setMixingRule(2);

// By name
fluid.setMixingRule("classic");
fluid.setMixingRule("HV");
fluid.setMixingRule("WS");

// By enum
fluid.setMixingRule(EosMixingRuleType.CLASSIC);

// With GE model (for HV/WS)
fluid.setMixingRule("HV", "UNIFAC_UMRPRU");
fluid.setMixingRule("WS", "NRTL");

8.8 Setting Custom kij Values

EosMixingRulesInterface mixRule = fluid.getPhase(0).getMixingRule();

// Set symmetric kij
mixRule.setBinaryInteractionParameter(0, 1, 0.12);

// Set asymmetric kij (for CPA)
mixRule.setBinaryInteractionParameterij(0, 1, 0.08);  // kij
mixRule.setBinaryInteractionParameterji(0, 1, 0.12);  // kji

// Set temperature-dependent kij
mixRule.setBinaryInteractionParameter(0, 1, 0.10);      // kij0
mixRule.setBinaryInteractionParameterT1(0, 1, 0.001);   // kijT

9. Auto-Select Model Feature

9.1 Overview

NeqSim provides an autoSelectModel() method that automatically chooses an appropriate thermodynamic model based on the components in your fluid. This is useful for quick setups or when you're unsure which model to use.

9.2 Auto-Selection Logic

The autoSelectModel() method follows this decision tree:

public SystemInterface autoSelectModel() {
    if (hasComponent("MDEA") && hasComponent("water") && hasComponent("CO2")) {
        return setModel("Electrolyte-ScRK-EOS");  // Amine systems
    } 
    else if (hasComponent("water") || hasComponent("methanol") || 
             hasComponent("MEG") || hasComponent("TEG") || 
             hasComponent("ethanol") || hasComponent("DEG")) {
        if (hasComponent("Na+") || hasComponent("K+") || 
            hasComponent("Br-") || hasComponent("Mg++") || 
            hasComponent("Cl-") || hasComponent("Ca++") || 
            hasComponent("Fe++") || hasComponent("SO4--")) {
            return setModel("Electrolyte-CPA-EOS-statoil");  // Electrolytes
        } else {
            return setModel("CPAs-SRK-EOS-statoil");  // Polar/associating
        }
    }
    else if (hasComponent("water")) {
        return setModel("ScRK-EOS");  // Water present
    }
    else if (hasComponent("mercury")) {
        return setModel("SRK-TwuCoon-Statoil-EOS");  // Mercury
    }
    else {
        return setModel("SRK-EOS");  // Default: standard SRK
    }
}

9.3 Model Selection Summary

Components Present Selected Model
MDEA + water + CO2 Electrolyte-ScRK-EOS
Water/glycols + ions Electrolyte-CPA-EOS-statoil
Water/glycols (no ions) CPAs-SRK-EOS-statoil
Only water (no glycols) ScRK-EOS
Mercury SRK-TwuCoon-Statoil-EOS
Default (hydrocarbons only) SRK-EOS

9.4 Usage

import neqsim.thermo.Fluid;

// Method 1: Using Fluid builder with autoSelectModel
Fluid fluidBuilder = new Fluid();
fluidBuilder.setAutoSelectModel(true);
SystemInterface fluid = fluidBuilder.create("black oil with water");

// Method 2: Calling autoSelectModel() on existing fluid
SystemInterface gas = new SystemSrkEos(300.0, 50.0);
gas.addComponent("methane", 0.80);
gas.addComponent("water", 0.15);
gas.addComponent("MEG", 0.05);
gas.createDatabase(true);

// Auto-select will switch to CPA model
SystemInterface optimizedFluid = gas.autoSelectModel();

9.5 Auto-Select Mixing Rule

There is also an autoSelectMixingRule() method that selects an appropriate mixing rule based on the model type:

fluid.autoSelectMixingRule();  // Automatically sets appropriate mixing rule

10. Model Selection Guidelines

10.1 Quick Reference Table

System Type Recommended Model Mixing Rule Notes
Dry natural gas SystemSrkEos classic (2) Simple, fast
Wet gas / condensate SystemPrEos classic (2) Better for C7+
Black oil with TBP SystemPrEos classic (2) With characterization
Water-hydrocarbon SystemSrkCPAstatoil CLASSIC_TX_CPA (10) Handles association
Glycol dehydration SystemSrkCPAstatoil CPA_MIX (7) or (10) MEG, TEG, DEG
Sour gas with brine SystemSoreideWhitson SOREIDE_WHITSON (11) Salinity-dependent
Amine gas treating SystemSrkEos HV (4) With NRTL
Fiscal metering SystemGERG2008Eos N/A ISO 20765-2
CCS / CO2 transport SystemEOSCGEos N/A Flue gas components
Electrolyte solutions SystemElectrolyteCPAstatoil (10) With chemicalReactionInit
Polar organics SystemUNIFAC N/A Group contribution
High-pressure polar SystemPrEos WS (5) Wong-Sandler

10.2 Decision Flow

1. Is high accuracy required for custody transfer?
   └─ YES → Use GERG-2008 or EOS-CG

2. Does the system contain electrolytes (Na+, Cl-, etc.)?
   └─ YES → Use Electrolyte-CPA

3. Does the system contain water, glycols, or alcohols?
   └─ YES → Use CPA models (SystemSrkCPAstatoil)

4. Is it a sour gas system with brine?
   └─ YES → Use Søreide-Whitson

5. Is it a non-ideal organic mixture?
   └─ YES → Use UNIFAC or NRTL with HV/WS mixing

6. Is it a standard hydrocarbon system?
   └─ YES → Use SRK or PR with classic mixing

10.3 Performance vs. Accuracy Trade-offs

Model Type Speed Accuracy Memory
Cubic (SRK/PR) ⚡⚡⚡ Fast ★★★ Good Low
CPA ⚡⚡ Medium ★★★★ Very Good Medium
GERG-2008 ⚡ Slow ★★★★★ Excellent High
UNIFAC ⚡⚡ Medium ★★★ Good Medium
Electrolyte-CPA ⚡ Slow ★★★★ Very Good High

11. Complete Reference Tables

11.1 All System Classes

Class Category Description
SystemSrkEos Cubic Standard SRK
SystemSrkPenelouxEos Cubic SRK with volume correction
SystemSrkMathiasCopeman Cubic SRK with MC alpha function
SystemSrkTwuCoonEos Cubic SRK with Twu-Coon alpha
SystemSrkTwuCoonParamEos Cubic SRK Twu-Coon parameterized
SystemSrkTwuCoonStatoilEos Cubic SRK Twu-Coon Equinor version
SystemSrkSchwartzentruberEos Cubic SRK-Schwartzentruber
SystemSrkEosvolcor Cubic SRK with volume correction
SystemPrEos Cubic Standard PR
SystemPrEos1978 Cubic Original 1978 PR
SystemPrMathiasCopeman Cubic PR with MC alpha
SystemPrDanesh Cubic Modified PR
SystemPrEosvolcor Cubic PR with volume correction
SystemPrEosDelft1998 Cubic Delft 1998 PR
SystemPrGassemEos Cubic Gassem PR
SystemRKEos Cubic Original RK
SystemTSTEos Cubic Twu-Sim-Tassone
SystemBWRSEos Cubic Benedict-Webb-Rubin-Starling
SystemCSPsrkEos Cubic CSP-SRK
SystemPsrkEos Cubic PSRK
SystemSrkCPA CPA Standard SRK-CPA
SystemSrkCPAs CPA Alternative SRK-CPA
SystemSrkCPAstatoil CPA Recommended Equinor SRK-CPA
SystemPrCPA CPA PR-CPA
SystemUMRCPAEoS CPA UMR-CPA
SystemPCSAFT SAFT PC-SAFT
SystemPCSAFTa SAFT PC-SAFT variant
SystemGERG2008Eos Reference GERG-2008 (ISO 20765-2)
SystemGERG2004Eos Reference GERG-2004 (older version)
SystemGERGwaterEos Reference GERG with water
SystemEOSCGEos Reference EOS-CG for CCS
SystemSpanWagnerEos Reference Span-Wagner for CO2
SystemLeachmanEos Reference Leachman for H2
SystemVegaEos Reference Vega equation
SystemAmmoniaEos Reference Ammonia-specific
SystemBnsEos Reference BNS equation
SystemUNIFAC GE UNIFAC
SystemUNIFACpsrk GE UNIFAC-PSRK
SystemNRTL GE NRTL
SystemGEWilson GE Wilson
SystemUMRPRUEos GE-EoS UMR-PRU
SystemUMRPRUMCEos GE-EoS UMR-PRU-MC
SystemElectrolyteCPA Electrolyte Electrolyte CPA
SystemElectrolyteCPAstatoil Electrolyte Recommended Equinor Electrolyte CPA
SystemElectrolyteCPAMM Electrolyte Electrolyte CPA-MM
SystemFurstElectrolyteEos Electrolyte Fürst electrolyte
SystemFurstElectrolyteEosMod2004 Electrolyte Modified Fürst (2004)
SystemSoreideWhitson Electrolyte Søreide-Whitson for sour gas
SystemPitzer Electrolyte Pitzer model
SystemDesmukhMather Electrolyte Desmukh-Mather
SystemKentEisenberg Electrolyte Kent-Eisenberg
SystemDuanSun Electrolyte Duan-Sun
SystemIdealGas Ideal Ideal gas law
SystemWaterIF97 Water IF-97 for steam

11.2 All Mixing Rules

Type Name String Key Best For
1 NO "NO" Ideal mixtures
2 CLASSIC "classic" General hydrocarbons
3 CLASSIC_HV "CLASSIC_HV" Polar mixtures
4 HV "HV" Alcohol-water-HC
5 WS "WS" High-pressure polar
7 CPA_MIX "CPA_MIX" CPA systems
8 CLASSIC_T "CLASSIC_T" T-dependent kij
9 CLASSIC_T_CPA "CLASSIC_T_CPA" CPA with T-dependency
10 CLASSIC_TX_CPA "CLASSIC_TX_CPA" Recommended for CPA
11 SOREIDE_WHITSON "SOREIDE_WHITSON" Sour gas, brine
12 CLASSIC_T2 "CLASSIC_T2" Inverse T-dependency

See Also


Last updated: January 2026

GERG-2008

GERG-2008 and EOS-CG Equations of State

NeqSim supports the GERG-2008 and EOS-CG equations of state, which are reference-quality models explicit in the Helmholtz free energy. These models are widely used for high-accuracy property calculations in natural gas and CCS (Carbon Capture and Storage) applications.

1. Mathematical Framework

Both GERG-2008 and EOS-CG share the same fundamental mathematical structure. They are fundamental equations of state explicit in the dimensionless Helmholtz free energy $\alpha$.

The dimensionless Helmholtz energy $\alpha$ is separated into an ideal gas part $\alpha^0$ and a residual part $\alpha^r$:

$$ \alpha(\delta, \tau, \bar{x}) = \frac{a(\rho, T, \bar{x})}{RT} = \alpha^0(\delta, \tau, \bar{x}) + \alpha^r(\delta, \tau, \bar{x}) $$

Where:

Ideal Gas Contribution ($\alpha^0$)

The ideal gas part is determined from the ideal gas heat capacity of the mixture components:

$$ \alpha^0(\delta, \tau, \bar{x}) = \sum_{i=1}^{N} x_i \left[ \alpha_{0i}^0(\delta, \tau) + \ln x_i \right] $$

Residual Contribution ($\alpha^r$)

The residual part accounts for intermolecular forces and real fluid behavior. It is typically expressed as a sum of polynomial and exponential terms fitted to high-accuracy experimental data:

$$ \alpha^r(\delta, \tau, \bar{x}) = \sum_{i=1}^{N} x_i \alpha_{0i}^r(\delta, \tau) + \sum_{i=1}^{N-1} \sum_{j=i+1}^{N} x_i x_j F_{ij} \alpha_{ij}^r(\delta, \tau) $$

This structure allows for extremely high accuracy in density, speed of sound, and heat capacity calculations, often superior to cubic equations of state (like SRK or PR), especially in the supercritical region.


2. GERG-2008

Full Name: GERG-2008 Wide-Range Equation of State for Natural Gases and Other Mixtures.
Authors: O. Kunz and W. Wagner (Ruhr-Universität Bochum).
Standard: ISO 20765-2.

Application

GERG-2008 is the standard reference equation for natural gas transport, processing, and custody transfer. It covers 21 components typical of natural gas.

Supported Components (21)

Methane, Nitrogen, Carbon Dioxide, Ethane, Propane, Butanes, Pentanes, Hexane, Heptane, Octane, Nonane, Decane, Hydrogen, Oxygen, Carbon Monoxide, Water, Helium, Argon.

Usage in NeqSim

To use GERG-2008 in NeqSim, use the SystemGERG2008Eos class.

import neqsim.thermo.system.SystemGERG2008Eos;
import neqsim.thermo.system.SystemInterface;

public class GergExample {
    public static void main(String[] args) {
        // Create system
        SystemInterface fluid = new SystemGERG2008Eos(298.15, 10.0); // T in K, P in bara

        // Add components
        fluid.addComponent("methane", 0.9);
        fluid.addComponent("ethane", 0.1);

        // Initialize
        fluid.createDatabase(true);
        fluid.setMixingRule("classic"); // Not strictly used by GERG but good practice for init

        // Flash calculation
        neqsim.thermodynamicoperations.ThermodynamicOperations ops = 
            new neqsim.thermodynamicoperations.ThermodynamicOperations(fluid);
        ops.TPflash();

        // Retrieve properties
        // Note: GERG-2008 properties are often accessed via specific methods
        double density = fluid.getPhase(0).getDensity_GERG2008();
        double[] props = fluid.getPhase(0).getProperties_GERG2008();

        System.out.println("Density (GERG): " + density + " kg/m3");
    }
}

3. GERG-2008-H2 (Hydrogen Enhanced)

Full Name: Extension of the equation of state for natural gases GERG-2008 with improved hydrogen parameters.
Authors: R. Beckmüller, M. Thol, I. Sampson, E.W. Lemmon, R. Span (Ruhr-Universität Bochum, NIST).

Application

GERG-2008-H2 is an extension of GERG-2008 with improved hydrogen binary interaction parameters. This extension is particularly important for:

Key Improvements

The GERG-2008-H2 model includes:

Expected Differences from GERG-2008

Binary System Typical Density Difference
CH₄-H₂ ~0.1-0.25%
N₂-H₂ ~0.05-0.5%
CO₂-H₂ ~1-1.5% (largest)
C₂H₆-H₂ ~0.5-0.8%

Differences increase with:

Usage in NeqSim

The GERG-2008-H2 model is available through SystemGERG2008Eos by enabling the hydrogen-enhanced mode:

import neqsim.thermo.system.SystemGERG2008Eos;
import neqsim.thermo.util.gerg.GERG2008Type;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class Gerg2008H2Example {
    public static void main(String[] args) {
        // Create system with GERG-2008
        SystemGERG2008Eos fluid = new SystemGERG2008Eos(300.0, 50.0); // T in K, P in bara

        // Add hydrogen-rich mixture
        fluid.addComponent("methane", 0.7);
        fluid.addComponent("hydrogen", 0.3);

        // Enable GERG-2008-H2 model with improved hydrogen parameters
        fluid.useHydrogenEnhancedModel();
        // or equivalently:
        // fluid.setGergModelType(GERG2008Type.HYDROGEN_ENHANCED);

        // Flash calculation
        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        ops.TPflash();

        // Retrieve properties
        double density = fluid.getPhase(0).getDensity();
        System.out.println("Density (GERG-2008-H2): " + density + " kg/m3");
        System.out.println("Model: " + fluid.getModelName()); // "GERG2008-H2-EOS"

        // Check which model is active
        if (fluid.isUsingHydrogenEnhancedModel()) {
            System.out.println("Using hydrogen-enhanced GERG-2008-H2 model");
        }
    }
}

API Methods

Method Description
useHydrogenEnhancedModel() Enable GERG-2008-H2 model
setGergModelType(GERG2008Type.STANDARD) Use standard GERG-2008
setGergModelType(GERG2008Type.HYDROGEN_ENHANCED) Use GERG-2008-H2
getGergModelType() Get current model type
isUsingHydrogenEnhancedModel() Check if H2 model is active

4. EOS-CG

Full Name: EOS-CG: A Helmholtz energy equation of state for combustion gases and CCS mixtures.
Authors: J. Gernert and R. Span (Ruhr-Universität Bochum).

Application

EOS-CG is an extension of the GERG framework designed for Carbon Capture and Storage (CCS) and combustion gas applications. It includes additional components found in flue gases and impurities relevant to CO2 transport.

Supported Components (27)

Includes all 21 components from GERG-2008, plus:

Recent PRs refreshed the EOS-CG component tables with updated critical properties and binary interaction data, improving phase behavior for acid-gas heavy blends. The refresh aligns the library with the latest GERG-compatible datasets so CCS mixtures match reference densities and sound speed benchmarks more closely.

Usage in NeqSim

To use EOS-CG in NeqSim, use the SystemEOSCGEos class.

import neqsim.thermo.system.SystemEOSCGEos;
import neqsim.thermo.system.SystemInterface;

public class EosCgExample {
    public static void main(String[] args) {
        // Create system
        SystemInterface fluid = new SystemEOSCGEos(298.15, 50.0);

        // Add components (including CCS impurities)
        fluid.addComponent("CO2", 0.95);
        fluid.addComponent("SO2", 0.05);

        // Initialize and Flash
        fluid.createDatabase(true);
        neqsim.thermodynamicoperations.ThermodynamicOperations ops = 
            new neqsim.thermodynamicoperations.ThermodynamicOperations(fluid);
        ops.TPflash();

        // Retrieve properties
        double density = fluid.getPhase(0).getDensity_EOSCG();

        System.out.println("Density (EOS-CG): " + density + " kg/m3");
    }
}

5. Literature References

  1. GERG-2008: Kunz, O., & Wagner, W. (2012). The GERG-2008 Wide-Range Equation of State for Natural Gases and Other Mixtures: An Expansion of GERG-2004. Journal of Chemical & Engineering Data, 57(11), 3032–3091.
  2. GERG-2008-H2: Beckmüller, R., Thol, M., Sampson, I., Lemmon, E.W., & Span, R. (2022). Extension of the equation of state for natural gases GERG-2008 with improved hydrogen parameters. Fluid Phase Equilibria, 557, 113411.
  3. EOS-CG: Gernert, J., & Span, R. (2016). EOS-CG: A Helmholtz energy equation of state for combustion gases and CCS mixtures. The Journal of Chemical Thermodynamics, 93, 274–293.

Mixing Rules

Mixing Rules in NeqSim

This guide provides comprehensive documentation on mixing rules available in NeqSim, including mathematical formulations, usage patterns, and recommendations for different applications.

Table of Contents

  1. Introduction to Mixing Rules
  2. Setting Mixing Rules
  3. Classic Mixing Rules
  4. Huron-Vidal Mixing Rules
  5. Wong-Sandler Mixing Rule
  6. CPA Mixing Rules
  7. Søreide-Whitson Mixing Rule
  8. Binary Interaction Parameters (kij)
  9. Complete Reference Table
  10. Examples by Application

1. Introduction to Mixing Rules

Mixing rules determine how pure-component parameters from an equation of state are combined to calculate mixture properties. For cubic equations of state like SRK and PR, the key parameters are:

The general form of cubic EoS mixing rules is:

$$ a_{mix} = \sum_i \sum_j x_i x_j a_{ij} $$

$$ b_{mix} = \sum_i x_i b_i $$

Where the cross-parameter $a_{ij}$ is typically calculated using a combining rule:

$$ a_{ij} = \sqrt{a_i a_j}(1 - k_{ij}) $$

The binary interaction parameter $k_{ij}$ is crucial for accurate phase equilibrium predictions.


2. Setting Mixing Rules

NeqSim provides three ways to set mixing rules:

2.1 By Integer Value

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.8);
fluid.addComponent("CO2", 0.2);
fluid.setMixingRule(2);  // Classic mixing rule

2.2 By Name (String)

fluid.setMixingRule("classic");
fluid.setMixingRule("HV");
fluid.setMixingRule("WS");

2.3 By Enum Type

import neqsim.thermo.mixingrule.EosMixingRuleType;

fluid.setMixingRule(EosMixingRuleType.CLASSIC);
fluid.setMixingRule(EosMixingRuleType.byName("classic"));
fluid.setMixingRule(EosMixingRuleType.byValue(2));

2.4 With GE Model (for Huron-Vidal/Wong-Sandler)

// Huron-Vidal with UNIFAC
fluid.setMixingRule("HV", "UNIFAC_UMRPRU");

// Wong-Sandler with NRTL
fluid.setMixingRule("WS", "NRTL");

3. Classic Mixing Rules

3.1 NO (kij = 0) - Type 1

The simplest mixing rule with no binary interactions. All $k_{ij}$ values are set to zero.

$$ a_{ij} = \sqrt{a_i a_j} $$

Use case: Quick calculations, ideal mixture approximations, or when no interaction parameters are available.

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.7);
fluid.addComponent("ethane", 0.3);
fluid.setMixingRule(1);  // or fluid.setMixingRule("NO");

3.2 CLASSIC - Type 2

The standard van der Waals one-fluid mixing rule with binary interaction parameters from the NeqSim database.

$$ a_{mix} = \sum_i \sum_j x_i x_j \sqrt{a_i a_j}(1 - k_{ij}) $$

$$ b_{mix} = \sum_i x_i b_i $$

Use case: General hydrocarbon systems, natural gas processing, most industrial applications.

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("CO2", 0.10);
fluid.addComponent("nitrogen", 0.05);
fluid.setMixingRule(2);  // or fluid.setMixingRule("classic");

3.3 CLASSIC_T - Type 8

Classic mixing rule with temperature-dependent binary interaction parameters:

$$ k_{ij}(T) = k_{ij,0} + k_{ij,T} \cdot T $$

Where $k_{ij,0}$ is the reference value and $k_{ij,T}$ is the temperature coefficient.

Use case: Systems where kij varies significantly with temperature.

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.8);
fluid.addComponent("H2S", 0.2);
fluid.setMixingRule(8);  // Temperature-dependent classic

3.4 CLASSIC_T2 - Type 12

Alternative temperature-dependent formulation:

$$ k_{ij}(T) = k_{ij,0} + \frac{k_{ij,T}}{T} $$

Use case: Systems requiring inverse temperature dependency.

fluid.setMixingRule(12);

4. Huron-Vidal Mixing Rules

Huron-Vidal (HV) mixing rules combine cubic EoS with activity coefficient models (like NRTL or UNIFAC) for improved liquid-phase behavior.

4.1 Mathematical Framework

The excess Gibbs energy from an activity coefficient model is incorporated into the EoS:

$$ a_{mix} = b_{mix} \left( \sum_i x_i \frac{a_i}{b_i} - \frac{G^E}{\Lambda} \right) $$

Where:

4.2 CLASSIC_HV - Type 3

Basic Huron-Vidal mixing rule with NRTL parameters from the database.

SystemInterface fluid = new SystemSrkEos(350.0, 10.0);
fluid.addComponent("methanol", 0.3);
fluid.addComponent("water", 0.4);
fluid.addComponent("methane", 0.3);
fluid.setMixingRule(3);  // or fluid.setMixingRule("CLASSIC_HV");

4.3 HV - Type 4

Enhanced Huron-Vidal with temperature-dependent parameters (HVDijT):

$$ D_{ij}(T) = D_{ij,0} + D_{ij,T} \cdot T $$

Use case: Polar/non-polar mixtures, alcohol-water-hydrocarbon systems.

SystemInterface fluid = new SystemPrEos(320.0, 20.0);
fluid.addComponent("ethanol", 0.2);
fluid.addComponent("water", 0.5);
fluid.addComponent("propane", 0.3);
fluid.setMixingRule(4);  // or fluid.setMixingRule("HV");

4.4 Huron-Vidal with UNIFAC

For predictive calculations without fitted parameters, use UNIFAC:

SystemInterface fluid = new SystemSrkEos(320.0, 15.0);
fluid.addComponent("ethanol", 0.3);
fluid.addComponent("n-hexane", 0.4);
fluid.addComponent("water", 0.3);

// HV with UNIFAC activity coefficients
fluid.setMixingRule("HV", "UNIFAC_UMRPRU");

Available GE models for HV:

4.5 Accessing HV Parameters

// Get the mixing rule interface
HVMixingRulesInterface hvRule = (HVMixingRulesInterface) fluid.getPhase(0).getMixingRule();

// Get/Set HV parameters
double dij = hvRule.getHVDijParameter(0, 1);
hvRule.setHVDijParameter(0, 1, 500.0);  // Set Dij in K

double alpha = hvRule.getHValphaParameter(0, 1);
hvRule.setHValphaParameter(0, 1, 0.3);  // Set alpha (non-randomness)

5. Wong-Sandler Mixing Rule

5.1 Overview

The Wong-Sandler (WS) mixing rule provides theoretically correct behavior at both low and high densities by matching:

5.2 Mathematical Formulation

$$ b_{mix} = \frac{\sum_i \sum_j x_i x_j \left( b - \frac{a}{RT} \right)_{ij}}{1 - \frac{A_\infty^E}{CRT} - \sum_i x_i \frac{a_i}{RTb_i}} $$

$$ a_{mix} = b_{mix} \left( \sum_i x_i \frac{a_i}{b_i} + \frac{A_\infty^E}{C} \right) $$

Where $C$ is an EoS-specific constant and $A_\infty^E$ is the excess Helmholtz energy at infinite pressure.

5.3 WS - Type 5

SystemInterface fluid = new SystemPrEos(350.0, 30.0);
fluid.addComponent("methanol", 0.2);
fluid.addComponent("water", 0.5);
fluid.addComponent("CO2", 0.3);
fluid.setMixingRule(5);  // or fluid.setMixingRule("WS");

5.4 Wong-Sandler with NRTL

SystemInterface fluid = new SystemSrkEos(320.0, 20.0);
fluid.addComponent("acetone", 0.3);
fluid.addComponent("water", 0.4);
fluid.addComponent("methane", 0.3);

// WS with NRTL activity coefficients
fluid.setMixingRule("WS", "NRTL");

5.5 Accessing WS Parameters

HVMixingRulesInterface wsRule = (HVMixingRulesInterface) fluid.getPhase(0).getMixingRule();

// Get/Set Wong-Sandler kij parameter
double kijWS = wsRule.getKijWongSandler(0, 1);
wsRule.setKijWongSandler(0, 1, 0.1);

Use case: High-pressure VLE, polar/non-polar mixtures, CO2 capture systems.


6. CPA Mixing Rules

For Cubic-Plus-Association (CPA) equations of state, specialized mixing rules handle both the cubic EoS part and the association term.

6.1 CPA_MIX - Type 7

Classic mixing rule with CPA-specific binary interaction parameters from the database.

SystemInterface fluid = new SystemSrkCPAstatoil(320.0, 50.0);
fluid.addComponent("water", 0.1);
fluid.addComponent("methane", 0.8);
fluid.addComponent("MEG", 0.1);
fluid.setMixingRule(7);  // CPA classic mixing

6.2 CLASSIC_T_CPA - Type 9

Temperature-dependent classic mixing rule for CPA systems:

fluid.setMixingRule(9);

6.3 CLASSIC_TX_CPA - Type 10

Temperature and composition dependent mixing rule for CPA. This is the recommended mixing rule for CPA systems as it:

SystemInterface fluid = new SystemSrkCPAstatoil(330.0, 100.0);
fluid.addComponent("water", 0.15);
fluid.addComponent("methane", 0.70);
fluid.addComponent("MEG", 0.10);
fluid.addComponent("CO2", 0.05);
fluid.setMixingRule(10);  // Recommended for CPA

6.4 Cross-Association Parameters

CPA mixing rules also handle association site interactions between different molecules:

Association Scheme Description Examples
CR-1 Cross-association with single site Water-MEG
ER Elliott combining rule General polar systems
4C Four-site model Water, glycols
2B Two-site model Alcohols

7. Søreide-Whitson Mixing Rule

7.1 Overview

The Søreide-Whitson mixing rule is specifically designed for systems containing:

It uses composition and salinity-dependent binary interaction parameters.

7.2 SOREIDE_WHITSON - Type 11

import neqsim.thermo.system.SystemSoreideWhitson;

SystemSoreideWhitson fluid = new SystemSoreideWhitson(350.0, 200.0);
fluid.addComponent("methane", 0.70);
fluid.addComponent("CO2", 0.15);
fluid.addComponent("H2S", 0.05);
fluid.addComponent("water", 0.10);

// Add salinity (important for sour gas solubility)
fluid.addSalinity(2.0, "mole/sec");

fluid.setMixingRule(11);  // Søreide-Whitson mixing rule

7.3 Salinity-Dependent kij

The mixing rule calculates kij for water-gas interactions based on salinity (S in mol/kg):

For CO2-water: $$ k_{ij} = -0.31092(1 + 0.156 S^{0.75}) + 0.236(1 + 0.178 S^{0.98}) T_r - 21.26 e^{-6.72^{T_r} - S} $$

For N2-water: $$ k_{ij} = -1.702(1 + 0.026 S^{0.75}) + 0.443(1 + 0.081 S^{0.75}) T_r $$

For hydrocarbons-water: $$ k_{ij} = (1 + a_0 S) A_0 + (1 + a_1 S) A_1 T_r + (1 + a_2 S) A_2 T_r^2 $$

Where $T_r$ is the reduced temperature and the $A$ and $a$ coefficients depend on the acentric factor.

Use case: Reservoir fluids with aqueous phases, sour gas processing, CCS with brine.


8. Binary Interaction Parameters (kij)

8.1 Default Database Values

NeqSim loads kij values from its internal database when you call setMixingRule(). These are stored in the inter table.

8.2 Setting Custom kij Values

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("CO2", 0.1);
fluid.setMixingRule("classic");

// Get the mixing rule interface
EosMixingRulesInterface mixRule = fluid.getPhase(0).getMixingRule();

// Set custom kij (symmetric: kij = kji)
mixRule.setBinaryInteractionParameter(0, 1, 0.12);

// Get current kij value
double kij = mixRule.getBinaryInteractionParameter(0, 1);
System.out.println("kij(CH4-CO2) = " + kij);

8.3 Setting Asymmetric kij (for CPA)

// For CPA systems with asymmetric parameters
mixRule.setBinaryInteractionParameterij(0, 1, 0.08);  // kij
mixRule.setBinaryInteractionParameterji(0, 1, 0.12);  // kji

8.4 Setting Temperature-Dependent kij

// For mixing rules 8, 9, 12
// kij(T) = kij0 + kijT * f(T)
mixRule.setBinaryInteractionParameter(0, 1, 0.10);    // kij0
mixRule.setBinaryInteractionParameterT1(0, 1, 0.001); // kijT

8.5 Displaying kij Matrix

double[][] kijMatrix = mixRule.getBinaryInteractionParameters();
for (int i = 0; i < fluid.getNumberOfComponents(); i++) {
    for (int j = 0; j < fluid.getNumberOfComponents(); j++) {
        System.out.printf("kij[%d][%d] = %.4f  ", i, j, kijMatrix[i][j]);
    }
    System.out.println();
}

9. Complete Reference Table

Type Name String Key Description Best For
1 NO "NO" All kij = 0 Quick estimates, ideal mixtures
2 CLASSIC "classic" Classic van der Waals with database kij General hydrocarbons
3 CLASSIC_HV "CLASSIC_HV" Huron-Vidal with database parameters Polar mixtures
4 HV "HV" Huron-Vidal with T-dependent parameters Alcohol-water-HC
5 WS "WS" Wong-Sandler High-pressure polar systems
7 CPA_MIX "CPA_MIX" Classic for CPA systems Water-HC with CPA
8 CLASSIC_T "CLASSIC_T" T-dependent kij (linear) Temperature-sensitive kij
9 CLASSIC_T_CPA "CLASSIC_T_CPA" T-dependent for CPA CPA with T-dependency
10 CLASSIC_TX_CPA "CLASSIC_TX_CPA" T and x dependent for CPA Recommended for CPA
11 SOREIDE_WHITSON "SOREIDE_WHITSON" Salinity-dependent Sour gas, brine systems
12 CLASSIC_T2 "CLASSIC_T2" T-dependent (inverse) Alternative T-dependency

10. Examples by Application

10.1 Natural Gas Processing

SystemInterface gas = new SystemSrkEos(280.0, 70.0);
gas.addComponent("nitrogen", 0.02);
gas.addComponent("CO2", 0.03);
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.06);
gas.addComponent("propane", 0.04);
gas.setMixingRule("classic");  // Type 2

ThermodynamicOperations ops = new ThermodynamicOperations(gas);
ops.TPflash();

10.2 Glycol Dehydration (CPA)

SystemInterface fluid = new SystemSrkCPAstatoil(320.0, 50.0);
fluid.addComponent("methane", 0.80);
fluid.addComponent("water", 0.05);
fluid.addComponent("TEG", 0.15);  // Triethylene glycol
fluid.setMixingRule(10);  // CLASSIC_TX_CPA - recommended for CPA

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

10.3 Amine Gas Treating

SystemInterface fluid = new SystemSrkEos(323.0, 20.0);
fluid.addComponent("CO2", 0.15);
fluid.addComponent("H2S", 0.05);
fluid.addComponent("methane", 0.60);
fluid.addComponent("MDEA", 0.10);  // Methyldiethanolamine
fluid.addComponent("water", 0.10);
fluid.setMixingRule("HV", "NRTL");  // Huron-Vidal with NRTL

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

10.4 CO2 Capture and Storage

SystemInterface fluid = new SystemPrEos(350.0, 150.0);
fluid.addComponent("CO2", 0.90);
fluid.addComponent("nitrogen", 0.05);
fluid.addComponent("oxygen", 0.02);
fluid.addComponent("water", 0.03);
fluid.setMixingRule("WS");  // Wong-Sandler for high pressure

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

10.5 Sour Gas with Brine

SystemSoreideWhitson fluid = new SystemSoreideWhitson(380.0, 250.0);
fluid.addComponent("methane", 0.65);
fluid.addComponent("CO2", 0.20);
fluid.addComponent("H2S", 0.05);
fluid.addComponent("water", 0.10);
fluid.addSalinity("NaCl", 3.0, "mole/sec");  // Add NaCl salinity
fluid.setMixingRule(11);  // Søreide-Whitson

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

System.out.println("Number of phases: " + fluid.getNumberOfPhases());
fluid.prettyPrint();

10.6 Pharmaceutical/Chemical (UNIFAC Predictive)

SystemInterface fluid = new SystemSrkEos(298.0, 1.0);
fluid.addComponent("ethanol", 0.3);
fluid.addComponent("acetone", 0.3);
fluid.addComponent("water", 0.4);
fluid.setMixingRule("HV", "UNIFAC_UMRPRU");  // Predictive UNIFAC

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

See Also

Mixing Rule Package

Mixing Rules Package

Documentation for mixing rules in NeqSim equations of state.

Table of Contents


Overview

Location: neqsim.thermo.mixingrule

Mixing rules define how pure component EoS parameters combine in mixtures.


Classic Mixing Rules

Van der Waals One-Fluid Rules

$$a_m = \sum_i \sum_j x_i x_j \sqrt{a_i a_j}(1 - k_{ij})$$

$$b_m = \sum_i x_i b_i$$

Usage

// Classic mixing rule
fluid.setMixingRule("classic");
// or
fluid.setMixingRule(2);

Asymmetric kij

// Set binary interaction parameter
fluid.getInterphaseProperties().setParameter("kij", "CO2", "methane", 0.12);

CPA Mixing Rules

For associating systems (water, alcohols, amines).

Usage

// CPA-specific mixing rule
SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(T, P);
fluid.setMixingRule(10);

Cross-Association

Cross-association between different associating molecules is handled via combining rules:

$$\epsilon^{A_iB_j} = \frac{\epsilon^{A_i} + \epsilon^{B_j}}{2}$$

$$\beta^{A_iB_j} = \sqrt{\beta^{A_i}\beta^{B_j}}$$


Activity Coefficient Mixing Rules

Huron-Vidal

// SRK with Huron-Vidal mixing
SystemSrkHuronVidal fluid = new SystemSrkHuronVidal(T, P);
fluid.setMixingRule("HV");

Wong-Sandler

// Wong-Sandler mixing rule
fluid.setMixingRule("WS");

Schwarzentruber-Renon

Combines EoS with UNIFAC.

SystemSrkSchwartzentruberRenon fluid = new SystemSrkSchwartzentruberRenon(T, P);

Binary Interaction Parameters

Accessing kij

double kij = fluid.getInterphaseProperties().getParameter("kij", "CO2", "methane");

Setting kij

fluid.getInterphaseProperties().setParameter("kij", "CO2", "methane", 0.12);

Temperature-Dependent kij

$$k_{ij}(T) = k_{ij}^0 + k_{ij}^1 \cdot T + k_{ij}^2 \cdot T^2$$


Mixing Rule Numbers

Number Mixing Rule
1 No mixing
2 Classic (Van der Waals)
4 Huron-Vidal
7 Wong-Sandler
9 Schwarzentruber-Renon
10 CPA

Phase Package

Phase Package

Documentation for phase modeling in NeqSim.

Table of Contents


Overview

Location: neqsim.thermo.phase

The phase package contains 62+ classes for modeling different phase types. Each phase type inherits from a base class that implements the PhaseInterface.


Phase Types

Available Phase Classes

Category Classes
Gas PhaseGas, PhaseGasEos, PhaseGasCPA, PhaseGasPCSAFT
Liquid PhaseLiquid, PhaseLiquidEos, PhaseLiquidCPA, PhaseLiquidPCSAFT
Aqueous PhaseAqueous, PhaseAqueousEos
Solid PhaseSolid, PhaseSolidComplex, PhaseHydrate, PhaseWax

Phase Hierarchy

PhaseInterface
└── Phase (abstract base)
    ├── PhaseEos (EoS phases)
    │   ├── PhaseGasEos
    │   ├── PhaseLiquidEos
    │   └── ...
    ├── PhaseCPA (CPA phases)
    │   ├── PhaseGasCPA
    │   ├── PhaseLiquidCPA
    │   └── ...
    └── PhaseSolid
        ├── PhaseHydrate
        └── PhaseWax

Phase Interface

Common Methods

PhaseInterface phase = fluid.getPhase(0);

// Phase fraction
double beta = phase.getBeta();  // Mole fraction of total
double betaV = phase.getBetaV(); // Volume fraction

// Thermodynamic properties
double T = phase.getTemperature();
double P = phase.getPressure();
double V = phase.getMolarVolume();
double rho = phase.getDensity("kg/m3");
double Z = phase.getZ();

// Energetic properties
double H = phase.getEnthalpy("kJ/kg");
double S = phase.getEntropy("kJ/kgK");
double G = phase.getGibbsEnergy();
double U = phase.getInternalEnergy();
double A = phase.getHelmholtzEnergy();

// Heat capacities
double Cp = phase.getCp("J/molK");
double Cv = phase.getCv("J/molK");

// Transport properties
double visc = phase.getViscosity("cP");
double k = phase.getThermalConductivity("W/mK");
double D = phase.getDiffusionCoefficient("m2/s");

// Speed of sound
double u = phase.getSoundSpeed("m/s");

Component Access

// Get component in phase
ComponentInterface comp = phase.getComponent("methane");

// Mole fraction in phase
double x = comp.getx();

// Fugacity
double f = comp.getFugacity();
double phi = comp.getFugacityCoefficient();

Gas Phase

Properties

PhaseInterface gas = fluid.getGasPhase();

// Compressibility
double Z = gas.getZ();

// Density
double rhoGas = gas.getDensity("kg/m3");

// Viscosity
double muGas = gas.getViscosity("cP");

// Specific volume
double Vm = gas.getMolarVolume();

Gas Phase Types

// Standard EoS gas phase
PhaseGasEos gasEos = (PhaseGasEos) fluid.getGasPhase();

// CPA gas phase (for associating components)
PhaseGasCPA gasCPA = (PhaseGasCPA) fluid.getGasPhase();

// PC-SAFT gas phase
PhaseGasPCSAFT gasSAFT = (PhaseGasPCSAFT) fluid.getGasPhase();

Liquid Phase

Oil Phase

PhaseInterface oil = fluid.getLiquidPhase();

// Liquid density
double rhoLiq = oil.getDensity("kg/m3");

// API gravity
double API = 141.5 / (oil.getDensity("g/cm3") / 0.999) - 131.5;

// Viscosity
double muOil = oil.getViscosity("cP");

Multiple Liquid Phases

// When system has multiple liquid phases
if (fluid.getNumberOfLiquidPhases() > 1) {
    PhaseInterface oil = fluid.getPhase("oil");
    PhaseInterface aqueous = fluid.getPhase("aqueous");
}

Aqueous Phase

For water-rich liquid phases.

PhaseInterface aqueous = fluid.getPhase("aqueous");

// Water activity
double aW = aqueous.getComponent("water").getActivity();

// pH (if electrolytes present)
double pH = aqueous.getpH();

// Ionic strength
double I = aqueous.getIonicStrength();

Solid Phases

General Solid

// Enable solid phase check
fluid.setSolidPhaseCheck(true);

// Get solid phase if formed
if (fluid.hasSolidPhase()) {
    PhaseInterface solid = fluid.getSolidPhase();
    double solidFraction = solid.getBeta();
}

Hydrate Phase

// Hydrate formation check
fluid.setHydrateCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

if (fluid.hasHydrate()) {
    PhaseInterface hydrate = fluid.getPhase("hydrate");
    double hydrateTemp = fluid.getHydrateTemperature();
}

Wax Phase

// Wax formation check
fluid.setWaxCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

if (fluid.hasWax()) {
    PhaseInterface wax = fluid.getPhase("wax");
    double waxFraction = wax.getBeta();
}

Asphaltene Phases

NeqSim supports two approaches for modeling asphaltene precipitation:

  1. Solid Asphaltene (PhaseType.ASPHALTENE): Traditional approach where precipitated asphaltene is treated as a solid phase with literature-based physical properties.

  2. Liquid Asphaltene (PhaseType.LIQUID_ASPHALTENE): Pedersen's approach where asphaltene is modeled as a liquid phase using cubic EOS (SRK/PR), enabling liquid-liquid equilibrium calculations.

Solid Asphaltene Approach

// Enable solid phase check for asphaltene
fluid.setSolidPhaseCheck("asphaltene");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Check for asphaltene phase using PhaseType
if (fluid.hasPhaseType(PhaseType.ASPHALTENE)) {
    PhaseInterface asphaltene = fluid.getPhaseOfType("asphaltene");
    double asphalteneFraction = asphaltene.getBeta();
    double density = asphaltene.getDensity("kg/m3");  // ~1150 kg/m³
    double viscosity = asphaltene.getViscosity("Pa*s"); // ~10,000 Pa·s
}

Pedersen's Liquid Asphaltene Approach

Pedersen's method treats asphaltene as a heavy liquid component using cubic EOS with estimated critical properties. This allows liquid-liquid equilibrium (LLE) calculations.

import neqsim.thermo.characterization.PedersenAsphalteneCharacterization;

// Create fluid system
SystemInterface fluid = new SystemSrkEos(373.15, 50.0);
fluid.addComponent("methane", 0.30);
fluid.addComponent("n-pentane", 0.25);  // Precipitant
fluid.addComponent("n-heptane", 0.20);
fluid.addComponent("nC10", 0.15);

// Create and configure asphaltene characterization
PedersenAsphalteneCharacterization asphChar = new PedersenAsphalteneCharacterization();
asphChar.setAsphalteneMW(750.0);     // g/mol
asphChar.setAsphalteneDensity(1.10); // g/cm³

// Add asphaltene as pseudo-component (before mixing rule)
asphChar.addAsphalteneToSystem(fluid, 0.10);  // 10 mol%
fluid.setMixingRule("classic");

// Perform TPflash with automatic asphaltene detection
boolean hasAsphaltene = PedersenAsphalteneCharacterization.TPflash(fluid);

// Check for asphaltene-rich liquid phase
if (fluid.hasPhaseType(PhaseType.LIQUID_ASPHALTENE)) {
    PhaseInterface asphLiquid = fluid.getPhaseOfType("asphaltene liquid");
    System.out.println("Asphaltene liquid fraction: " + asphLiquid.getBeta());
}

Detecting Asphaltene-Rich Phases

// Check if any phase is asphaltene-rich (works for both approaches)
for (int i = 0; i < fluid.getNumberOfPhases(); i++) {
    PhaseInterface phase = fluid.getPhase(i);
    if (phase.isAsphalteneRich()) {
        System.out.println("Phase " + i + " is asphaltene-rich");
    }
}

// Using StateOfMatter helper
import neqsim.thermo.phase.StateOfMatter;
boolean isAsph = StateOfMatter.isAsphaltene(phase.getType());

Asphaltene Phase Properties:

Property Solid Approach Liquid (Pedersen) Unit
Density ~1150 (literature) EOS-calculated kg/m³
Heat Capacity (Cp) ~0.9 (literature) EOS-calculated kJ/kgK
Thermal Conductivity ~0.20 (literature) EOS-calculated W/mK
Viscosity ~10,000 (literature) EOS-calculated Pa·s
Speed of Sound ~1745 (literature) EOS-calculated m/s

Phase Identification

Phase Type Detection

// Get phase type
String type = phase.getPhaseTypeName();  // "gas", "oil", "aqueous", "asphaltene", etc.

// Check phase type using enum
boolean isGas = phase.getType() == PhaseType.GAS;
boolean isLiquid = phase.getType() == PhaseType.LIQUID;
boolean isAqueous = phase.getType() == PhaseType.AQUEOUS;
boolean isAsphaltene = phase.getType() == PhaseType.ASPHALTENE;
boolean isSolid = phase.getType() == PhaseType.SOLID;

// Check using string-based lookup (convenient API)
boolean hasGas = fluid.hasPhaseType("gas");
boolean hasAsphaltene = fluid.hasPhaseType("asphaltene");

Available PhaseType Values

PhaseType Description Value StateOfMatter
GAS Gas phase 1 GAS
LIQUID Generic liquid 0 LIQUID
OIL Oil/hydrocarbon liquid 2 LIQUID
AQUEOUS Water-rich liquid 3 LIQUID
HYDRATE Gas hydrate solid 4 SOLID
WAX Wax solid 5 SOLID
SOLID Generic solid 6 SOLID
SOLIDCOMPLEX Complex solid 7 SOLID
ASPHALTENE Asphaltene solid 8 SOLID
LIQUID_ASPHALTENE Asphaltene liquid (Pedersen) 9 LIQUID

Stability Analysis

// Phase stability check
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.checkStability();

boolean stable = fluid.isPhaseStable();

Phase Properties Calculation

EoS Properties

// Fugacity coefficients
for (int i = 0; i < phase.getNumberOfComponents(); i++) {
    double phi = phase.getComponent(i).getFugacityCoefficient();
    double lnPhi = phase.getComponent(i).getLogFugacityCoefficient();
}

// Compressibility factor derivatives
double dZdT = phase.getdZdT();
double dZdP = phase.getdZdP();

// Fugacity coefficient derivatives
double dPhidT = phase.getComponent(0).getdfugdT();
double dPhidP = phase.getComponent(0).getdfugdP();

Mixing Properties

// Excess properties
double GE = phase.getExcessGibbsEnergy();
double HE = phase.getExcessEnthalpy();
double SE = phase.getExcessEntropy();
double VE = phase.getExcessVolume();

// Activity coefficients (for liquid)
double gamma = phase.getComponent("ethanol").getActivityCoefficient();

Example: Phase Analysis

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

SystemSrkEos fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.80);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.addComponent("n-pentane", 0.05);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

System.out.println("Number of phases: " + fluid.getNumberOfPhases());

for (int p = 0; p < fluid.getNumberOfPhases(); p++) {
    PhaseInterface phase = fluid.getPhase(p);

    System.out.println("\nPhase " + (p + 1) + ": " + phase.getPhaseTypeName());
    System.out.println("  Mole fraction: " + phase.getBeta());
    System.out.println("  Density: " + phase.getDensity("kg/m3") + " kg/m³");
    System.out.println("  Z-factor: " + phase.getZ());
    System.out.println("  Viscosity: " + phase.getViscosity("cP") + " cP");

    System.out.println("  Composition:");
    for (int i = 0; i < phase.getNumberOfComponents(); i++) {
        String name = phase.getComponent(i).getName();
        double x = phase.getComponent(i).getx();
        System.out.printf("    %s: %.4f%n", name, x);
    }
}

Electrolyte CPA

Electrolyte CPA Model Documentation

Overview

The electrolyte CPA (Cubic Plus Association) model in NeqSim extends the standard CPA equation of state to handle aqueous electrolyte solutions. The model is based on the work of Solbraa (2002) and combines:

  1. CPA equation of state for non-electrolyte interactions (van der Waals + association)
  2. Fürst electrostatic contribution for ion-ion and ion-solvent interactions
  3. Short-range Wij parameters for specific ion-solvent and ion-ion correlations

Implementation Classes

The Statoil (now Equinor) implementation of the electrolyte CPA model:

import neqsim.thermo.system.SystemElectrolyteCPAstatoil;

SystemElectrolyteCPAstatoil system = new SystemElectrolyteCPAstatoil(298.15, 1.01325);
system.addComponent("water", 55.5);
system.addComponent("Na+", 1.0);
system.addComponent("Cl-", 1.0);
system.chemicalReactionInit();
system.createDatabase(true);
system.setMixingRule(10);  // Required: CPA mixing rule with temperature/composition dependency

Key Features of the Statoil Implementation

Feature Description
Model Name Electrolyte-CPA-EOS-statoil
Base Class Extends SystemFurstElectrolyteEos
Phase Class PhaseElectrolyteCPAstatoil
Component Class ComponentElectrolyteCPAstatoil
Attractive Term Term 15 (Mathias-Copeman alpha function)
Volume Correction Enabled by default
Fürst Parameters Uses electrolyteCPA parameter set

Class Hierarchy

SystemThermo
  └── SystemSrkEos
        └── SystemFurstElectrolyteEos
              └── SystemElectrolyteCPAstatoil
                    ├── PhaseElectrolyteCPAstatoil (phase calculations)
                    └── ComponentElectrolyteCPAstatoil (component properties)

Mixing Rule 10 - CPA with Temperature/Composition Dependency

Overview

Mixing rule 10 is the recommended mixing rule for all CPA and electrolyte CPA systems. It automatically selects the appropriate sub-type based on the binary interaction parameters:

system.setMixingRule(10);  // Automatically selects optimal sub-type

Automatic Sub-Type Selection

The mixing rule analyzes the binary interaction parameter matrices and selects:

Condition Sub-Type Class Description
Symmetric kij, no T-dependency classic-CPA ClassicSRK Simple symmetric mixing
Symmetric kij, with T-dependency classic-CPA_T ClassicSRKT2 Temperature-dependent symmetric
Asymmetric kij (kij ≠ kji) classic-CPA_Tx ClassicSRKT2x Full asymmetric + T-dependent

Mathematical Formulation

The a parameter mixing rule:

$$a = \sum_i \sum_j x_i x_j \sqrt{a_i a_j} (1 - k_{ij})$$

For asymmetric mixing (ClassicSRKT2x):

$$k_{ij} \neq k_{ji}$$

Temperature dependency:

$$k_{ij}(T) = k_{ij,0} + k_{ij,T} \cdot T$$

Why Mixing Rule 10?

  1. Automatic optimization: Selects the simplest sufficient mixing rule
  2. Temperature dependency: Captures T-dependent phase behavior
  3. Asymmetric parameters: Handles non-symmetric ion-solvent interactions
  4. Database integration: Uses binary parameters from NeqSim database

Theoretical Background

Helmholtz Energy Decomposition

The total residual Helmholtz energy is decomposed as:

$$A^{res} = A^{CPA} + A^{elec}$$

Where:

Fürst Electrostatic Model

The electrostatic contribution follows the Fürst model, which combines:

  1. Mean Spherical Approximation (MSA) for ion-ion interactions
  2. Born solvation term for ion-solvent interactions
  3. Short-range Wij terms for specific ion interactions

$$A^{elec} = A^{MSA} + A^{Born} + A^{SR}$$

Mean Spherical Approximation (MSA)

The MSA term accounts for the electrostatic screening between ions:

$$\frac{A^{MSA}}{RT} = -\frac{V}{3\pi} \left[ \Gamma^3 + \frac{3\Gamma\sigma_+ \sigma_-}{1 + \Gamma\sigma_{+-}} \right]$$

Where:

Born Solvation Term

The Born term accounts for the solvation energy of ions in the dielectric medium:

$$\frac{A^{Born}}{RT} = -\frac{e^2 N_A}{8\pi\varepsilon_0 k_B T} \sum_i n_i \frac{z_i^2}{\sigma_i} \left(1 - \frac{1}{\varepsilon_r}\right)$$

Where:

Short-Range Interaction Parameters (Wij)

Overview

The short-range Wij parameters capture specific ion-solvent and ion-ion interactions not described by the electrostatic terms. These are fitted to experimental activity coefficient and osmotic coefficient data.

Parameter Correlations

The Wij values are calculated using linear correlations with ionic diameter:

Monovalent (1+) Cations

Wij(cation-water) = furstParamsCPA[2] × stokesDiameter + furstParamsCPA[3]
Wij(cation-anion) = furstParamsCPA[4] × (d_cat + d_an)^4 + furstParamsCPA[5]

Current fitted values (2024):

Divalent (2+) Cations

Wij(2+ cation-water) = furstParamsCPA[6] × stokesDiameter + furstParamsCPA[7]
Wij(2+ cation-anion) = furstParamsCPA[8] × (d_cat + d_an)^4 + furstParamsCPA[9]

Current fitted values (refitted December 2024):

Why Separate Parameters for Divalent Cations?

The divalent cation parameters differ significantly from monovalent:

Using unified parameters would give dramatically wrong Wij values for divalent cations (sometimes even wrong sign), making separate parameters essential for accuracy.

Parameter Monovalent Divalent Ratio
Slope 4.98e-05 5.40e-05 1.08
Intercept -1.22e-04 -1.72e-04 1.42

Validation Against Experimental Data

Robinson & Stokes Data (1965)

The model has been validated against Robinson & Stokes experimental data for mean activity coefficients (γ±) and osmotic coefficients (φ) at 25°C.

Monovalent Salts (1:1 electrolytes)

Salt Type γ± Error φ Error
NaCl 1-1 2.4% 1.6%
KCl 1-1 4.3% 1.0%
LiCl 1-1 3.4% 2.5%
NaBr 1-1 2.8% 2.0%
KBr 1-1 1.4% 2.0%

Divalent Cation Salts (2:1 electrolytes)

Salt Type γ± Error φ Error
CaCl₂ 2-1 7.0% 4.2%
MgCl₂ 2-1 9.6% 4.6%
BaCl₂ 2-1 2.3% 1.5%

Divalent Anion Salts (1:2 electrolytes)

Salt Type γ± Error φ Error
Na₂SO₄ 1-2 20.0% 19.7%
K₂SO₄ 1-2 2.9% 1.6%

Overall Performance

Dielectric Constant Mixing Rules

The model supports three dielectric constant mixing rules:

$$\varepsilon_{mix} = \sum_i x_i \varepsilon_i$$

2. VOLUME_AVERAGE

$$\varepsilon_{mix} = \sum_i \phi_i \varepsilon_i$$

Where $\phi_i$ is the volume fraction.

3. LOOYENGA

$$\varepsilon_{mix}^{1/3} = \sum_i \phi_i \varepsilon_i^{1/3}$$

Thermodynamic Consistency

The model has been verified for thermodynamic consistency using built-in checks:

Fugacity Coefficient Identities

✅ $\sum_i x_i \ln\phi_i = \frac{G^{res}}{RT}$ - PASSED

Derivative Consistency

✅ $\left(\frac{\partial \ln\phi_i}{\partial P}\right)_T = \frac{\bar{V}_i - V_{ig}}{RT}$ - PASSED

✅ $\left(\frac{\partial \ln\phi_i}{\partial T}\right)_P = \frac{H_{ig} - \bar{H}_i}{RT^2}$ - PASSED

✅ $\left(\frac{\partial \ln\phi_i}{\partial n_j}\right)_{T,P,n_{k\neq j}} = \left(\frac{\partial \ln\phi_j}{\partial n_i}\right)_{T,P,n_{k\neq i}}$ (symmetry) - PASSED

Usage Example

// Create electrolyte CPA system
SystemInterface system = new SystemElectrolyteCPAstatoil(298.15, 1.01325);

// Add water (solvent)
system.addComponent("water", 55.5); // mol

// Add electrolyte
system.addComponent("Na+", 1.0);
system.addComponent("Cl-", 1.0);

// Initialize
system.chemicalReactionInit();
system.createDatabase(true);
system.setMixingRule(10); // Electrolyte CPA mixing rule

// Run flash calculation
ThermodynamicOperations ops = new ThermodynamicOperations(system);
ops.TPflash();

// Get activity coefficients
int aq = system.getPhaseNumberOfPhase("aqueous");
int naIdx = system.getPhase(aq).getComponent("Na+").getComponentNumber();
int clIdx = system.getPhase(aq).getComponent("Cl-").getComponentNumber();
int waterIdx = system.getPhase(aq).getComponent("water").getComponentNumber();

double gammaNa = system.getPhase(aq).getActivityCoefficient(naIdx, waterIdx);
double gammaCl = system.getPhase(aq).getActivityCoefficient(clIdx, waterIdx);
double meanGamma = Math.sqrt(gammaNa * gammaCl); // Mean activity coefficient

double phi = system.getPhase(aq).getOsmoticCoefficientOfWater();

Mixed Solvent Systems

The model supports mixed solvent systems including:

Separate Wij parameters are available for each solvent system.

Known Limitations

  1. Divalent anions (SO₄²⁻): Higher errors for 1:2 electrolytes like Na₂SO₄ (~20%)
  2. High concentrations: Accuracy decreases above ~2 mol/kg for some salts
  3. VOLUME_AVERAGE/LOOYENGA mixing rules: Incomplete composition derivatives
  4. Temperature range: Parameters fitted at 25°C, extrapolation accuracy may vary

Comparison: CPA vs Electrolyte CPA

Feature SystemSrkCPAstatoil SystemElectrolyteCPAstatoil
Use Case Non-ionic associating systems Aqueous electrolyte solutions
Electrostatics None MSA + Born solvation
Ions Not supported Na+, K+, Ca++, Mg++, Cl-, etc.
Mixing Rule 10 (recommended) 10 (required)
Chemical Reactions Optional Recommended (pH, speciation)
Phase Class PhaseSrkCPAs PhaseElectrolyteCPAstatoil

Quick Start Examples

Simple NaCl Solution

SystemInterface system = new SystemElectrolyteCPAstatoil(298.15, 1.01325);
system.addComponent("water", 55.5);
system.addComponent("Na+", 0.5);
system.addComponent("Cl-", 0.5);
system.createDatabase(true);
system.setMixingRule(10);
system.init(0);
system.init(1);

// Get mean activity coefficient
double gammaMean = system.getPhase(0).getMeanIonicActivityCoefficient("Na+", "Cl-");

With Chemical Reactions (pH Calculation)

SystemInterface system = new SystemElectrolyteCPAstatoil(298.15, 1.01325);
system.addComponent("water", 55.5);
system.addComponent("CO2", 0.01);
system.addComponent("Na+", 0.1);
system.addComponent("Cl-", 0.1);
system.chemicalReactionInit();  // Enable pH and speciation
system.createDatabase(true);
system.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(system);
ops.TPflash();

// Access aqueous phase
int aq = system.getPhaseNumberOfPhase("aqueous");
double pH = -Math.log10(system.getPhase(aq).getComponent("H3O+").getx() * 55.5);

Gas-Liquid Equilibrium with Electrolytes

SystemInterface system = new SystemElectrolyteCPAstatoil(323.15, 50.0);
system.addComponent("methane", 10.0);
system.addComponent("water", 100.0);
system.addComponent("MEG", 20.0);
system.addComponent("Na+", 1.0);
system.addComponent("Cl-", 1.0);
system.createDatabase(true);
system.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(system);
ops.TPflash();

// Check phase compositions
for (int i = 0; i < system.getNumberOfPhases(); i++) {
    System.out.println("Phase " + i + ": " + system.getPhase(i).getType());
}

References

  1. Solbraa, E. (2002). "Measurement and Modelling of Absorption of Carbon Dioxide into Methyldiethanolamine Solutions at High Pressures." PhD Thesis, Norwegian University of Science and Technology.

  2. Fürst, W., & Renon, H. (1993). "Representation of excess properties of electrolyte solutions using a new equation of state." AIChE Journal, 39(2), 335-343.

  3. Robinson, R.A., & Stokes, R.H. (1965). "Electrolyte Solutions." 2nd Edition, Butterworths, London.

  4. Kontogeorgis, G.M., & Folas, G.K. (2010). "Thermodynamic Models for Industrial Applications." Wiley.

  5. Michelsen, M.L., & Mollerup, J.M. (2007). "Thermodynamic Models: Fundamentals & Computational Aspects." Tie-Line Publications.

Parameter History

Date Change Impact
2002 Initial parameters from Solbraa thesis Baseline model
2024 Refitted monovalent parameters to Robinson & Stokes γ± error: 2.8%
Dec 2024 Refitted divalent cation parameters [6-9] CaCl₂: 16%→7%, MgCl₂: 22%→10%
Dec 2024 Updated chemical equilibrium solver Improved pH accuracy

Source Code References

System Classes

Phase Classes

Component Classes

Mixing Rules

Parameters

Tests


Last updated: December 27, 2024

Chapter 7: Flash Calculations

Flash Guide

Flash Calculations Guide

This guide provides comprehensive documentation of flash calculations available in NeqSim via the ThermodynamicOperations class. Flash calculations determine the equilibrium state of a thermodynamic system by solving phase equilibrium equations under specified constraints.

Table of Contents


Overview

Flash calculations solve phase equilibrium problems by finding:

The mathematical basis is the equality of chemical potentials (or fugacities) for all components across all phases:

$$f_i^{vapor} = f_i^{liquid} = f_i^{solid}$$

where $f_i$ is the fugacity of component $i$.


Basic Usage

All flash calculations use the ThermodynamicOperations class:

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// 1. Create a fluid system
SystemInterface fluid = new SystemSrkEos(298.15, 50.0);  // T in K, P in bara
fluid.addComponent("methane", 0.8);
fluid.addComponent("ethane", 0.15);
fluid.addComponent("propane", 0.05);
fluid.setMixingRule("classic");

// 2. Create operations object
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);

// 3. Run the flash calculation
ops.TPflash();

// 4. Access results
System.out.println("Vapor fraction: " + fluid.getBeta());
System.out.println("Number of phases: " + fluid.getNumberOfPhases());
System.out.println("Temperature: " + fluid.getTemperature("C") + " °C");
System.out.println("Pressure: " + fluid.getPressure("bara") + " bara");

Flash Types

TP Flash (Temperature-Pressure)

The most common flash type. Given temperature and pressure, find phase split and compositions. NeqSim implements the classical Michelsen flash algorithm with stability analysis.

Method signatures:

void TPflash()
void TPflash(boolean checkForSolids)

Example:

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("n-heptane", 0.1);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Results
double vaporFraction = fluid.getBeta();  // Molar vapor fraction
double liquidDensity = fluid.getPhase("oil").getDensity("kg/m3");

With solid phase checking:

fluid.setSolidPhaseCheck(true);
ops.TPflash(true);  // Includes solid equilibrium

Multi-Phase Checking

By default, NeqSim checks for two-phase (gas-liquid) equilibrium. For systems that may form multiple liquid phases (VLLE, LLE), enable multi-phase checking:

fluid.setMultiPhaseCheck(true);
ops.TPflash();
// Will detect gas + multiple liquid phases (e.g., oil, aqueous)

Enhanced Stability Analysis

For complex mixtures where standard stability analysis may miss additional phases (e.g., sour gas with CO₂/H₂S, LLE systems), enable enhanced stability analysis:

fluid.setMultiPhaseCheck(true);
fluid.setEnhancedMultiPhaseCheck(true);  // Enable enhanced phase detection
ops.TPflash();

The enhanced stability analysis:

Example - Sour Gas Three-Phase Detection:

// Sour gas mixture: methane/CO2/H2S at low temperature
SystemInterface sourGas = new SystemPrEos(210.0, 55.0);  // ~-63°C, 55 bar
sourGas.addComponent("methane", 49.88);
sourGas.addComponent("CO2", 9.87);
sourGas.addComponent("H2S", 40.22);

sourGas.setMixingRule("classic");
sourGas.setMultiPhaseCheck(true);
sourGas.setEnhancedMultiPhaseCheck(true);  // Critical for finding 3 phases

ThermodynamicOperations ops = new ThermodynamicOperations(sourGas);
ops.TPflash();
sourGas.initProperties();

System.out.println("Number of phases: " + sourGas.getNumberOfPhases());
// May find: vapor + CO2-rich liquid + H2S-rich liquid

When to use enhanced stability analysis:

Note: Enhanced stability analysis adds computational overhead. For simple VLE systems, the standard analysis is sufficient.


PH Flash (Pressure-Enthalpy)

Given pressure and total enthalpy, find temperature and phase split. Essential for:

Method signatures:

void PHflash(double Hspec)                    // H in J
void PHflash(double Hspec, String unit)       // Supported: J, J/mol, J/kg, kJ/kg
void PHflash(double Hspec, int type)          // type 0 = standard

Example - Joule-Thomson expansion:

SystemInterface fluid = new SystemSrkEos(350.0, 100.0);
fluid.addComponent("methane", 1.0);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
fluid.initPhysicalProperties();

// Store inlet enthalpy
double inletH = fluid.getEnthalpy("J");

// Reduce pressure (isenthalpic process)
fluid.setPressure(10.0, "bara");

// Find new temperature at same enthalpy
ops.PHflash(inletH);

System.out.println("Outlet temperature: " + fluid.getTemperature("C") + " °C");
// Demonstrates Joule-Thomson cooling

With unit specification:

// Enthalpy specified in kJ/kg
ops.PHflash(-150.0, "kJ/kg");

PS Flash (Pressure-Entropy)

Given pressure and total entropy, find temperature and phase split. Used for:

Method signatures:

void PSflash(double Sspec)                    // S in J/K
void PSflash(double Sspec, String unit)       // Supported: J/K, J/molK, J/kgK, kJ/kgK

Example - Isentropic compression:

SystemInterface fluid = new SystemSrkEos(300.0, 10.0);
fluid.addComponent("methane", 1.0);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
fluid.initPhysicalProperties();

// Inlet conditions
double T1 = fluid.getTemperature("K");
double S_inlet = fluid.getEntropy("J/K");

// Compress to higher pressure (isentropic)
fluid.setPressure(50.0, "bara");
ops.PSflash(S_inlet);

double T2 = fluid.getTemperature("K");
System.out.println("Isentropic outlet T: " + (T2 - 273.15) + " °C");

// Compare to actual with polytropic efficiency
double eta_poly = 0.85;
double T2_actual = T1 + (T2 - T1) / eta_poly;

PU Flash (Pressure-Internal Energy)

Given pressure and internal energy, find temperature and phase split.

Method signatures:

void PUflash(double Uspec)                    // U in J
void PUflash(double Uspec, String unit)       // Supported: J, J/mol, J/kg, kJ/kg
void PUflash(double Pspec, double Uspec, String unitP, String unitU)

Example:

ops.PUflash(100.0, -500.0, "bara", "kJ/kg");

TV Flash (Temperature-Volume)

Given temperature and total volume, find pressure and phase split. Used for:

Method signatures:

void TVflash(double Vspec)                    // V in cm³
void TVflash(double Vspec, String unit)       // Supported: m3

Example - Fixed volume vessel:

SystemInterface fluid = new SystemSrkEos(300.0, 10.0);
fluid.addComponent("nitrogen", 1.0);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Vessel volume = 1 m³
double vesselVolume = 1.0;  // m³

// Heat the vessel (isochoric process)
fluid.setTemperature(400.0, "K");
ops.TVflash(vesselVolume, "m3");

System.out.println("New pressure: " + fluid.getPressure("bara") + " bara");

TS Flash (Temperature-Entropy)

Given temperature and entropy, find pressure and phase split. Uses the Q-function methodology based on Michelsen (1999).

Method signatures:

void TSflash(double Sspec)                    // S in J/K
void TSflash(double Sspec, String unit)       // Supported: J/K, J/molK, J/kgK, kJ/kgK

Thermodynamic derivative: $$\left(\frac{\partial S}{\partial P}\right)_T = -\left(\frac{\partial V}{\partial T}\right)_P$$


TH Flash (Temperature-Enthalpy)

Given temperature and enthalpy, find pressure and phase split. Uses the Q-function methodology with Newton iteration.

Applications:

Method signatures:

void THflash(double Hspec)                    // H in J
void THflash(double Hspec, String unit)       // Supported: J, J/mol, J/kg, kJ/kg

Example:

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.1);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Store enthalpy at initial pressure
double H_target = fluid.getEnthalpy("J");

// Change temperature (enthalpy will change)
fluid.setTemperature(280.0, "K");

// Find pressure that gives same enthalpy at new temperature
ops.THflash(H_target);
System.out.println("Pressure for same H at new T: " + fluid.getPressure("bara") + " bara");

Thermodynamic derivative: $$\left(\frac{\partial H}{\partial P}\right)_T = V - T\left(\frac{\partial V}{\partial T}\right)_P$$


TU Flash (Temperature-Internal Energy)

Given temperature and internal energy, find pressure and phase split. Uses the Q-function methodology with Newton iteration.

Applications:

Method signatures:

void TUflash(double Uspec)                    // U in J
void TUflash(double Uspec, String unit)       // Supported: J, J/mol, J/kg, kJ/kg

Example:

SystemInterface fluid = new SystemSrkEos(350.0, 20.0);
fluid.addComponent("methane", 1.0);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Store internal energy
double U_target = fluid.getInternalEnergy("J");

// Change temperature
fluid.setTemperature(300.0, "K");

// Find pressure that maintains same internal energy
ops.TUflash(U_target);
System.out.println("Pressure for same U at new T: " + fluid.getPressure("bara") + " bara");

Thermodynamic derivative: $$\left(\frac{\partial U}{\partial P}\right)_T = -T\left(\frac{\partial V}{\partial T}\right)_P - P\left(\frac{\partial V}{\partial P}\right)_T$$


PV Flash (Pressure-Volume)

Given pressure and volume, find temperature and phase split. Uses the Q-function methodology with Newton iteration.

Applications:

Method signatures:

void PVflash(double Vspec)                    // V in m³
void PVflash(double Vspec, String unit)       // Supported: m3

Example:

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("nitrogen", 1.0);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Store volume at initial conditions
double V_target = fluid.getVolume("m3");

// Change pressure
fluid.setPressure(100.0, "bara");

// Find temperature that gives same volume at new pressure
ops.PVflash(V_target);
System.out.println("Temperature for same V at new P: " + fluid.getTemperature("C") + " °C");

Thermodynamic derivative: $$\left(\frac{\partial V}{\partial T}\right)_P$$


VH Flash (Volume-Enthalpy)

Given volume and enthalpy, find temperature, pressure, and phase split. Used for:

Method signatures:

void VHflash(double Vspec, double Hspec)
void VHflash(double V, double H, String unitV, String unitH)

Example:

ops.VHflash(0.5, -50000.0, "m3", "J");

VU Flash (Volume-Internal Energy)

Given volume and internal energy, find temperature, pressure, and phase split. Critical for:

Method signatures:

void VUflash(double Vspec, double Uspec)
void VUflash(double V, double U, String unitV, String unitU)

Example - Dynamic depressurization:

SystemInterface fluid = new SystemSrkCPAstatoil(300.0, 100.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("CO2", 0.1);
fluid.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Initial state
double V0 = fluid.getVolume("m3");
double U0 = fluid.getInternalEnergy("J");

// Simulate adiabatic expansion (U constant, V increases)
double V_new = V0 * 2.0;  // Volume doubles
ops.VUflash(V_new, U0, "m3", "J");

System.out.println("New T: " + fluid.getTemperature("C") + " °C");
System.out.println("New P: " + fluid.getPressure("bara") + " bara");

VS Flash (Volume-Entropy)

Given volume and entropy, find temperature, pressure, and phase split.

Method signatures:

void VSflash(double Vspec, double Sspec)
void VSflash(double V, double S, String unitV, String unitS)

Q-Function Flash Methodology

Several flash types in NeqSim use the Q-function methodology described by Michelsen (1999). This approach is particularly effective for state function specifications (entropy, enthalpy, internal energy, volume) where the flash must solve for temperature and/or pressure.

Theory

The Q-function method uses a nested iteration approach:

  1. Outer loop: Newton iteration on the state function residual (e.g., $S - S_{spec}$)
  2. Inner loop: TP flash to determine phase equilibrium at each T,P guess

The key advantage is that the inner TP flash handles all the phase equilibrium complexity, while the outer loop only needs to adjust T or P based on the state function derivative.

Thermodynamic Derivatives

The Q-function flashes use analytical thermodynamic derivatives computed via init(3):

Flash Specification Solved Derivative Used
TSflash T, S P $\left(\frac{\partial S}{\partial P}\right)_T = -\left(\frac{\partial V}{\partial T}\right)_P$
THflash T, H P $\left(\frac{\partial H}{\partial P}\right)_T = V - T\left(\frac{\partial V}{\partial T}\right)_P$
TUflash T, U P $\left(\frac{\partial U}{\partial P}\right)_T = -T\left(\frac{\partial V}{\partial T}\right)_P - P\left(\frac{\partial V}{\partial P}\right)_T$
TVflash T, V P $\left(\frac{\partial V}{\partial P}\right)_T$
PVflash P, V T $\left(\frac{\partial V}{\partial T}\right)_P$
VUflash V, U T, P 2D Newton with $\frac{\partial U}{\partial T}$, $\frac{\partial U}{\partial P}$, $\frac{\partial V}{\partial T}$, $\frac{\partial V}{\partial P}$
VHflash V, H T, P 2D Newton with $\frac{\partial H}{\partial T}$, $\frac{\partial H}{\partial P}$, $\frac{\partial V}{\partial T}$, $\frac{\partial V}{\partial P}$
VSflash V, S T, P 2D Newton with $\frac{\partial S}{\partial T}$, $\frac{\partial S}{\partial P}$, $\frac{\partial V}{\partial T}$, $\frac{\partial V}{\partial P}$

Implementation Pattern

All Q-function flashes follow a similar pattern:

// Example: TH flash pseudocode
public double solveQ() {
    do {
        system.init(3);  // Calculate derivatives

        double residual = system.getEnthalpy() - Hspec;

        // Analytical derivative: (dH/dP)_T = V - T*(dV/dT)_P
        double V = system.getVolume();
        double T = system.getTemperature();
        double dVdT = -system.getdVdTpn();  // Note sign convention
        double dHdP = (V - T * dVdT) * 1e5;  // Convert to J/bar

        // Newton step with damping
        double deltaP = -factor * residual / dHdP;

        nyPres = oldPres + deltaP;
        system.setPressure(nyPres);
        tpFlash.run();

    } while (error > tolerance && iterations < maxIter);
}

NeqSim Sign Conventions

Note the sign conventions used in NeqSim for thermodynamic derivatives:

References

  1. Michelsen, M.L. (1999). "State function based flash specifications." Fluid Phase Equilibria, 158-160, 617-626.
  2. Michelsen, M.L. and Mollerup, J.M. (2007). Thermodynamic Models: Fundamentals & Computational Aspects, 2nd Edition. Tie-Line Publications.

Saturation Calculations

Bubble Point

Calculate the bubble point (onset of vaporization) at a given temperature or pressure.

Temperature flash (find T at given P):

void bubblePointTemperatureFlash()

Pressure flash (find P at given T):

void bubblePointPressureFlash()
void bubblePointPressureFlash(boolean derivatives)  // Include dP/dT, dP/dx

Example:

SystemInterface fluid = new SystemSrkEos(298.15, 10.0);
fluid.addComponent("propane", 0.5);
fluid.addComponent("n-butane", 0.5);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);

// Find bubble point pressure at 25°C
fluid.setTemperature(298.15, "K");
ops.bubblePointPressureFlash();
System.out.println("Bubble point pressure: " + fluid.getPressure("bara") + " bara");

// Find bubble point temperature at 5 bar
fluid.setPressure(5.0, "bara");
ops.bubblePointTemperatureFlash();
System.out.println("Bubble point temperature: " + fluid.getTemperature("C") + " °C");

Dew Point

Calculate the dew point (onset of condensation) at a given temperature or pressure.

Temperature flash:

void dewPointTemperatureFlash()
void dewPointTemperatureFlash(boolean derivatives)

Pressure flash:

void dewPointPressureFlash()

Example:

// Natural gas dew point
SystemInterface gas = new SystemSrkEos(298.15, 50.0);
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.08);
gas.addComponent("propane", 0.04);
gas.addComponent("n-butane", 0.02);
gas.addComponent("n-pentane", 0.01);
gas.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(gas);

// Find dew point at 50 bar
ops.dewPointTemperatureFlash();
System.out.println("Hydrocarbon dew point: " + gas.getTemperature("C") + " °C");

Water Dew Point

Calculate the water dew point (onset of water condensation).

Methods:

void waterDewPointTemperatureFlash()
void waterDewPointTemperatureMultiphaseFlash()  // For complex systems

Example:

SystemInterface wetGas = new SystemSrkCPAstatoil(298.15, 80.0);
wetGas.addComponent("methane", 0.95);
wetGas.addComponent("water", 0.05);
wetGas.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(wetGas);
ops.waterDewPointTemperatureFlash();

System.out.println("Water dew point: " + wetGas.getTemperature("C") + " °C");

Hydrocarbon Dew Point

Calculate the cricondentherm (maximum temperature for two-phase region).

void dewPointPressureFlashHC()

Special Flash Types

Solid Phase Flash

For systems with potential solid precipitation (wax, ice, hydrates).

void TPSolidflash()
void PHsolidFlash(double Hspec)
void freezingPointTemperatureFlash()

Example - Wax precipitation:

SystemInterface oil = new SystemSrkEos(320.0, 10.0);
oil.addComponent("n-C20", 0.1);
oil.addComponent("n-C10", 0.9);
oil.setSolidPhaseCheck("n-C20");
oil.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(oil);
ops.freezingPointTemperatureFlash();
System.out.println("Wax appearance temperature: " + oil.getTemperature("C") + " °C");

Critical Point Flash

Find the critical point of a mixture.

void criticalPointFlash()

Example:

SystemInterface mix = new SystemSrkEos(300.0, 50.0);
mix.addComponent("methane", 0.7);
mix.addComponent("ethane", 0.3);
mix.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(mix);
ops.criticalPointFlash();

System.out.println("Critical T: " + mix.getTemperature("K") + " K");
System.out.println("Critical P: " + mix.getPressure("bara") + " bara");

Gradient Flash

Calculate composition variation with depth (gravitational segregation).

SystemInterface TPgradientFlash(double height, double temperature)

Parameters:


Phase Fraction Flash

Find conditions for a specified phase fraction.

void constantPhaseFractionPressureFlash(double fraction)      // Find P at given vapor fraction
void constantPhaseFractionTemperatureFlash(double fraction)   // Find T at given vapor fraction
void TVfractionFlash(double Vfraction)                        // Volume fraction based

Reference EoS Flash Methods

For high-accuracy calculations, NeqSim provides flash methods using reference equations of state.

GERG-2008 Flashes

For natural gas systems with GERG-2008 accuracy:

void PHflashGERG2008(double Hspec)
void PSflashGERG2008(double Sspec)

Example:

SystemInterface gas = new SystemGERG2008Eos(280.0, 100.0);
gas.addComponent("methane", 0.9);
gas.addComponent("ethane", 0.06);
gas.addComponent("CO2", 0.04);

ThermodynamicOperations ops = new ThermodynamicOperations(gas);
ops.TPflash();
double H = gas.getEnthalpy("J");

gas.setPressure(50.0);
ops.PHflashGERG2008(H);  // High-accuracy isenthalpic flash

Leachman (Hydrogen) Flashes

For pure hydrogen systems:

void PHflashLeachman(double Hspec)
void PSflashLeachman(double Sspec)

Vega (CO₂) Flashes

For pure CO₂ systems:

void PHflashVega(double Hspec)
void PSflashVega(double Sspec)

Hydrate Calculations

NeqSim provides comprehensive calculations for gas hydrate formation, including multi-phase equilibrium with hydrate, inhibitor effects, and cavity occupancy calculations.

📚 Detailed Documentation:

Hydrate TPflash

Calculate phase equilibrium including hydrate at given T and P:

SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 5.0, 100.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.04);
fluid.addComponent("water", 0.03);
fluid.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.hydrateTPflash();

// Check phases: GAS, AQUEOUS, HYDRATE
fluid.prettyPrint();

Gas-Hydrate Equilibrium (No Aqueous)

For systems with trace water where all water can be consumed by hydrate:

SystemInterface fluid = new SystemSrkCPAstatoil(273.15 - 15.0, 250.0);
fluid.addComponent("methane", 0.9998);
fluid.addComponent("water", 0.0002);  // 200 ppm water
fluid.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.gasHydrateTPflash();  // Targets gas-hydrate equilibrium

// Result: GAS + HYDRATE phases (no AQUEOUS)

Formation Temperature

void hydrateFormationTemperature()
void hydrateFormationTemperature(double initialGuess)
void hydrateFormationTemperature(int structure)  // 0=ice, 1=sI, 2=sII

Formation Pressure

void hydrateFormationPressure()
void hydrateFormationPressure(int structure)

Inhibitor Calculations

void hydrateInhibitorConcentration(String inhibitor, double targetT)
void hydrateInhibitorConcentrationSet(String inhibitor, double wtFrac)

Supported inhibitors: MEG, TEG, methanol, ethanol

Complete Example

SystemInterface gas = new SystemSrkCPAstatoil(280.0, 100.0);
gas.addComponent("methane", 0.9);
gas.addComponent("ethane", 0.05);
gas.addComponent("CO2", 0.02);
gas.addComponent("water", 0.03);
gas.setMixingRule(10);
gas.setHydrateCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(gas);

// Calculate hydrate formation temperature
ops.hydrateFormationTemperature();
System.out.println("Hydrate formation T: " + gas.getTemperature("C") + " °C");

// Check if hydrate forms at 5°C
gas.setTemperature(273.15 + 5.0);
ops.hydrateTPflash();
if (gas.hasHydratePhase()) {
    System.out.println("Hydrate fraction: " + gas.getBeta(PhaseType.HYDRATE));
}

Four-Phase Equilibrium (Gas-Oil-Aqueous-Hydrate)

SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 4.0, 100.0);
fluid.addComponent("methane", 0.70);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.05);
fluid.addComponent("n-hexane", 0.02);
fluid.addComponent("n-heptane", 0.05);
fluid.addComponent("water", 0.10);
fluid.setMixingRule(10);

ops.hydrateTPflash();
// Phases: GAS, OIL, AQUEOUS, HYDRATE

Unit Handling

Most flash methods accept unit specifications for flexibility:

Enthalpy Units (PH Flash)

Unit Description
J Joules (total)
J/mol Joules per mole
J/kg Joules per kilogram
kJ/kg Kilojoules per kilogram

Entropy Units (PS Flash)

Unit Description
J/K Joules per Kelvin (total)
J/molK Joules per mole-Kelvin
J/kgK Joules per kg-Kelvin
kJ/kgK Kilojoules per kg-Kelvin

Volume Units

Unit Description
m3 Cubic meters
(default) cm³ for internal methods

Error Handling

Flash calculations can fail if:

Best practice:

try {
    ops.dewPointTemperatureFlash();
} catch (IsNaNException e) {
    System.err.println("No dew point found: " + e.getMessage());
}

// Check for valid result
if (Double.isNaN(fluid.getTemperature())) {
    System.err.println("Flash calculation did not converge");
}

Best Practices

  1. Always initialize before flashing:

    fluid.init(0);  // Basic initialization
    ops.TPflash();
    fluid.init(3);  // Full thermodynamic initialization after flash
    
  2. Reuse ThermodynamicOperations:

    // Good - single instance
    ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
    for (double T : temperatures) {
        fluid.setTemperature(T, "K");
        ops.TPflash();
    }
    
  3. Clone fluids for independent calculations:

    SystemInterface fluid2 = fluid.clone();
    ThermodynamicOperations ops2 = new ThermodynamicOperations(fluid2);
    
  4. Check phase existence before accessing:

    if (fluid.hasPhaseType("gas")) {
        double gasRho = fluid.getPhase("gas").getDensity("kg/m3");
    }
    
  5. Use appropriate EoS for the application:

    • Hydrocarbons: SRK or PR
    • Polar/associating: CPA
    • Natural gas (high accuracy): GERG-2008
    • CO₂ systems: CPA or Vega

API Reference Table

Method Input Specs Output Use Case
TPflash() T, P phases, compositions General equilibrium
PHflash(H) P, H T, phases Valves, heat exchangers
PSflash(S) P, S T, phases Compressors, turbines
PUflash(U) P, U T, phases Energy balance
TVflash(V) T, V P, phases Fixed-volume systems
TSflash(S) T, S P, phases Process analysis
VHflash(V,H) V, H T, P, phases Dynamic simulation
VUflash(V,U) V, U T, P, phases Blowdown, depressurization
VSflash(V,S) V, S T, P, phases Isentropic vessel
bubblePointTemperatureFlash() P T_bubble Evaporator design
bubblePointPressureFlash() T P_bubble Vapor pressure
dewPointTemperatureFlash() P T_dew Condenser design
dewPointPressureFlash() T P_dew Dew point control
waterDewPointTemperatureFlash() P T_wdp Gas dehydration
hydrateFormationTemperature() P T_hydrate Hydrate prevention
criticalPointFlash() - T_c, P_c Mixture critical

See Also


References

  1. Michelsen, M.L. & Mollerup, J.M. (2007). Thermodynamic Models: Fundamentals & Computational Aspects.
  2. Rachford, H.H. & Rice, J.D. (1952). Procedure for Use of Electronic Digital Computers in Calculating Flash Vaporization.
  3. Kunz, O. & Wagner, W. (2012). The GERG-2008 Wide-Range Equation of State for Natural Gases.

Flash Equations

Flash calculations validated by tests

NeqSim's flash algorithms are exercised heavily in the JUnit suite under src/test/java/neqsim/thermodynamicoperations/flashops. The tests document how the solvers are configured and what outputs they must reproduce, giving a reproducible view of the underlying theory.

Rachford-Rice vapor fraction solving

RachfordRiceTest switches between the Nielsen (2023) and Michelsen (2001) variants of the Rachford–Rice solver to verify that all implementations converge to the same vapor fraction for the same K-values and overall composition.【F:src/test/java/neqsim/thermodynamicoperations/flashops/RachfordRiceTest.java†L14-L39】 The test uses a binary mixture with z=[0.7, 0.3] and K=[2.0, 0.01] and asserts a vapor fraction ((\beta)) of 0.40707, which is the root of the classic balance equation:

[ \sum_i z_i \frac{K_i - 1}{1 + \beta (K_i - 1)} = 0 ]

The converged solution satisfies material balance between vapor and liquid while honoring the phase equilibrium ratios supplied by the K-values. Switching RachfordRice.setMethod(...) in the test demonstrates that NeqSim exposes multiple solver strategies for the same equation without altering the target root.【F:src/test/java/neqsim/thermodynamicoperations/flashops/RachfordRiceTest.java†L21-L33】 When modeling your own flashes, choose a method that matches your numerical preferences; the test shows that the default and named methods must agree on the fundamental solution.

TP flash energy consistency

TPFlashTest configures multicomponent systems with cubic equations of state (Peng–Robinson, UMR-PRU-MC, SRK-CPA) and validates both phase splits and energy properties after a TPflash() call. The tests cover low and high pressure regimes, multi-phase checks, and heavy pseudo-component handling.【F:src/test/java/neqsim/thermodynamicoperations/flashops/TPFlashTest.java†L19-L140】 Assertions include vapor fraction (getBeta()), number of phases, and total enthalpy, confirming that the flash calculation preserves the combined internal energy and molar balance implied by the Rachford–Rice solution and the chosen EOS.

To mirror the test configuration:

The enthalpy checks in testRun2 and testRun3 highlight that the flash solution must satisfy both material balance and the caloric EOS relationships at the specified state points.【F:src/test/java/neqsim/thermodynamicoperations/flashops/TPFlashTest.java†L43-L82】 If discrepancies appear in your own models, align your setup with the tested recipe before exploring alternative property packages.

Q-Function Flash Testing

QfuncFlashTest provides comprehensive testing for state-function based flash calculations following Michelsen's (1999) Q-function methodology. The test class validates multiple flash specifications:

Single-Variable Q-Function Flashes

These flashes solve for one unknown (T or P) given a state function constraint:

Test Flash Type Specification Validates
testTSFlash_* TSflash Temperature, Entropy Pressure convergence
testTHFlash_* THflash Temperature, Enthalpy Pressure convergence
testTUFlash_* TUflash Temperature, Internal Energy Pressure convergence
testTVFlash_* TVflash Temperature, Volume Pressure convergence
testPVFlash_* PVflash Pressure, Volume Temperature convergence

Two-Variable Q-Function Flashes

These flashes solve for both T and P simultaneously:

Test Flash Type Specification Validates
testVUFlash_* VUflash Volume, Internal Energy T, P convergence
testVHFlash_* VHflash Volume, Enthalpy T, P convergence
testVSFlash_* VSflash Volume, Entropy T, P convergence

Test Methodology

Each Q-function flash test follows this pattern:

  1. Create a fluid system and run TPflash at known conditions
  2. Store the target state function value (H, S, U, or V)
  3. Perturb the system (change T or P)
  4. Run the Q-function flash with the stored specification
  5. Verify the state function converges to the original value within tolerance

Example from the test suite:

// Store enthalpy at initial conditions
ops.TPflash();
double targetH = system.getEnthalpy();

// Change temperature (perturb the system)
system.setTemperature(newTemperature);

// Flash should find pressure that recovers original enthalpy
ops.THflash(targetH);

assertEquals(targetH, system.getEnthalpy(), tolerance);

Thermodynamic Derivatives

The Q-function flashes use analytical derivatives computed via system.init(3):

These are combined to form the Newton iteration Jacobians for each flash type.

Reference

Michelsen, M.L. (1999). "State function based flash specifications." Fluid Phase Equilibria, 158-160, 617-626.

Thermo Operations

Thermodynamic Operations

Thermodynamic operations execute equilibrium and property tasks using a configured fluid. Most workflows create a ThermodynamicOperations object once and reuse it for multiple calls.

Flash Calculations

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
double vaporFraction = fluid.getBeta();

Phase Envelopes

Property Routines

After flashes, properties are available on each phase:

Electrolytes and Reactions

Best Practices

TP Flash Algorithm

TPflash Algorithm Documentation

Overview

The Temperature-Pressure (TP) flash calculation is a fundamental operation in chemical engineering thermodynamics. Given a mixture composition, temperature, and pressure, the TP flash determines:

NeqSim implements the classical Michelsen flash algorithm with stability analysis, as described in the landmark work Thermodynamic Models: Fundamentals and Computational Aspects (Michelsen & Mollerup, 2007). The implementation supports:


Table of Contents

  1. Two-Phase Flash Algorithm
  2. Stability Analysis
  3. Multi-Phase Flash Algorithm
  4. Electrolytes and Chemical Reactions
  5. References

1. Two-Phase Flash Algorithm

1.0 Complete TPflash Algorithm Flow

The following flowchart shows the complete two-phase flash algorithm as implemented in TPflash.run():

╔═══════════════════════════════════════════════════════════════════════════════╗
║                         TPflash.run() ALGORITHM FLOW                          ║
╚═══════════════════════════════════════════════════════════════════════════════╝

┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 1: INITIALIZATION                                                          │
├─────────────────────────────────────────────────────────────────────────────────┤
│  • system.init(0) - Initialize molar composition                                │
│  • system.init(1) - Calculate thermodynamic properties                          │
│  • Determine minimum Gibbs energy phase (gas or liquid)                         │
│  • Store reference: minGibsPhaseLogZ[i], minGibsLogFugCoef[i]                   │
│  • Handle single-component or single-phase systems → return early               │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 2: INITIAL K-VALUES (Wilson Equation)                                      │
├─────────────────────────────────────────────────────────────────────────────────┤
│  K-values are pre-initialized using Wilson's correlation:                       │
│                                                                                 │
│    Ki = (Pc,i / P) × exp[5.373(1 + ωi)(1 - Tc,i/T)]                            │
│                                                                                 │
│  • Solve Rachford-Rice equation to get initial β                                │
│  • Calculate initial x, y from material balance                                 │
│  • system.init(1) - Update fugacity coefficients                                │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 3: INITIAL SSI (3 iterations)                                              │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF β is at bounds (all liquid or all vapor):                                   │
│     • Reset β = 0.5                                                             │
│     • Run 1 sucsSubs() iteration                                                │
│                                                                                 │
│  FOR k = 0 to 2:  (exactly 3 preliminary SSI iterations)                        │
│     • IF β is in valid range (not at bounds):                                   │
│         - Run sucsSubs() iteration                                              │
│         - IF Gibbs energy decreased significantly → break early                 │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 4: QUICK STABILITY CHECK (TPD-based)                                       │
├─────────────────────────────────────────────────────────────────────────────────┤
│  Calculate tangent plane distances for both phases:                             │
│                                                                                 │
│    tpdy = Σ yi × [ln(φi^V) + ln(yi) - ln(zi) - ln(φi^ref)]                     │
│    tpdx = Σ xi × [ln(φi^L) + ln(xi) - ln(zi) - ln(φi^ref)]                     │
│    dgonRT = β × tpdy + (1-β) × tpdx                                            │
│                                                                                 │
│  IF dgonRT > 0 AND tpdx > 0 AND tpdy > 0:                                      │
│     → Single phase is stable                                                    │
│     → Run full stability analysis if checkStability() enabled                   │
│     → If multiPhaseCheck: delegate to TPmultiflash                              │
│     → return                                                                    │
│                                                                                 │
│  ELSE IF tpdx < 0 or tpdy < 0:                                                 │
│     → Re-estimate K-values from fugacity ratios                                 │
│     → Continue to main iteration loop                                           │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 5: PHASE TYPE DETERMINATION                                                │
├─────────────────────────────────────────────────────────────────────────────────┤
│  Compare Gibbs energy of phase 0 as GAS vs LIQUID:                              │
│     • Calculate G(gas), G(liquid)                                               │
│     • Set phase type to lower Gibbs energy option                               │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 6: MAIN ITERATION LOOP                                                     │
├─────────────────────────────────────────────────────────────────────────────────┤
│  Parameters:                                                                    │
│     • accelerateInterval = 7 (use DEM every 7 iterations)                       │
│     • newtonLimit = 20 (switch to Newton after 20 SSI iterations)               │
│     • maxNumberOfIterations = 50 (default)                                      │
│     • convergence tolerance = 1e-10                                             │
│                                                                                 │
│  DO (outer loop for chemical systems):                                          │
│  │   iterations = 0                                                             │
│  │   DO (inner loop):                                                           │
│  │   │   iterations++                                                           │
│  │   │                                                                          │
│  │   │   IF iterations < 20 (or chemical system, or no fugacity derivatives):   │
│  │   │   │   IF timeFromLastGibbsFail > 6 AND iterations % 7 == 0:              │
│  │   │   │       → accselerateSucsSubs()  [DEM acceleration]                    │
│  │   │   │   ELSE:                                                              │
│  │   │   │       → sucsSubs()  [standard SSI]                                   │
│  │   │   │                                                                      │
│  │   │   ELSE IF iterations >= 20:                                              │
│  │   │   │   IF iterations == 20:                                               │
│  │   │   │       → Create SysNewtonRhapsonTPflash solver                        │
│  │   │   │   → secondOrderSolver.solve()  [Newton-Raphson]                      │
│  │   │   │                                                                      │
│  │   │   Check Gibbs energy:                                                    │
│  │   │   IF G increased OR β at bounds:                                         │
│  │   │       → resetK() [restore previous K-values]                             │
│  │   │       → timeFromLastGibbsFail = 0                                        │
│  │   │   ELSE:                                                                  │
│  │   │       → setNewK() [store current K-values]                               │
│  │   │       → timeFromLastGibbsFail++                                          │
│  │   │                                                                          │
│  │   WHILE (deviation > 1e-10 AND iterations < 50)                              │
│  │                                                                              │
│  │   IF chemical system:                                                        │
│  │       → Solve chemical equilibrium in liquid phase                           │
│  │       → Calculate chemical equilibrium deviation                             │
│  │                                                                              │
│  WHILE (chemdev > 1e-6 AND totiter < 300) OR (chemical system AND totiter < 2)  │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 7: POST-PROCESSING                                                         │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF multiPhaseCheck enabled:                                                    │
│     → Delegate to TPmultiflash for stability analysis and phase split           │
│  ELSE:                                                                          │
│     → Final phase type check (gas vs liquid Gibbs energy)                       │
│                                                                                 │
│  IF solidCheck enabled:                                                         │
│     → Run solid phase flash                                                     │
│                                                                                 │
│  Remove phases with β < βmin                                                    │
│  Order phases by density                                                        │
│  Final system.init(1)                                                           │
│                                                                                 │
│  IF chemical system:                                                            │
│     → Final chemical equilibrium solve in aqueous/liquid phases                 │
└─────────────────────────────────────────────────────────────────────────────────┘

Key Algorithm Parameters

Parameter Value Description
phaseFractionMinimumLimit ~1e-12 Minimum allowed phase fraction
Initial SSI iterations 3 Preliminary iterations before stability check
accelerateInterval 7 Apply DEM every 7th iteration
newtonLimit 20 Switch to Newton-Raphson after 20 SSI iterations
maxNumberOfIterations 50 Maximum iterations per convergence loop
Convergence tolerance 1e-10 Deviation threshold for K-value convergence
Gibbs increase tolerance 1e-8 Relative increase that triggers K-reset

1.1 Problem Formulation

Consider a mixture of $N_c$ components with overall mole fractions $z_i$ at temperature $T$ and pressure $P$. The two-phase flash problem seeks the vapor fraction $\beta$ (also called $V$ for vapor) and the mole fractions in each phase ($x_i$ for liquid, $y_i$ for vapor) such that thermodynamic equilibrium is satisfied.

Equilibrium Conditions:

At equilibrium, the fugacity of each component must be equal in all phases:

$$f_i^V = f_i^L \quad \text{for } i = 1, 2, \ldots, N_c$$

This can be rewritten using fugacity coefficients $\phi_i$:

$$y_i \phi_i^V P = x_i \phi_i^L P$$

Defining the equilibrium ratio (K-factor):

$$K_i = \frac{y_i}{x_i} = \frac{\phi_i^L}{\phi_i^V}$$

Material Balance:

The overall material balance constrains the phase compositions:

$$z_i = \beta y_i + (1 - \beta) x_i$$

Combining with the K-factor definition:

$$y_i = \frac{K_i z_i}{1 + \beta(K_i - 1)}$$

$$x_i = \frac{z_i}{1 + \beta(K_i - 1)}$$

1.2 Rachford-Rice Equation

The vapor fraction $\beta$ is found by solving the Rachford-Rice equation, derived from the constraint $\sum_i y_i = \sum_i x_i = 1$:

$$g(\beta) = \sum_{i=1}^{N_c} \frac{z_i (K_i - 1)}{1 + \beta(K_i - 1)} = 0$$

Properties of $g(\beta)$:

Where the bounds ensure positive mole fractions:

$$\beta_{\min} = \max_i \left( \frac{K_i z_i - 1}{K_i - 1} \right) \quad \text{for } K_i > 1$$

$$\beta_{\max} = \min_i \left( \frac{1 - z_i}{1 - K_i} \right) \quad \text{for } K_i < 1$$

Derivative for Newton's Method:

$$\frac{dg}{d\beta} = -\sum_{i=1}^{N_c} \frac{z_i (K_i - 1)^2}{[1 + \beta(K_i - 1)]^2}$$

NeqSim Implementation

NeqSim implements two Rachford-Rice solvers:

  1. Michelsen (2001): Newton-Raphson with bisection fallback
  2. Nielsen (2023): Robust reformulation avoiding round-off errors

The method can be selected via:

RachfordRice.setMethod("Nielsen2023");  // or "Michelsen2001"

See RachfordRice.java for implementation details.

1.3 Successive Substitution

The standard approach to solve the two-phase flash is Successive Substitution Iteration (SSI), which iteratively updates K-factors until convergence.

Algorithm:

  1. Initialize K-factors using Wilson's correlation: $$K_i^{(0)} = \frac{P_{c,i}}{P} \exp\left[ 5.373(1 + \omega_i)\left(1 - \frac{T_{c,i}}{T}\right) \right]$$

  2. Solve Rachford-Rice to obtain $\beta$

  3. Calculate phase compositions using material balance: $$x_i = \frac{z_i}{1 + \beta(K_i - 1)}, \quad y_i = K_i x_i$$

  4. Update K-factors from fugacity coefficients: $$K_i^{(n+1)} = \frac{\phi_i^L(x, T, P)}{\phi_i^V(y, T, P)}$$

  5. Check convergence: $$\sum_i \left| \ln K_i^{(n+1)} - \ln K_i^{(n)} \right| < \epsilon$$

  6. If not converged, return to step 2.

NeqSim Implementation:

// From TPflash.java - sucsSubs() method
public void sucsSubs() {
    for (i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
        Kold = system.getPhase(0).getComponent(i).getK();
        system.getPhase(0).getComponent(i).setK(
            system.getPhase(1).getComponent(i).getFugacityCoefficient()
            / system.getPhase(0).getComponent(i).getFugacityCoefficient() * presdiff);
        deviation += Math.abs(Math.log(system.getPhase(0).getComponent(i).getK()) 
                            - Math.log(Kold));
    }

    RachfordRice rachfordRice = new RachfordRice();
    system.setBeta(rachfordRice.calcBeta(system.getKvector(), system.getzvector()));
    system.calc_x_y();
    system.init(1);
}

1.4 Accelerated Successive Substitution

Near the critical point or for systems with similar K-factors, standard SSI converges slowly. NeqSim implements the Dominant Eigenvalue Method (DEM) for acceleration.

Theory (Michelsen, 1982):

The convergence of SSI is limited by the dominant eigenvalue of the iteration matrix. The acceleration factor $\lambda$ is estimated from the last three iterates:

$$\lambda = \frac{\sum_i (\Delta \ln K_i^{(n)}) \cdot (\Delta \ln K_i^{(n-1)})}{\sum_i (\Delta \ln K_i^{(n-1)})^2}$$

Where $\Delta \ln K_i^{(n)} = \ln K_i^{(n)} - \ln K_i^{(n-1)}$.

Accelerated Update:

$$\ln K_i^{(n+1)} = \ln K_i^{(n)} + \frac{\lambda}{1 - \lambda} \Delta \ln K_i^{(n)}$$

NeqSim Implementation:

// From TPflash.java - accselerateSucsSubs() method
public void accselerateSucsSubs() {
    double prod1 = 0.0, prod2 = 0.0;
    for (i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
        prod1 += oldDeltalnK[i] * oldoldDeltalnK[i];
        prod2 += oldoldDeltalnK[i] * oldoldDeltalnK[i];
    }
    double lambda = prod1 / prod2;

    for (i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
        lnK[i] += lambda / (1.0 - lambda) * deltalnK[i];
        system.getPhase(0).getComponent(i).setK(Math.exp(lnK[i]));
    }
    // ... Rachford-Rice and update
}

1.5 Second-Order Newton-Raphson Method

For difficult systems or near-critical conditions, NeqSim employs a second-order Newton-Raphson method using fugacity derivatives.

Formulation:

Define the objective function vector $\mathbf{f}$ with components:

$$f_i = \ln \left( \frac{y_i \phi_i^V}{x_i \phi_i^L} \right) = 0$$

The solution is found by iterating:

$$\mathbf{u}^{(n+1)} = \mathbf{u}^{(n)} - \mathbf{J}^{-1} \mathbf{f}(\mathbf{u}^{(n)})$$

Where $\mathbf{u} = (\beta y_1, \beta y_2, \ldots, \beta y_{N_c})^T$ and the Jacobian $\mathbf{J}$ includes composition derivatives of fugacity coefficients:

$$J_{ij} = \frac{\partial f_i}{\partial u_j} = \frac{1}{\beta}\left(\frac{\delta_{ij}}{x_i} - 1 + \frac{\partial \ln \phi_i^V}{\partial x_j}\right) + \frac{1}{1-\beta}\left(\frac{\delta_{ij}}{y_i} - 1 + \frac{\partial \ln \phi_i^L}{\partial y_j}\right)$$

NeqSim Implementation:

See SysNewtonRhapsonTPflash.java for the full implementation.


2. Stability Analysis

2.1 Theoretical Foundation

A phase is thermodynamically stable if it has the lowest Gibbs energy among all possible phase configurations. The stability analysis determines whether a given phase will spontaneously split into multiple phases.

Gibbs Energy Criterion:

For a single-phase mixture with mole numbers $\mathbf{n}$, the mixture is stable if and only if the Gibbs energy $G(\mathbf{n})$ is at its global minimum. This is equivalent to requiring that no other phase can exist with lower chemical potential.

2.2 Tangent Plane Distance Function

Michelsen (1982) introduced the Tangent Plane Distance (TPD) function for stability analysis. Consider a reference phase with composition $\mathbf{z}$ and a trial phase with composition $\mathbf{w}$.

TPD Definition:

$$\text{TPD}(\mathbf{w}) = \sum_{i=1}^{N_c} w_i \left[ \mu_i(\mathbf{w}) - \mu_i(\mathbf{z}) \right]$$

In terms of fugacity coefficients:

$$\text{TPD}(\mathbf{w}) = \sum_{i=1}^{N_c} w_i \left[ \ln w_i + \ln \phi_i(\mathbf{w}) - d_i \right]$$

Where: $$d_i = \ln z_i + \ln \phi_i(\mathbf{z})$$

2.3 Stationary Points and Stability Criterion

Stationary Point Condition:

At a stationary point of TPD, the gradient is zero:

$$\frac{\partial \text{TPD}}{\partial w_i} = \ln w_i + \ln \phi_i(\mathbf{w}) - d_i + 1 = 0$$

Using the substitution $W_i = \exp(\ln w_i)$, define:

$$\ln W_i = d_i - \ln \phi_i(\mathbf{w})$$

Stability Test:

The reduced TPD at a stationary point is:

$$\text{tm} = 1 - \sum_{i=1}^{N_c} W_i$$

Criterion:

2.4 Solution Algorithm

NeqSim implements a hybrid algorithm combining successive substitution with Newton's method:

Phase 1: Successive Substitution

  1. Initialize trial phase with pure component or Wilson K-factor estimate: $$W_i^{(0)} = z_i \cdot K_i \quad \text{(vapor-like)} \quad \text{or} \quad W_i^{(0)} = z_i / K_i \quad \text{(liquid-like)}$$

  2. Iterate: $$\ln W_i^{(n+1)} = d_i - \ln \phi_i(\mathbf{w}^{(n)})$$

    Where $w_i = W_i / \sum_j W_j$ (normalized composition)

  3. Accelerate using DEM (every 7 iterations): $$\lambda = \frac{\sum_i \Delta(\ln W_i)^{(n)} \cdot \Delta(\ln W_i)^{(n-1)}}{\sum_i [\Delta(\ln W_i)^{(n-1)}]^2}$$ $$\ln W_i^{(n+1)} = \ln W_i^{(n)} + \frac{\lambda}{1-\lambda} \Delta(\ln W_i)^{(n)}$$

  4. Continue until $\sum_i |\ln W_i^{(n+1)} - \ln W_i^{(n)}| < \epsilon$

Phase 2: Second-Order Newton (if needed)

For difficult cases (iteration > 150), switch to Newton's method using the variable $\alpha_i = 2\sqrt{W_i}$:

Objective function: $$F_i = \sqrt{W_i} \left[ \ln W_i + \ln \phi_i(\mathbf{w}) - d_i \right]$$

Jacobian: $$\frac{\partial F_i}{\partial \alpha_j} = \delta_{ij} + \sqrt{W_i W_j} \frac{\partial \ln \phi_i}{\partial n_j}$$

Newton step: $$\boldsymbol{\alpha}^{(n+1)} = \boldsymbol{\alpha}^{(n)} - (\mathbf{I} + \mathbf{H})^{-1} \mathbf{F}$$

NeqSim Implementation:

// From TPmultiflash.java - stabilityAnalysis() method
// Successive substitution phase
for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
    logWi[i] = d[i] - clonedSystem.getPhase(1).getComponent(i).getLogFugacityCoefficient();
    Wi[j][i] = safeExp(logWi[i]);
}

// Check convergence and compute tm
tm[j] = 1.0;
for (int i = 0; i < system.getPhase(1).getNumberOfComponents(); i++) {
    tm[j] -= safeExp(logWi[i]);
}

// Phase is unstable if tm < -1e-8
if (tm[j] < -1e-8) {
    system.addPhase();  // Add new phase
    // Set composition from stationary point
}

Trivial Solution Check:

To avoid converging to trivial solutions (identical to existing phases):

$$\sum_i |w_i - x_i^{\text{existing}}| < \epsilon_{\text{trivial}}$$

If the trial composition is too close to an existing phase, it is rejected.


3. Multi-Phase Flash Algorithm

When system.setMultiPhaseCheck(true) is called, NeqSim uses the TPmultiflash class which extends the basic two-phase flash with comprehensive stability analysis and support for three or more equilibrium phases.

3.0 Complete TPmultiflash Algorithm Flow

╔═══════════════════════════════════════════════════════════════════════════════╗
║                       TPmultiflash.run() ALGORITHM FLOW                        ║
╚═══════════════════════════════════════════════════════════════════════════════╝

┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 1: ELECTROLYTE PREPROCESSING                                               │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF system is chemical/electrolyte:                                             │
│     • Store ionic component compositions: ionicZ[i] = z[i] for ions             │
│     • Temporarily set ion z = 1e-100 (remove from stability analysis)           │
│     • hasIons = true                                                            │
│     • system.init(1) - Recalculate properties without ions                      │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 2: PRIMARY STABILITY ANALYSIS                                              │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF doStabilityAnalysis == true:                                                │
│     → stabilityAnalysis()  [see detailed flow below]                            │
│     → Sets multiPhaseTest = true if unstable phase found                        │
│     → Adds new phase with composition from stationary point                     │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 3: HEURISTIC PHASE SEEDING                                                 │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF NOT multiPhaseTest AND seedAdditionalPhaseFromFeed():                       │
│     → Add gas phase seeded from feed composition                                │
│     → multiPhaseTest = true                                                     │
│                                                                                 │
│  IF seedHydrocarbonLiquidFromFeed():                                           │
│     → Add hydrocarbon liquid phase if conditions met                            │
│     → multiPhaseTest = true                                                     │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 4: ION RESTORATION (Electrolyte Systems)                                   │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF hasIons:                                                                    │
│     FOR each ionic component:                                                   │
│        • Restore z[i] = ionicZ[i] in all phases                                │
│        • IF phase is AQUEOUS: set x[i] = ionicZ[i]                             │
│        • ELSE: set x[i] = 1e-50 (ions only in aqueous)                         │
│     • Normalize all phases                                                      │
│     • system.init(1)                                                            │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 5: INITIAL CHEMICAL EQUILIBRIUM                                            │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF chemical system AND has aqueous phase:                                      │
│     → solveChemEq(aqueousPhaseNumber, 0)  [stoichiometric]                      │
│     → solveChemEq(aqueousPhaseNumber, 1)  [full Newton]                         │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 6: MULTIPHASE SPLIT CALCULATION                                            │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF multiPhaseTest == true:                                                     │
│     maxerr = 1e-12                                                              │
│                                                                                 │
│     DO (outer loop - chemical equilibrium):                                     │
│     │   iterOut++                                                               │
│     │                                                                           │
│     │   IF chemical system with aqueous phase:                                  │
│     │      → Solve chemical equilibrium                                         │
│     │      → Calculate chemical deviation                                       │
│     │                                                                           │
│     │   setDoubleArrays()  [allocate Q-function arrays]                        │
│     │   iterations = 0                                                          │
│     │                                                                           │
│     │   DO (inner loop - Q-function minimization):                              │
│     │   │   iterations++                                                        │
│     │   │   oldDiff = diff                                                      │
│     │   │   diff = solveBeta()  [Newton step on Q-function]                    │
│     │   │                                                                       │
│     │   │   IF iterations % 50 == 0:                                           │
│     │   │       maxerr *= 100  [relax tolerance]                               │
│     │   │                                                                       │
│     │   WHILE (diff > maxerr AND NOT removePhase                               │
│     │          AND (diff < oldDiff OR iterations < 50)                         │
│     │          AND iterations < 200)                                            │
│     │                                                                           │
│     WHILE (|chemdev| > 1e-10 AND iterOut < 100)                                │
│            OR (iterOut < 3 AND chemical AND aqueous)                           │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 7: AQUEOUS PHASE SEEDING (if water present but no aqueous)                │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF has water component AND NOT aqueousPhaseSeedAttempted                       │
│     AND multiPhaseCheck AND NOT hasAqueousPhase:                                │
│                                                                                 │
│     IF waterZ > 1e-6 AND numberOfPhases < 3:                                   │
│        → Add new phase                                                          │
│        → Set phase type = AQUEOUS                                               │
│        → Initialize with water-rich composition                                 │
│        → Set β = max(1e-5, 10 × βmin)                                          │
│        → multiPhaseTest = true                                                  │
│        → aqueousPhaseSeedAttempted = true                                       │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 8: SINGLE AQUEOUS PHASE ENFORCEMENT (Electrolytes)                         │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF chemical system:                                                            │
│     → ensureSingleAqueousPhase()                                               │
│     → Reclassify extra "aqueous" phases as OIL                                  │
│     → Move ions to the true aqueous phase                                       │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 9: PHASE CLEANUP                                                           │
├─────────────────────────────────────────────────────────────────────────────────┤
│  Remove negligible phases:                                                      │
│     FOR each phase:                                                             │
│        IF β < 1.1 × βmin:                                                      │
│           → removePhaseKeepTotalComposition()                                   │
│           → hasRemovedPhase = true                                              │
│                                                                                 │
│  Detect trivial solutions (phases with same density):                           │
│     FOR each pair of phases (i, j):                                             │
│        IF |ρi - ρj| < 1.1e-5:                                                  │
│           → Remove phase j                                                      │
│           → hasRemovedPhase = true                                              │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 10: RECURSIVE STABILITY CHECK                                              │
├─────────────────────────────────────────────────────────────────────────────────┤
│  IF hasRemovedPhase AND NOT secondTime:                                         │
│     → secondTime = true                                                         │
│     → stabilityAnalysis3()  [re-check stability]                               │
│     → run()  [RECURSIVE CALL - restart algorithm]                              │
└─────────────────────────────────────────────────────────────────────────────────┘

3.0.1 Stability Analysis Detailed Flow

The stabilityAnalysis() method tests multiple trial phases to find instabilities:

╔═══════════════════════════════════════════════════════════════════════════════╗
║                    stabilityAnalysis() DETAILED FLOW                           ║
╚═══════════════════════════════════════════════════════════════════════════════╝

┌─────────────────────────────────────────────────────────────────────────────────┐
│ INITIALIZATION                                                                  │
├─────────────────────────────────────────────────────────────────────────────────┤
│  • Clone system for trial phase calculations                                    │
│  • Calculate reference chemical potentials:                                     │
│       d[k] = ln(x[k]) + ln(φ[k])   for each component k                        │
│  • Initialize logWi[j] = 1.0 for components with z > 1e-100                    │
│  • Find heaviest and lightest hydrocarbon components                           │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ COMPONENT SELECTION                                                             │
├─────────────────────────────────────────────────────────────────────────────────┤
│  Components to test (loop j from Nc-1 down to 0):                              │
│     SKIP if:                                                                    │
│        • x[j] < 1e-100  (negligible)                                           │
│        • Component is ionic                                                     │
│        • Hydrocarbon but NOT heaviest AND NOT lightest                         │
│                                                                                 │
│  This typically tests: water, CO2, H2S, heaviest HC, lightest HC, etc.         │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
   ╔═════════════════════════════════════════════════════════════════════════════╗
   ║  FOR EACH SELECTED COMPONENT j:                                             ║
   ╚═════════════════════════════════════════════════════════════════════════════╝
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ TRIAL PHASE INITIALIZATION                                                      │
├─────────────────────────────────────────────────────────────────────────────────┤
│  Initialize trial phase composition (nearly pure component j):                  │
│     w[i] = 1.0      if i == j                                                  │
│     w[i] = 1e-12    if i ≠ j  (trace amounts)                                  │
│     w[i] = 0        if z[i] < 1e-100                                           │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ SSI LOOP (up to 150 iterations)                                                 │
├─────────────────────────────────────────────────────────────────────────────────┤
│  Parameters:                                                                    │
│     • maxsucssubiter = 150  (max SSI iterations)                               │
│     • maxiter = 200  (absolute max with Newton)                                 │
│     • convergence = 1e-9                                                        │
│                                                                                 │
│  iter = 0                                                                       │
│  DO:                                                                            │
│  │   iter++                                                                     │
│  │   errOld = err                                                               │
│  │   err = 0                                                                    │
│  │                                                                              │
│  │   IF iter <= 150 (SSI phase):                                               │
│  │   │                                                                          │
│  │   │   IF iter % 7 == 0 AND useaccsubst (DEM acceleration):                  │
│  │   │   │   Calculate acceleration factor λ:                                  │
│  │   │   │      λ = Σ(ΔlnW^n × ΔlnW^n-1 × (ΔlnW^n-1)²)                        │
│  │   │   │          / Σ(ΔlnW^n-1)⁴                                            │
│  │   │   │   Apply acceleration:                                               │
│  │   │   │      lnW[i] += λ/(1-λ) × ΔlnW[i]                                   │
│  │   │   │                                                                      │
│  │   │   ELSE (standard SSI):                                                  │
│  │   │   │   Store old values for acceleration                                 │
│  │   │   │   Calculate fugacity coefficients: clonedSystem.init(1,1)           │
│  │   │   │   Update:                                                           │
│  │   │   │      lnW[i] = d[i] - ln(φ[i])                                       │
│  │   │   │      W[j][i] = exp(lnW[i])                                          │
│  │   │   │   err += |lnW[i] - lnW_old[i]|                                      │
│  │   │   │                                                                      │
│  │   │   IF err > errOld after 2 iters:                                        │
│  │   │      useaccsubst = false  (disable acceleration)                        │
│  │   │                                                                          │
│  │   ELSE (iter > 150 - Newton phase):                                         │
│  │   │   clonedSystem.init(3,1)  [compute fugacity derivatives]                │
│  │   │   α[i] = 2√(W[j][i])                                                    │
│  │   │                                                                          │
│  │   │   Build objective function F and Jacobian J:                            │
│  │   │      F[i] = √W[i] × (lnW[i] + ln(φ[i]) - d[i])                         │
│  │   │      J[i,k] = δ[i,k] + √(W[i]×W[k]) × ∂ln(φ[i])/∂n[k]                  │
│  │   │                                                                          │
│  │   │   Solve Newton step:                                                    │
│  │   │      Δα = -(I + J)⁻¹ × F                                               │
│  │   │      (with regularization fallback if singular)                         │
│  │   │                                                                          │
│  │   │   Update:                                                               │
│  │   │      α_new = α + Δα                                                     │
│  │   │      W[j][i] = (α_new/2)²                                              │
│  │   │      lnW[i] = ln(W[j][i])                                               │
│  │   │                                                                          │
│  │   Normalize and update trial phase composition:                             │
│  │      sumw = Σ exp(lnW[i])                                                   │
│  │      x[i] = exp(lnW[i]) / sumw                                              │
│  │                                                                              │
│  WHILE (|err| > 1e-9 OR err > errOld) AND iter < 200                           │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ CONVERGENCE CHECK AND tm CALCULATION                                            │
├─────────────────────────────────────────────────────────────────────────────────┤
│  Calculate tangent plane distance:                                              │
│     tm[j] = 1 - Σ exp(lnW[i])                                                  │
│                                                                                 │
│  Check for trivial solution:                                                    │
│     trivialCheck0 = Σ |w[i] - x_phase0[i]|                                     │
│     trivialCheck1 = Σ |w[i] - x_phase1[i]|                                     │
│     IF trivialCheck0 < 1e-4 OR trivialCheck1 < 1e-4:                           │
│        tm[j] = 10.0  (mark as stable - trivial solution)                       │
│                                                                                 │
│  IF tm[j] < -1e-8:                                                             │
│     → UNSTABLE! Break loop, proceed to phase addition                          │
└─────────────────────────────────────────────────────────────────────────────────┘
   ║                                                                              ║
   ║  END FOR EACH COMPONENT                                                      ║
   ╚══════════════════════════════════════════════════════════════════════════════╝
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ PHASE ADDITION (if instability found)                                          │
├─────────────────────────────────────────────────────────────────────────────────┤
│  FOR k = Nc-1 down to 0:                                                       │
│     IF tm[k] < -1e-8 AND NOT NaN:                                              │
│        • system.addPhase()                                                      │
│        • Set new phase composition = x[k][i] (from stationary point)           │
│        • Normalize new phase                                                    │
│        • multiPhaseTest = true                                                  │
│        • Set initial β = z[destabilizing_component]                            │
│        • system.init(1)                                                         │
│        • system.normalizeBeta()                                                 │
│        → RETURN (exit stability analysis)                                       │
│                                                                                 │
│  IF no instability found:                                                       │
│     → system.normalizeBeta()                                                    │
│     → RETURN (system is stable)                                                 │
└─────────────────────────────────────────────────────────────────────────────────┘

Stability Analysis Key Parameters

Parameter Value Description
maxsucssubiter 150 Maximum SSI iterations before Newton
maxiter 200 Absolute maximum iterations
DEM interval 7 Apply acceleration every 7th iteration
Convergence tolerance 1e-9 Error threshold for lnW convergence
Instability threshold -1e-8 tm value indicating phase split
Trivial solution threshold 1e-4 Composition difference to detect trivial

3.0.2 Multiphase Stability Analysis Additional Details

The multiphase stability analysis in NeqSim is more sophisticated than the basic two-phase version. It systematically tests multiple trial phase compositions to ensure no additional phases can form.

3.0.2.1 Trial Phase Selection Strategy

Instead of using only Wilson K-factor estimates, the multiphase stability analysis tests component-seeded trial phases:

  1. Pure component initialization: For each component $j$, create a trial phase with: $$w_i^{(0)} = \begin{cases} 1.0 & \text{if } i = j \ 10^{-12} & \text{if } i \neq j \end{cases}$$

  2. Hydrocarbon optimization: To reduce computational cost, only two hydrocarbon components are tested:

    • The heaviest hydrocarbon (highest molar mass)
    • The lightest hydrocarbon (lowest molar mass)

    This captures both potential liquid-liquid separation (heavy components) and vapor formation (light components).

  3. Non-hydrocarbon components: All non-hydrocarbon components (water, CO₂, H₂S, etc.) are tested individually.

  4. Ion exclusion: Components with ionic charge are excluded from stability testing since they cannot exist in separate non-aqueous phases.

// From TPmultiflash.java - component selection logic
for (int j = system.getPhase(0).getNumberOfComponents() - 1; j >= 0; j--) {
    // Skip negligible components
    if (minimumGibbsEnergySystem.getPhase(0).getComponent(j).getx() < 1e-100)
        continue;
    // Skip ions
    if (minimumGibbsEnergySystem.getPhase(0).getComponent(j).getIonicCharge() != 0)
        continue;
    // For hydrocarbons, only test heaviest and lightest
    if (minimumGibbsEnergySystem.getPhase(0).getComponent(j).isHydrocarbon()
        && j != hydrocarbonTestCompNumb && j != lightTestCompNumb)
        continue;

    // Perform stability test for this component...
}

3.0.2.2 Reference Phase Chemical Potential

The reference chemical potential $d_i$ is computed from the current phase (typically the phase with lowest Gibbs energy):

$$d_i = \ln x_i^{\text{ref}} + \ln \phi_i^{\text{ref}}$$

Where superscript "ref" denotes the reference phase. This is computed once before the iteration loop:

for (int k = 0; k < system.getPhase(0).getNumberOfComponents(); k++) {
    if (system.getPhase(0).getComponent(k).getx() > 1e-100) {
        d[k] = Math.log(system.getPhase(0).getComponent(k).getx())
             + system.getPhase(0).getComponent(k).getLogFugacityCoefficient();
    }
}

3.0.2.3 Successive Substitution with Normalization

The multiphase stability analysis maintains both unnormalized ($W_i$) and normalized ($w_i$) compositions:

Iteration update: $$\ln W_i^{(n+1)} = d_i - \ln \phi_i(\mathbf{w}^{(n)})$$

Normalization for fugacity calculation: $$w_i = \frac{W_i}{\sum_j W_j}$$

This is important because fugacity coefficients must be evaluated at normalized compositions, but the TPD criterion uses the unnormalized $W_i$ values.

// Compute sum for normalization
sumw[j] = 0;
for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
    sumw[j] += safeExp(logWi[i]);
}

// Set normalized composition for fugacity calculation
for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
    clonedSystem.get(0).getPhase(1).getComponent(i).setx(safeExp(logWi[i]) / sumw[j]);
}

3.0.2.4 Convergence Acceleration (DEM)

Every 7 iterations, the Dominant Eigenvalue Method accelerates convergence:

$$\lambda = \frac{\sum_i (\Delta \ln W_i^{(n)}) \cdot (\Delta \ln W_i^{(n-1)}) \cdot (\Delta \ln W_i^{(n-1)})^2}{\sum_i (\Delta \ln W_i^{(n-1)})^4}$$

$$\ln W_i^{\text{acc}} = \ln W_i^{(n)} + \frac{\lambda}{1 - \lambda} \Delta \ln W_i^{(n)}$$

Acceleration is disabled if the error increases (indicating divergence):

if (iter > 2 && err > errOld) {
    useaccsubst = false;
}

3.0.2.5 Second-Order Newton Method (Optional)

After 150 successive substitution iterations, NeqSim switches to a second-order Newton method using the substitution $\alpha_i = 2\sqrt{W_i}$:

Objective function: $$F_i = \sqrt{W_i} \left[ \ln W_i + \ln \phi_i(\mathbf{w}) - d_i \right]$$

Jacobian with fugacity derivatives: $$\frac{\partial F_i}{\partial \alpha_k} = \delta_{ik} + \sqrt{W_i W_k} \cdot \frac{\partial \ln \phi_i}{\partial n_k}$$

Newton update with regularization: $$\boldsymbol{\alpha}^{(n+1)} = \boldsymbol{\alpha}^{(n)} - (\mathbf{I} + \mathbf{J})^{-1} \mathbf{F}$$

The implementation includes robust fallbacks for singular matrices:

  1. Try with small regularization ($10^{-6} \cdot \mathbf{I}$)
  2. Try with larger regularization ($0.5 \cdot \mathbf{I}$)
  3. Use pseudo-inverse as last resort

3.0.2.6 Trivial Solution Detection

After convergence, the algorithm checks if the solution is trivial (identical to an existing phase):

$$\text{trivialCheck}_k = \sum_i |w_i - x_i^{(k)}|$$

If $\text{trivialCheck}_k < 10^{-4}$ for any existing phase $k$, the stationary point is rejected:

double xTrivialCheck0 = 0.0;
double xTrivialCheck1 = 0.0;

for (int i = 0; i < system.getPhase(1).getNumberOfComponents(); i++) {
    xTrivialCheck0 += Math.abs(x[j][i] - system.getPhase(0).getComponent(i).getx());
    xTrivialCheck1 += Math.abs(x[j][i] - system.getPhase(1).getComponent(i).getx());
}

if (Math.abs(xTrivialCheck0) < 1e-4 || Math.abs(xTrivialCheck1) < 1e-4) {
    tm[j] = 10.0;  // Mark as stable (trivial solution)
}

3.0.2.7 Phase Addition from Unstable Stationary Point

When an unstable stationary point is found ($\text{tm} < -10^{-8}$):

  1. Add new phase to the system
  2. Set composition from the converged stationary point
  3. Set initial phase fraction proportional to the destabilizing component's feed fraction
  4. Normalize phase fractions
if (tm[k] < -1e-8 && !(Double.isNaN(tm[k]))) {
    system.addPhase();
    unstabcomp = k;

    // Set composition from stationary point
    for (int i = 0; i < system.getPhase(1).getNumberOfComponents(); i++) {
        system.getPhase(system.getNumberOfPhases() - 1).getComponent(i).setx(x[k][i]);
    }
    system.getPhases()[system.getNumberOfPhases() - 1].normalize();

    // Set initial phase fraction
    multiPhaseTest = true;
    system.setBeta(system.getNumberOfPhases() - 1,
                   system.getPhase(0).getComponent(unstabcomp).getz());
    system.init(1);
    system.normalizeBeta();
    return;  // Exit stability analysis, proceed to phase split
}

3.0.2.8 Multiple Stability Analysis Variants

NeqSim implements three stability analysis methods in TPmultiflash:

Method Description Use Case
stabilityAnalysis() Single cloned system, optimized for performance Primary method
stabilityAnalysis2() Multiple cloned systems (one per component) Alternative for difficult cases
stabilityAnalysis3() Re-run after phase removal Post-processing verification

The main run() method orchestrates these:

if (doStabilityAnalysis) {
    stabilityAnalysis();  // Primary stability check
}

// ... phase equilibrium calculation ...

if (hasRemovedPhase && !secondTime) {
    secondTime = true;
    stabilityAnalysis3();  // Re-check after phase removal
    run();  // Recursive call
}

3.0.3 Enhanced Stability Analysis

When system.setEnhancedMultiPhaseCheck(true) is enabled, an additional stability analysis is performed using Wilson K-value based initialization. This is particularly useful for detecting liquid-liquid equilibria in complex mixtures such as sour gas systems (methane/CO₂/H₂S).

Motivation

The standard stability analysis may fail to detect additional phases in certain systems because:

  1. Pure component initialization may not provide good starting points for LLE
  2. Wilson K-values are vapor-pressure based and work well for VLE but not LLE
  3. Some systems require testing both vapor-like and liquid-like trial phases
  4. Polarity-driven LLE requires different initialization strategies

Enhanced Algorithm

The enhanced stability analysis (stabilityAnalysisEnhanced()) addresses these limitations:

╔═══════════════════════════════════════════════════════════════════════════════╗
║                stabilityAnalysisEnhanced() ALGORITHM FLOW                      ║
╚═══════════════════════════════════════════════════════════════════════════════╝

┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 1: WILSON K-VALUE CALCULATION                                              │
├─────────────────────────────────────────────────────────────────────────────────┤
│  FOR each valid component i (z > 1e-100, not ionic):                           │
│     K[i] = (Pc[i] / P) × exp[5.373 × (1 + ω[i]) × (1 - Tc[i]/T)]              │
│     log(K[i]) = ln(K[i])                                                        │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 2: PRE-CALCULATE REFERENCE FUGACITIES                                      │
├─────────────────────────────────────────────────────────────────────────────────┤
│  FOR each existing phase p = 0 to numPhases-1:                                 │
│     FOR each component k:                                                       │
│        d_ref[p][k] = ln(x[p][k]) + ln(φ[p][k])                                 │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
   ╔═════════════════════════════════════════════════════════════════════════════╗
   ║  FOR EACH EXISTING PHASE AS REFERENCE (p = 0 to numPhases-1):               ║
   ╠═════════════════════════════════════════════════════════════════════════════╣
   ║  FOR EACH TRIAL TYPE (vapor-like, liquid-like, LLE):                        ║
   ╚═════════════════════════════════════════════════════════════════════════════╝
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 3: TRIAL PHASE INITIALIZATION                                              │
├─────────────────────────────────────────────────────────────────────────────────┤
│  trialType = 1:  VAPOR-LIKE (VLE gas detection)                                │
│     W[i] = exp(ln(K[i]))  → volatile components enriched                       │
│                                                                                 │
│  trialType = -1: LIQUID-LIKE (VLE liquid detection)                            │
│     W[i] = exp(-ln(K[i])) = 1/K[i]  → heavy components enriched               │
│                                                                                 │
│  trialType = 0:  LLE TRIAL (polarity-based perturbation)                       │
│     perturbFactor = 2.0 if ω[i] > 0.15 (polar), else 0.5 (non-polar)          │
│     W[i] = z[i] × perturbFactor                                                │
│                                                                                 │
│  Note: LLE uses acentric factor as polarity proxy since Wilson K-values       │
│  are derived from vapor pressure and don't capture activity coefficient-       │
│  driven liquid-liquid splits.                                                   │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 4: SSI LOOP WITH WEGSTEIN ACCELERATION                                     │
├─────────────────────────────────────────────────────────────────────────────────┤
│  FOR iter = 1 to maxIter (300):                                                │
│     Calculate fugacity coefficients at normalized w[i]                         │
│     Update: ln(W[i]) = d_ref[p] - ln(φ[i])                                     │
│                                                                                 │
│     IF iter % 5 == 0 AND iter > 5 (Wegstein acceleration):                     │
│        λ = Σ(Δln(W)^n × Δln(W)^n-1) / Σ(Δln(W)^n-1)²                          │
│        λ = clamp(λ, -0.5, 0.9)                                                 │
│        ln(W[i]) += λ/(1-λ) × Δln(W[i])                                         │
│                                                                                 │
│     Check convergence: err = Σ|ln(W[i])^n - ln(W[i])^n-1|                      │
│     IF err < 1e-10: BREAK                                                       │
└─────────────────────────────────────────────────────────────────────────────────┘
                                        ↓
┌─────────────────────────────────────────────────────────────────────────────────┐
│ STEP 5: STABILITY CHECK AND PHASE ADDITION                                      │
├─────────────────────────────────────────────────────────────────────────────────┤
│  tm = 1 - Σ W[i]                                                               │
│                                                                                 │
│  Check for trivial solution (composition too close to existing phase)          │
│                                                                                 │
│  IF tm < -1e-8 AND NOT trivial:                                                │
│     → Add new phase with composition w[i] = W[i]/ΣW[j]                         │
│     → multiPhaseTest = true                                                     │
│     → RETURN                                                                    │
└─────────────────────────────────────────────────────────────────────────────────┘

Key Differences from Standard Stability Analysis

Feature Standard Analysis Enhanced Analysis
Initial guess Pure component Wilson K-values
Trial types Single Vapor-like, Liquid-like, LLE
Reference phase Phase 0 only All existing phases
LLE detection Component-based Polarity perturbation
Acceleration DEM every 7 iterations Wegstein every 5 iterations
Hydrocarbon filtering Yes (only heaviest/lightest) No (all components tested)

When to Enable Enhanced Stability Analysis

Enable setEnhancedMultiPhaseCheck(true) for:

Example usage:

SystemInterface fluid = new SystemPrEos(210.0, 55.0);  // Low T, moderate P
fluid.addComponent("methane", 49.88);
fluid.addComponent("CO2", 9.87);
fluid.addComponent("H2S", 40.22);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);
fluid.setEnhancedMultiPhaseCheck(true);  // Enable enhanced detection

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
// May find vapor + CO2-rich liquid + H2S-rich liquid

Note: Enhanced stability analysis adds computational overhead. For simple VLE systems, the standard analysis is sufficient and more efficient.

3.1 Multiphase Equilibrium Formulation

For systems with $N_p$ phases, the equilibrium conditions become:

$$f_i^{(1)} = f_i^{(2)} = \cdots = f_i^{(N_p)} \quad \text{for } i = 1, \ldots, N_c$$

Material Balance:

$$z_i = \sum_{k=1}^{N_p} \beta_k x_i^{(k)}$$

With the constraint:

$$\sum_{k=1}^{N_p} \beta_k = 1$$

3.2 Q-Function Minimization

Michelsen (1982) introduced the Q-function for multiphase flash:

$$Q = \sum_{k=1}^{N_p} \beta_k - \sum_{i=1}^{N_c} z_i \ln E_i$$

Where: $$E_i = \sum_{k=1}^{N_p} \frac{\beta_k}{\phi_i^{(k)}}$$

Gradient: $$\frac{\partial Q}{\partial \beta_k} = 1 - \sum_{i=1}^{N_c} \frac{z_i}{E_i \phi_i^{(k)}}$$

Hessian: $$\frac{\partial^2 Q}{\partial \beta_k \partial \beta_l} = \sum_{i=1}^{N_c} \frac{z_i}{E_i^2 \phi_i^{(k)} \phi_i^{(l)}}$$

Newton Update:

$$\boldsymbol{\beta}^{(n+1)} = \boldsymbol{\beta}^{(n)} - \mathbf{H}^{-1} \nabla Q$$

Phase Compositions:

$$x_i^{(k)} = \frac{z_i}{E_i \phi_i^{(k)}}$$

NeqSim Implementation:

// From TPmultiflash.java - calcQ() and solveBeta()
public double calcQ() {
    this.calcE();  // Calculate E_i

    // Compute gradient dQ/dβ
    for (int k = 0; k < system.getNumberOfPhases(); k++) {
        dQdbeta[k][0] = 1.0;
        for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
            dQdbeta[k][0] -= multTerm[i] / system.getPhase(k).getComponent(i).getFugacityCoefficient();
        }
    }

    // Compute Hessian Q_matrix
    for (int i = 0; i < system.getNumberOfPhases(); i++) {
        for (int j = 0; j < system.getNumberOfPhases(); j++) {
            Qmatrix[i][j] = 0.0;
            for (int k = 0; k < system.getPhase(0).getNumberOfComponents(); k++) {
                Qmatrix[i][j] += multTerm2[k] / (phi_j[k] * phi_i[k]);
            }
        }
    }
    return Q;
}

3.3 Phase Addition and Removal

Phase Addition:

When stability analysis indicates an unstable phase (tm < 0):

  1. A new phase is added to the system
  2. Initial composition is taken from the unstable stationary point
  3. Initial phase fraction is set to a small value (~0.001)

Phase Removal:

Phases with negligible fractions ($\beta_k < \beta_{\min}$) are removed:

// From TPmultiflash.java - run()
for (int i = 0; i < system.getNumberOfPhases(); i++) {
    if (system.getBeta(i) < 1.1 * phaseFractionMinimumLimit) {
        system.removePhaseKeepTotalComposition(i);
    }
}

Trivial Solution Detection:

Phases with nearly identical densities are merged:

if (Math.abs(system.getPhase(i).getDensity() - system.getPhase(j).getDensity()) < 1.1e-5) {
    system.removePhaseKeepTotalComposition(j);
}

3.4 Complete Multiphase Flash Workflow

The complete workflow in TPmultiflash.run() is:

┌─────────────────────────────────────────────────────────────┐
│  1. PREPROCESSING                                           │
│     - For electrolyte systems: temporarily remove ions      │
│     - Store ionic compositions for later restoration        │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  2. STABILITY ANALYSIS                                      │
│     - Test component-seeded trial phases                    │
│     - Use SSI + DEM acceleration + Newton fallback          │
│     - If tm < -1e-8: add new phase, set multiPhaseTest=true │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  3. ADDITIONAL PHASE SEEDING (if stability didn't add)      │
│     - seedAdditionalPhaseFromFeed(): gas phase seeding      │
│     - seedHydrocarbonLiquidFromFeed(): oil phase seeding    │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  4. ION RESTORATION (electrolyte systems)                   │
│     - Restore ions to aqueous phase(s) only                 │
│     - Set ion x = 1e-50 in non-aqueous phases               │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  5. CHEMICAL EQUILIBRIUM (if applicable)                    │
│     - Solve chemical equilibrium in aqueous phase           │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  6. PHASE SPLIT CALCULATION (if multiPhaseTest = true)      │
│     - Q-function minimization with Newton's method          │
│     - Nested iteration with chemical equilibrium            │
│     - Continue until convergence                            │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  7. AQUEOUS PHASE SEEDING (if water present but no aq)      │
│     - Add aqueous phase seeded with water                   │
│     - Re-run phase split if phase added                     │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  8. PHASE CLEANUP                                           │
│     - Remove phases with β < βmin                           │
│     - Detect and merge trivial solutions (same density)     │
│     - ensureSingleAqueousPhase() for electrolytes           │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  9. POST-REMOVAL STABILITY CHECK                            │
│     - If phase removed: run stabilityAnalysis3()            │
│     - Recursive call to run() if new phase found            │
└─────────────────────────────────────────────────────────────┘

3.5 Phase Seeding Strategies

Beyond stability analysis, NeqSim uses heuristic phase seeding to improve convergence:

3.5.1 Gas Phase Seeding from Feed

When an aqueous phase exists without a gas phase, seed a gas phase:

private boolean seedAdditionalPhaseFromFeed() {
    // Only if multiphase check enabled and < 3 phases
    if (!system.doMultiPhaseCheck() || system.getNumberOfPhases() >= 3)
        return false;

    // Need aqueous phase but no gas phase
    boolean hasAqueous = false, hasGas = false;
    for (int phase = 0; phase < system.getNumberOfPhases(); phase++) {
        if (type == PhaseType.GAS) hasGas = true;
        if (type == PhaseType.AQUEOUS) hasAqueous = true;
    }
    if (!hasAqueous || hasGas) return false;

    // Seed gas phase with feed composition
    system.addPhase();
    system.setPhaseType(phaseIndex, PhaseType.GAS);
    for (int comp = 0; comp < ncomp; comp++) {
        system.getPhase(phaseIndex).getComponent(comp).setx(z[comp]);
    }
    system.setBeta(phaseIndex, 1e-3);
    return true;
}

3.5.2 Aqueous Phase Seeding

When water is present but no aqueous phase exists:

if (waterZ > 1.0e-6 && !system.hasPhaseType(PhaseType.AQUEOUS)) {
    system.addPhase();
    system.setPhaseType(aquPhaseIndex, PhaseType.AQUEOUS);

    // Initialize with water-concentrated composition
    for (int comp = 0; comp < ncomp; comp++) {
        double x = 1.0e-16;
        if (comp == waterComponentIndex) {
            x = Math.max(waterZ, 1.0e-12);  // Concentrate water
        } else if (!isHydrocarbon(comp) && !isInert(comp)) {
            x = Math.min(z[comp] * 1.0e-2, 1.0e-8);  // Trace aqueous components
        }
        system.getPhase(aquPhaseIndex).getComponent(comp).setx(x);
    }
    system.setBeta(aquPhaseIndex, 1e-5);
}

4. Electrolytes and Chemical Reactions

4.1 Chemical Equilibrium Coupling

For systems with chemical reactions (electrolytes, acid-base equilibria), the flash calculation must be coupled with chemical equilibrium. NeqSim solves this as a nested iteration:

Outer Loop: Phase equilibrium (flash) Inner Loop: Chemical equilibrium within each phase

Chemical Equilibrium Condition:

For a reaction $\sum_i \nu_i A_i = 0$:

$$\sum_i \nu_i \mu_i = 0$$

Or equivalently:

$$\prod_i a_i^{\nu_i} = K_{eq}(T)$$

Where $a_i$ is the activity and $K_{eq}$ is the equilibrium constant.

NeqSim Implementation:

// From TPflash.java - chemical equilibrium integration
if (system.isChemicalSystem()) {
    for (int phaseNum = 0; phaseNum < system.getNumberOfPhases(); phaseNum++) {
        if ("aqueous".equalsIgnoreCase(phaseType)) {
            system.getChemicalReactionOperations().solveChemEq(phaseNum, 0);
            system.getChemicalReactionOperations().solveChemEq(phaseNum, 1);
        }
    }
}

The chemical equilibrium solver uses:

4.2 Ion Handling in Stability Analysis

Ionic species present special challenges for stability analysis because they cannot exist in non-aqueous phases. NeqSim handles this by:

  1. Temporarily removing ions before stability analysis:

    if (system.isChemicalSystem()) {
        ionicZ = new double[system.getPhase(0).getNumberOfComponents()];
        for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
            if (system.getPhase(0).getComponent(i).getIonicCharge() != 0) {
                ionicZ[i] = system.getPhase(0).getComponent(i).getz();
                // Temporarily set to near-zero
                system.getPhase(phase).getComponent(i).setz(1e-100);
            }
        }
    }
    
  2. Running stability analysis on the neutral system

  3. Restoring ions to aqueous phases after phase configuration is determined:

    if (hasIons && ionicZ != null) {
        for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
            if (system.getPhase(0).getComponent(i).getIonicCharge() != 0) {
                // Restore z values, put ions only in aqueous phase
                if (system.getPhase(phase).getType() == PhaseType.AQUEOUS) {
                    system.getPhase(phase).getComponent(i).setx(ionicZ[i]);
                } else {
                    system.getPhase(phase).getComponent(i).setx(1e-50);
                }
            }
        }
    }
    

4.3 Aqueous Phase Management

For electrolyte systems, NeqSim ensures proper aqueous phase handling:

Single Aqueous Phase Constraint:

The system ensures only one aqueous phase exists, containing all ionic species:

private void ensureSingleAqueousPhase() {
    if (!system.isChemicalSystem() || system.getNumberOfPhases() < 2) {
        return;
    }

    // Find phase with highest aqueous component content
    int bestAqueousPhase = -1;
    double maxAqueousContent = 0.0;

    for (int phase = 0; phase < system.getNumberOfPhases(); phase++) {
        double aqueousContent = 0.0;
        for (int comp = 0; comp < ncomp; comp++) {
            // Count water, glycols, alcohols, and ions
            if (isAqueousComponent(component)) {
                aqueousContent += component.getx();
            }
        }
        if (aqueousContent > maxAqueousContent) {
            maxAqueousContent = aqueousContent;
            bestAqueousPhase = phase;
        }
    }

    // Reclassify other phases as OIL
}

Aqueous Phase Seeding:

When water is present but no aqueous phase exists, a seed aqueous phase can be created:

if (waterZ > 1.0e-6 && !system.hasPhaseType(PhaseType.AQUEOUS)) {
    system.addPhase();
    system.setPhaseType(aquPhaseIndex, PhaseType.AQUEOUS);
    // Initialize with water-rich composition
}

5. References

Primary References

  1. Michelsen, M. L. (1982). "The isothermal flash problem. Part I. Stability." Fluid Phase Equilibria, 9(1), 1-19.

    • Introduces the tangent plane distance criterion for stability analysis.
  2. Michelsen, M. L. (1982). "The isothermal flash problem. Part II. Phase-split calculation." Fluid Phase Equilibria, 9(1), 21-40.

    • Presents the Q-function minimization for multiphase flash.
  3. Michelsen, M. L. & Mollerup, J. M. (2007). Thermodynamic Models: Fundamentals and Computational Aspects, 2nd Ed. Tie-Line Publications.

    • Comprehensive textbook covering all aspects of phase equilibrium calculations.

Rachford-Rice Equation

  1. Rachford, H. H. & Rice, J. D. (1952). "Procedure for use of electronic digital computers in calculating flash vaporization hydrocarbon equilibrium." Journal of Petroleum Technology, 4(10), 19-3.

  2. Nielsen, R. F. & Lia, A. (2023). "Avoiding round-off error in the Rachford–Rice equation." Fluid Phase Equilibria, 571, 113801.

    • Robust reformulation used in the Nielsen2023 solver.

Successive Substitution and Acceleration

  1. Mehra, R. K., Heidemann, R. A., & Aziz, K. (1983). "An accelerated successive substitution algorithm." The Canadian Journal of Chemical Engineering, 61(4), 590-596.
    • Dominant eigenvalue method for acceleration.

Chemical Equilibrium

  1. Smith, W. R. & Missen, R. W. (1982). Chemical Reaction Equilibrium Analysis: Theory and Algorithms. Wiley-Interscience.

  2. Michelsen, M. L. (1989). "Calculation of multiphase equilibrium in ideal solutions." Fluid Phase Equilibria, 53, 73-80.

Electrolyte Systems

  1. Thomsen, K. (2005). "Modeling electrolyte solutions with the extended UNIQUAC model." Pure and Applied Chemistry, 77(3), 531-542.

Implementation Files

File Description
TPflash.java Two-phase flash with SSI and Newton
TPmultiflash.java Multi-phase flash with stability analysis
RachfordRice.java Rachford-Rice equation solvers
SysNewtonRhapsonTPflash.java Second-order Newton solver
ChemicalReactionOperations.java Chemical equilibrium solver

Usage Example

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create system
SystemSrkEos system = new SystemSrkEos(298.15, 10.0);
system.addComponent("methane", 0.7);
system.addComponent("ethane", 0.2);
system.addComponent("propane", 0.1);
system.setMixingRule("classic");

// Enable multi-phase check for stability analysis
system.setMultiPhaseCheck(true);

// Perform TP flash
ThermodynamicOperations ops = new ThermodynamicOperations(system);
ops.TPflash();

// Results
System.out.println("Number of phases: " + system.getNumberOfPhases());
System.out.println("Vapor fraction: " + system.getBeta(0));
system.display();

For electrolyte systems:

import neqsim.thermo.system.SystemElectrolyteCPA;

SystemElectrolyteCPA system = new SystemElectrolyteCPA(298.15, 1.0);
system.addComponent("CO2", 1.0);
system.addComponent("water", 100.0);
system.setMixingRule(10);  // CPA mixing rule with electrolyte support
system.setMultiPhaseCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(system);
ops.TPflash();  // Automatically solves chemical equilibrium in aqueous phase

Thermo Ops Overview

Thermodynamic Operations Package

The thermodynamicoperations package provides flash calculations, phase envelope construction, and chemical equilibrium solvers.

Table of Contents


Overview

Location: neqsim.thermodynamicoperations

Purpose:

Main Entry Point: ThermodynamicOperations

import neqsim.thermodynamicoperations.ThermodynamicOperations;

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();  // Temperature-Pressure flash

Package Structure

thermodynamicoperations/
├── ThermodynamicOperations.java     # Main facade class
├── BaseOperation.java               # Base class for operations
├── OperationInterface.java          # Operation interface
│
├── flashops/                        # Flash calculations
│   ├── TPflash.java                 # Temperature-Pressure flash
│   ├── PHflash.java                 # Pressure-Enthalpy flash
│   ├── PSFlash.java                 # Pressure-Entropy flash
│   ├── TVflash.java                 # Temperature-Volume flash
│   ├── TSFlash.java                 # Temperature-Entropy flash (Q-function)
│   ├── THflash.java                 # Temperature-Enthalpy flash (Q-function)
│   ├── TUflash.java                 # Temperature-Internal Energy flash (Q-function)
│   ├── PVflash.java                 # Pressure-Volume flash (Q-function)
│   ├── VUflash.java                 # Volume-Internal Energy flash (Q-function)
│   ├── VHflash.java                 # Volume-Enthalpy flash (Q-function)
│   ├── VSflash.java                 # Volume-Entropy flash (Q-function)
│   ├── PUflash.java                 # Pressure-Internal Energy flash
│   ├── TVfractionFlash.java         # Temperature-Vapor fraction flash
│   ├── dTPflash.java                # Dual temperature flash
│   ├── TPmultiflash.java            # Multiphase TP flash
│   ├── SolidFlash.java              # Flash with solids
│   ├── CriticalPointFlash.java      # Critical point calculation
│   ├── QfuncFlash.java              # Base class for Q-function flashes
│   ├── RachfordRice.java            # Rachford-Rice solver
│   └── saturationops/               # Saturation calculations
│       ├── BubblePointPressureFlash.java
│       ├── BubblePointTemperatureFlash.java
│       ├── DewPointPressureFlash.java
│       ├── DewPointTemperatureFlash.java
│       ├── WaterDewPointFlash.java
│       └── HydrateEquilibrium.java
│
├── phaseenvelopeops/                # Phase envelope calculations
│   ├── multicomponentenvelopeops/
│   │   ├── PTPhaseEnvelope.java
│   │   └── PHPhaseEnvelope.java
│   └── reactivecurves/
│       └── ReactivePhaseEnvelope.java
│
├── chemicalequilibrium/             # Chemical equilibrium
│   └── ChemicalEquilibrium.java
│
└── propertygenerator/               # Property tables
    └── OLGApropertyTableGenerator.java

Flash Calculations

Flash Types

Flash Type Method Known Variables Solved Variables
TP TPflash() T, P Phase amounts, compositions
PH PHflash(H) P, H T, phase amounts, compositions
PS PSflash(S) P, S T, phase amounts, compositions
PU PUflash(U) P, U T, phase amounts, compositions
TV TVflash(V) T, V P, phase amounts, compositions
TS TSflash(S) T, S P, phase amounts, compositions
TH THflash(H) T, H P, phase amounts, compositions
TU TUflash(U) T, U P, phase amounts, compositions
PV PVflash(V) P, V T, phase amounts, compositions
VU VUflash(V, U) V, U T, P, phase amounts
VH VHflash(V, H) V, H T, P, phase amounts
VS VSflash(V, S) V, S T, P, phase amounts

TP Flash

The most common flash calculation - given temperature and pressure, find equilibrium phases.

SystemInterface fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.1);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

System.out.println("Number of phases: " + fluid.getNumberOfPhases());
System.out.println("Vapor fraction: " + fluid.getBeta());

PH Flash (Adiabatic)

Find temperature given pressure and enthalpy - essential for adiabatic processes.

// Initial state
double H = fluid.getEnthalpy();

// Change pressure
fluid.setPressure(20.0);

// Find new temperature at same enthalpy
ops.PHflash(H);

System.out.println("New temperature: " + fluid.getTemperature("C") + " °C");

PS Flash (Isentropic)

Find temperature given pressure and entropy - for isentropic compression/expansion.

double S = fluid.getEntropy();
fluid.setPressure(100.0);
ops.PSflash(S);

System.out.println("Isentropic temperature: " + fluid.getTemperature("C") + " °C");

VU Flash (Dynamic)

For dynamic simulations - given volume and internal energy, find T and P.

double V = fluid.getVolume();
double U = fluid.getInternalEnergy();

// Simulate heat addition
double Unew = U + 10000.0;  // Add 10 kJ

ops.VUflash(V, Unew);
System.out.println("New T: " + fluid.getTemperature("C") + " °C");
System.out.println("New P: " + fluid.getPressure() + " bar");

TV Fraction Flash

Find pressure at given vapor/liquid fraction.

// Find pressure where vapor fraction = 0.5
ops.TVfractionFlash(0.5);
System.out.println("Pressure at 50% vapor: " + fluid.getPressure() + " bar");

Saturation Operations

Bubble Point

// Bubble point pressure at current temperature
ops.bubblePointPressureFlash(false);
double Pbub = fluid.getPressure();

// Bubble point temperature at current pressure  
ops.bubblePointTemperatureFlash();
double Tbub = fluid.getTemperature();

Dew Point

// Dew point pressure at current temperature
ops.dewPointPressureFlash();
double Pdew = fluid.getPressure();

// Dew point temperature at current pressure
ops.dewPointTemperatureFlash();
double Tdew = fluid.getTemperature();

Water Dew Point

// Water dew point temperature at given pressure
ops.waterDewPointTemperatureFlash();
double TwaterDew = fluid.getTemperature();

Hydrate Equilibrium

📚 See Hydrate Flash Operations for complete documentation

// Hydrate formation temperature
ops.hydrateFormationTemperature();
double Thyd = fluid.getTemperature();

// Hydrate formation pressure
fluid.setTemperature(278.15);
ops.hydrateFormationPressure();
double Phyd = fluid.getPressure();

// Hydrate TPflash (phase equilibrium with hydrate)
ops.hydrateTPflash();

// Gas-Hydrate equilibrium (no aqueous phase)
ops.gasHydrateTPflash();

Phase Envelope Operations

PT Phase Envelope

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.calcPTphaseEnvelope();


// Get results
double[][] envelope = ops.getOperation().get2DData();
// envelope[0] = temperatures (K)
// envelope[1] = pressures (bar)

// Get cricondenbar and cricondentherm
double cricondenbar = ops.getOperation().getCricondenbar();
double cricondentherm = ops.getOperation().getCricondentherm();

PH Phase Envelope

ops.calcPHenveloppe();
double[][] phEnvelope = ops.getOperation().get2DData();

Property Generators

OLGA Property Tables

Generate property tables for multiphase flow simulators.

import neqsim.thermodynamicoperations.propertygenerator.OLGApropertyTableGenerator;

OLGApropertyTableGenerator generator = new OLGApropertyTableGenerator(fluid);
generator.setFileName("fluid_properties");

// Set ranges
generator.setPressureRange(1.0, 200.0, 50);   // 1-200 bar, 50 points
generator.setTemperatureRange(250.0, 400.0, 30); // 250-400 K, 30 points
generator.setWaterCutRange(0.0, 1.0, 5);      // 0-100% water cut, 5 points

generator.run();

Chemical Equilibrium

For reactive systems, calculate equilibrium composition considering reactions.

// Set up reactive system
SystemInterface reactive = new SystemSrkEos(700.0, 10.0);
reactive.addComponent("methane", 1.0);
reactive.addComponent("water", 2.0);
reactive.addComponent("CO2", 0.0);
reactive.addComponent("hydrogen", 0.0);

// Enable chemical reactions
reactive.setChemicalReactions(true);

ThermodynamicOperations ops = new ThermodynamicOperations(reactive);
ops.calcChemicalEquilibrium();

// Get equilibrium composition
for (int i = 0; i < reactive.getNumberOfComponents(); i++) {
    System.out.println(reactive.getComponent(i).getName() + 
        ": " + reactive.getComponent(i).getx() + " mol/mol");
}

Multi-Phase Flash

Handle systems with multiple liquid phases, solids, or hydrates.

SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 5, 100.0);
fluid.addComponent("methane", 0.90);
fluid.addComponent("water", 0.10);
fluid.setMixingRule("CPA_Statoil");
fluid.setMultiPhaseCheck(true);  // Enable multi-phase check

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

System.out.println("Number of phases: " + fluid.getNumberOfPhases());
for (int i = 0; i < fluid.getNumberOfPhases(); i++) {
    System.out.println("Phase " + i + ": " + fluid.getPhase(i).getPhaseTypeName());
}

Solid Flash (Wax, Ice)

fluid.setSolidPhaseCheck("wax");
ops.TPsolidflash();

Advanced Options

Calculation Identifiers

Track calculations with UUIDs for parallel processing.

UUID calcId = UUID.randomUUID();
ops.TPflash(calcId);

Flash Settings

// Set maximum iterations
ops.setMaxIterations(100);

// Set convergence tolerance
ops.setTolerance(1e-10);

Best Practices

  1. Always set mixing rule before flash calculations
  2. Initialize fluid with createDatabase(true) for new components
  3. Use multi-phase check when expecting multiple liquid phases
  4. Check convergence after flash - verify getNumberOfPhases() makes sense
  5. Handle exceptions for failed convergence

Chapter 8: Fluid Characterization

Characterization

Fluid Characterization in NeqSim

Real reservoir fluids often contain a complex mixture of heavy hydrocarbons (C7+) that cannot be represented by standard pure components. NeqSim provides a robust characterization framework to model these fluids using TBP (True Boiling Point) fractions and Plus fractions.

Related Documentation:

1. Adding Heavy Fractions

You can add heavy fractions to a system using two primary methods: addTBPfraction and addPlusFraction.

1.1 TBP Fractions

Use addTBPfraction when you have data for specific carbon number cuts (e.g., C7, C8, C9) with defined properties.

// addTBPfraction(name, moles, molarMass_kg_mol, density_kg_m3)
system.addTBPfraction("C7", 1.0, 0.092, 0.73); 
system.addTBPfraction("C8", 1.0, 0.104, 0.76);

1.2 Plus Fractions

Use addPlusFraction for the final residue or "plus" fraction (e.g., C10+, C20+) where you only have average properties.

// addPlusFraction(name, moles, molarMass_kg_mol, density_kg_m3)
system.addPlusFraction("C10+", 10.0, 0.250, 0.85);

2. Characterization Process

After adding the components, you must run the characterization routine to split the plus fraction into pseudo-components and estimate their critical properties (Tc, Pc, w).

2.1 Setting the Model

NeqSim supports several characterization models. The most common is the Pedersen model.

// Set the TBP Model (affects how TBP fractions are treated)
system.getCharacterization().setTBPModel("PedersenSRK"); 

// Set the Plus Fraction Model (affects how the plus fraction is split)
system.getCharacterization().setPlusFractionModel("Pedersen");

Available TBP Models

NeqSim provides 10 TBP models for estimating critical properties (Tc, Pc, ω) from molecular weight and density:

Model Best Application
PedersenSRK General SRK EOS (default)
PedersenPR General Peng-Robinson EOS
PedersenSRKHeavyOil Heavy oils with SRK
PedersenPRHeavyOil Heavy oils with PR
Lee-Kesler General purpose, uses Watson K-factor
RiaziDaubert Light fractions (MW < 300 g/mol)
Twu Paraffinic fluids, gas condensates
Cavett Refining industry, API gravity corrections
Standing Reservoir engineering

See TBP Fraction Models for detailed mathematical correlations and model selection guidelines.

Available Plus Fraction Models

Pedersen Model

The standard Pedersen model assumes an exponential distribution for the mole fraction $z_i$ of each carbon number fraction $i$:

[ z_i = \exp(A + B \cdot i) ]

where $i$ is the carbon number, and $A$ and $B$ are coefficients determined to match the total mole fraction and average molar mass of the plus fraction.

The density $\rho_i$ is modeled as a logarithmic function of the carbon number:

[ \rho_i = C + D \cdot \ln(i) ]

where $C$ and $D$ are fitted coefficients.

Whitson Gamma Model

The Whitson Gamma model uses a three-parameter Gamma probability density function (PDF) to describe the molar mass distribution:

[ p(M) = \frac{(M - \eta)^{\alpha - 1} \exp\left(-\frac{M - \eta}{\beta}\right)}{\beta^\alpha \Gamma(\alpha)} ]

where:

The mole fraction $z_i$ for a pseudo-component covering the molar mass range $[M_{L}, M_{U}]$ is obtained by integrating the PDF:

[ z_i = z_{plus} \int_{M_{L}}^{M_{U}} p(M) \, dM ]

The density of each pseudo-component is calculated using the Watson UOP characterization factor $K_w$:

[ K_w = 4.5579 \cdot (M_{plus})^{0.15178} \cdot \rho_{plus}^{-1.18241} ]

[ \rho_i = 6.0108 \cdot M_i^{0.17947} \cdot K_w^{-1.18241} ]

(Note: Molar masses are in g/mol and densities in g/cm³ for these correlations).

2.2 Running Characterization

Once models are set, execute the characterization.

system.getCharacterization().characterisePlusFraction();

This process will:

  1. Extrapolate the molar distribution to C80+.
  2. Calculate properties for each carbon number.
  3. Group them into pseudo-components (if lumping is enabled).

3. Lumping (ModelLumping)

To reduce simulation time, it is often necessary to group the many characterized components into a smaller number of "lumped" pseudo-components.

3.1 Configuring Lumping

You can control the lumping behavior via the LumpingModel.

// Set the lumping method (Default is "PVTlumpingModel")
system.getCharacterization().setLumpingModel("PVTlumpingModel");

// Configure the number of pseudo-components to generate
system.getCharacterization().getLumpingModel().setNumberOfLumpedComponents(12);

3.2 Full Example

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class CharacterizationExample {
    public static void main(String[] args) {
        // 1. Create System
        SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
        fluid.addComponent("nitrogen", 0.5);
        fluid.addComponent("CO2", 1.0);
        fluid.addComponent("methane", 60.0);
        fluid.addComponent("ethane", 5.0);
        fluid.addComponent("propane", 3.0);

        // 2. Add Heavy Fractions
        fluid.addTBPfraction("C6", 1.0, 0.086, 0.66);
        fluid.addTBPfraction("C7", 2.0, 0.092, 0.73);
        fluid.addTBPfraction("C8", 2.0, 0.104, 0.76);
        fluid.addTBPfraction("C9", 1.0, 0.118, 0.78);
        fluid.addPlusFraction("C10+", 15.0, 0.280, 0.84); // The Plus Fraction

        // 3. Configure Characterization
        fluid.getCharacterization().setTBPModel("PedersenSRK");
        fluid.getCharacterization().setPlusFractionModel("Pedersen");

        // 4. Configure Lumping
        // We want to lump the C10+ distribution into 5 pseudo-components
        fluid.getCharacterization().setLumpingModel("PVTlumpingModel");
        fluid.getCharacterization().getLumpingModel().setNumberOfLumpedComponents(5);

        // 5. Run Characterization
        fluid.getCharacterization().characterisePlusFraction();

        // 6. Use the Fluid
        fluid.setMixingRule("classic");
        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        ops.TPflash();

        fluid.prettyPrint();
    }
}

4. Advanced Options

TBP Fractions

TBP Fraction Models in NeqSim

This guide provides comprehensive documentation on True Boiling Point (TBP) fraction models available in NeqSim for petroleum fluid characterization.

1. Introduction

What are TBP Models?

TBP (True Boiling Point) models are empirical correlations that estimate the critical properties of petroleum pseudo-components from easily measured bulk properties like molecular weight (MW) and specific gravity (SG). These critical properties are essential inputs for cubic equations of state (EOS) such as SRK and Peng-Robinson.

Why Do We Need Them?

Petroleum fluids contain thousands of individual hydrocarbon species that cannot all be individually identified and characterized. Instead, heavy fractions (typically C7+) are lumped into pseudo-components. TBP models provide the thermodynamic properties needed for EOS calculations:

2. Available Models

NeqSim provides 10 TBP models, each optimized for different applications:

Model Name Best Application Key Feature
PedersenSRK General SRK EOS Default, auto light/heavy switching
PedersenSRKHeavyOil Heavy oils with SRK Optimized for MW > 500 g/mol
PedersenPR General PR EOS Optimized for Peng-Robinson
PedersenPR2 PR EOS alternate Søreide boiling point correlation
PedersenPRHeavyOil Heavy oils with PR For viscous/heavy crude
RiaziDaubert Light fractions Best for MW < 300 g/mol
Lee-Kesler General purpose Uses Watson K-factor
Twu Paraffinic fluids n-alkane reference method
Cavett Refining industry API gravity corrections
Standing Reservoir engineering Simple, widely used

3. Model Details

3.1 Pedersen Models (PedersenSRK, PedersenPR)

Best for: General purpose petroleum characterization

The Pedersen correlations are the default and most widely used TBP models in NeqSim. They were specifically developed for use with cubic equations of state.

Correlations

Critical Temperature: $$T_c = a_0 \cdot \rho + a_1 \cdot \ln(M) + a_2 \cdot M + \frac{a_3}{M}$$

Critical Pressure: $$P_c = \exp\left(b_0 + b_1 \cdot \rho^{b_4} + \frac{b_2}{M} + \frac{b_3}{M^2}\right)$$

EOS m-parameter: $$m = c_0 + c_1 \cdot M + c_2 \cdot \rho + c_3 \cdot M^2$$

The model automatically switches between light oil and heavy oil coefficients at MW = 1120 g/mol.

Usage

import neqsim.thermo.system.SystemSrkEos;

// For SRK equation of state
SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.getCharacterization().setTBPModel("PedersenSRK");
fluid.addTBPfraction("C7", 1.0, 0.092, 0.73);
import neqsim.thermo.system.SystemPrEos;

// For Peng-Robinson equation of state
SystemPrEos fluid = new SystemPrEos(298.15, 50.0);
fluid.getCharacterization().setTBPModel("PedersenPR");
fluid.addTBPfraction("C7", 1.0, 0.092, 0.73);

Reference: Pedersen, K.S., Thomassen, P., Fredenslund, A. (1984). "Thermodynamics of Petroleum Mixtures Containing Heavy Hydrocarbons." Ind. Eng. Chem. Process Des. Dev., 23, 566-573.


3.2 Riazi-Daubert Model

Best for: Light to medium petroleum fractions (MW < 300 g/mol)

The Riazi-Daubert model uses a simple exponential-power law form that works well for lighter fractions. For heavier fractions (MW > 300), it automatically falls back to the Pedersen model.

Correlations

Critical Temperature (K): $$T_c = \frac{5}{9} \times 554.4 \times \exp(-1.3478 \times 10^{-4} \cdot M - 0.61641 \cdot SG) \times M^{0.2998} \times SG^{1.0555}$$

Critical Pressure (bar): $$P_c = 0.068947 \times 4.5203 \times 10^{4} \times \exp(-1.8078 \times 10^{-3} \cdot M - 0.3084 \cdot SG) \times M^{-0.8063} \times SG^{1.6015}$$

Boiling Point (K): $$T_b = 97.58 \times M^{0.3323} \times SG^{0.04609}$$

Applicability Range

Usage

SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.getCharacterization().setTBPModel("RiaziDaubert");
fluid.addTBPfraction("C7", 1.0, 0.092, 0.73);  // Light fraction - uses Riazi-Daubert
fluid.addTBPfraction("C30", 0.5, 0.400, 0.90); // Heavy fraction - falls back to Pedersen

Reference: Riazi, M.R. and Daubert, T.E. (1980). "Simplify Property Predictions." Hydrocarbon Processing, 59(3), 115-116.


3.3 Lee-Kesler Model

Best for: General purpose characterization, especially when Watson K-factor is known

The Lee-Kesler model is based on generalized correlations using boiling point and specific gravity as primary inputs. It is widely used in the petroleum industry.

Correlations

Critical Temperature (K): $$T_c = 189.8 + 450.6 \cdot SG + (0.4244 + 0.1174 \cdot SG) \cdot T_b + (0.1441 - 1.0069 \cdot SG) \times \frac{10^5}{T_b}$$

Critical Pressure (bar): $$\ln(P_c) = 3.3864 - \frac{0.0566}{SG} - f(T_b, SG)$$

where $f(T_b, SG)$ is a polynomial function of boiling point and specific gravity.

Acentric Factor (Kesler-Lee):

For $T_{br} < 0.8$: $$\omega = \frac{\ln(P_{br}) - 5.92714 + \frac{6.09649}{T_{br}} + 1.28862 \cdot \ln(T_{br}) - 0.169347 \cdot T_{br}^6}{15.2518 - \frac{15.6875}{T_{br}} - 13.4721 \cdot \ln(T_{br}) + 0.43577 \cdot T_{br}^6}$$

For $T_{br} \geq 0.8$: $$\omega = -7.904 + 0.1352 \cdot K_w - 0.007465 \cdot K_w^2 + 8.359 \cdot T_{br} + \frac{1.408 - 0.01063 \cdot K_w}{T_{br}}$$

where $T_{br} = T_b/T_c$ and $K_w$ is the Watson characterization factor.

Usage

SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.getCharacterization().setTBPModel("Lee-Kesler");
fluid.addTBPfraction("C10", 1.0, 0.142, 0.78);

Reference: Kesler, M.G. and Lee, B.I. (1976). "Improve Prediction of Enthalpy of Fractions." Hydrocarbon Processing, 55(3), 153-158.


3.4 Twu Model

Best for: Paraffinic fluids and gas condensates (Watson K > 12)

The Twu model uses n-alkanes as reference compounds and applies perturbation corrections based on specific gravity differences. This approach is particularly accurate for waxy/paraffinic petroleum fractions.

Method Overview

  1. Calculate reference n-alkane properties from boiling point
  2. Apply perturbation corrections based on $\Delta SG = SG_{alkane} - SG_{actual}$
  3. Iterate to find equivalent n-alkane molecular weight

Key Equations

n-Alkane Critical Temperature: $$T_{c,alk} = T_b \times \left[0.533272 + 0.343831 \times 10^{-3} \cdot T_b + 2.526167 \times 10^{-7} \cdot T_b^2 - 1.65848 \times 10^{-10} \cdot T_b^3 + \frac{4.60774 \times 10^{24}}{T_b^{13}}\right]^{-1}$$

Perturbation Function: $$f_T = \Delta S_T \times \left(-0.270159 \cdot T_b^{-0.5} + (0.0398285 - 0.706691 \cdot T_b^{-0.5}) \cdot \Delta S_T\right)$$

where $\Delta S_T = \exp(5.0 \cdot (SG_{alk} - SG)) - 1$

Corrected Critical Temperature: $$T_c = T_{c,alk} \times \left(\frac{1 + 2f_T}{1 - 2f_T}\right)^2$$

When to Use

Usage

SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.getCharacterization().setTBPModel("Twu");
fluid.addTBPfraction("C10", 1.0, 0.142, 0.78);

Reference: Twu, C.H. (1984). "An Internally Consistent Correlation for Predicting the Critical Properties and Molecular Weights of Petroleum and Coal-Tar Liquids." Fluid Phase Equilibria, 16, 137-150.


3.5 Cavett Model

Best for: Refining industry applications, heavy oils with API gravity data

The Cavett model in NeqSim uses a hybrid Lee-Kesler/Cavett approach with API gravity corrections. This provides robust results across a wide range of petroleum fractions while maintaining the API gravity sensitivity important for refining applications.

API Gravity Relationship

$$API = \frac{141.5}{SG} - 131.5$$

API Range Classification SG Range
> 31.1° Light crude < 0.87
22.3° - 31.1° Medium crude 0.87 - 0.92
< 22.3° Heavy crude > 0.92

Correlations

The model uses Lee-Kesler correlations as the base, with API corrections for heavy fractions:

Critical Temperature (API < 30°): $$T_c = T_{c,LK} \times [1 + 0.002 \cdot (30 - API)]$$

Critical Pressure (API < 30°): $$P_c = P_{c,LK} \times [1 + 0.001 \cdot (30 - API)]$$

Acentric Factor (Edmister): $$\omega = \frac{3}{7} \times \frac{\log_{10}(P_c/P_{ref})}{T_c/T_b - 1} - 1$$

Bounded to range [0.0, 1.5] for physical validity.

Usage

SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.getCharacterization().setTBPModel("Cavett");

// Heavy oil example (API ~ 20°)
fluid.addTBPfraction("HeavyFrac", 1.0, 0.300, 0.93);

Reference: Cavett, R.H. (1962). "Physical Data for Distillation Calculations, Vapor-Liquid Equilibria." Proc. 27th API Meeting, San Francisco.


3.6 Standing Model

Best for: Reservoir engineering, quick estimates, black oil PVT

The Standing model uses Riazi-Daubert style correlations for robust critical property estimation. It's widely used in reservoir simulation tools.

Correlations

Same as Riazi-Daubert (see Section 3.2).

Usage

SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.getCharacterization().setTBPModel("Standing");
fluid.addTBPfraction("C7", 1.0, 0.092, 0.73);

Reference: Standing, M.B. (1977). "Volumetric and Phase Behavior of Oil Field Hydrocarbon Systems." SPE, Dallas.


4. Watson Characterization Factor

The Watson characterization factor ($K_w$) is useful for classifying petroleum fractions and selecting appropriate TBP models:

$$K_w = \frac{(1.8 \cdot T_b)^{1/3}}{SG}$$

K_w Range Fluid Type Recommended Model
> 12.5 Paraffinic (gas condensates) Twu
11.5 - 12.5 Mixed/intermediate Pedersen or Lee-Kesler
10.5 - 11.5 Naphthenic Pedersen or RiaziDaubert
< 10.5 Aromatic Pedersen

Calculating K_w in NeqSim

TBPfractionModel tbpModel = new TBPfractionModel();
double Kw = tbpModel.calcWatsonKFactor(0.142, 0.78);  // MW in kg/mol, density in g/cm³
System.out.println("Watson K-factor: " + Kw);

5. Model Selection Guidelines

5.1 Decision Tree

Is EOS = Peng-Robinson?
├── Yes → Is fluid heavy (MW > 500)?
│         ├── Yes → PedersenPRHeavyOil
│         └── No → PedersenPR
└── No (SRK) → Is fluid heavy (MW > 500)?
              ├── Yes → PedersenSRKHeavyOil
              └── No → Is K_w > 12 (paraffinic)?
                        ├── Yes → Twu
                        └── No → Is MW < 300?
                                  ├── Yes → RiaziDaubert or Lee-Kesler
                                  └── No → PedersenSRK

5.2 Automatic Model Recommendation

NeqSim can recommend an appropriate model based on fluid properties:

TBPfractionModel tbpModel = new TBPfractionModel();
String recommended = tbpModel.recommendTBPModel(
    0.200,    // Average MW in kg/mol
    0.85,     // Average density in g/cm³
    "SRK"     // EOS type: "SRK" or "PR"
);
System.out.println("Recommended model: " + recommended);

5.3 List All Available Models

String[] models = TBPfractionModel.getAvailableModels();
for (String model : models) {
    System.out.println(model);
}

6. Typical Property Values

Reference values for common petroleum fractions:

Component MW (g/mol) SG T_c (K) P_c (bar) ω
n-Heptane (C7) 100 0.684 540 27.4 0.35
C7 (typical) 96-100 0.72-0.74 540-560 27-30 0.30-0.35
C10 134-142 0.76-0.79 600-640 20-25 0.45-0.55
C15 200-210 0.81-0.83 680-720 15-18 0.65-0.75
C20 275-285 0.85-0.87 750-800 12-15 0.85-0.95
C30 400-420 0.88-0.90 850-900 8-10 1.0-1.2

7. Complete Examples

7.1 Basic Fluid Characterization

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class BasicCharacterization {
    public static void main(String[] args) {
        // Create SRK fluid
        SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);

        // Add light components
        fluid.addComponent("methane", 70.0);
        fluid.addComponent("ethane", 10.0);
        fluid.addComponent("propane", 5.0);

        // Set TBP model before adding heavy fractions
        fluid.getCharacterization().setTBPModel("PedersenSRK");

        // Add TBP fractions
        fluid.addTBPfraction("C7", 3.0, 0.092, 0.73);
        fluid.addTBPfraction("C8", 2.5, 0.104, 0.76);
        fluid.addTBPfraction("C9", 2.0, 0.118, 0.78);
        fluid.addTBPfraction("C10", 1.5, 0.134, 0.79);

        // Set mixing rule and initialize
        fluid.setMixingRule("classic");

        // Perform flash calculation
        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        ops.TPflash();

        // Print results
        fluid.prettyPrint();

        // Access individual component properties
        System.out.println("\nC7 Critical Properties:");
        System.out.println("Tc = " + fluid.getComponent("C7_PC").getTC() + " K");
        System.out.println("Pc = " + fluid.getComponent("C7_PC").getPC() + " bar");
        System.out.println("omega = " + fluid.getComponent("C7_PC").getAcentricFactor());
    }
}

7.2 Comparing Different TBP Models

import neqsim.thermo.system.SystemSrkEos;

public class ModelComparison {
    public static void main(String[] args) {
        String[] models = {"PedersenSRK", "Lee-Kesler", "RiaziDaubert", "Twu", "Cavett", "Standing"};

        System.out.println("=== TBP Model Comparison for C10 (MW=142 g/mol, SG=0.78) ===");
        System.out.printf("%-15s %10s %10s %10s%n", "Model", "Tc (K)", "Pc (bar)", "omega");
        System.out.println(StringUtils.repeat("-", 50));

        for (String modelName : models) {
            SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
            fluid.getCharacterization().setTBPModel(modelName);
            fluid.addTBPfraction("C10", 1.0, 0.142, 0.78);

            double Tc = fluid.getComponent(0).getTC();
            double Pc = fluid.getComponent(0).getPC();
            double omega = fluid.getComponent(0).getAcentricFactor();

            System.out.printf("%-15s %10.2f %10.2f %10.4f%n", modelName, Tc, Pc, omega);
        }
    }
}

7.3 Gas Condensate with Twu Model

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class GasCondensateExample {
    public static void main(String[] args) {
        SystemSrkEos fluid = new SystemSrkEos(350.0, 150.0);

        // Lean gas condensate composition
        fluid.addComponent("nitrogen", 1.5);
        fluid.addComponent("CO2", 2.0);
        fluid.addComponent("methane", 80.0);
        fluid.addComponent("ethane", 6.0);
        fluid.addComponent("propane", 3.0);
        fluid.addComponent("i-butane", 0.8);
        fluid.addComponent("n-butane", 1.2);
        fluid.addComponent("i-pentane", 0.5);
        fluid.addComponent("n-pentane", 0.5);

        // Use Twu model for paraffinic gas condensate
        fluid.getCharacterization().setTBPModel("Twu");

        // Add C6+ fractions
        fluid.addTBPfraction("C6", 1.0, 0.086, 0.68);
        fluid.addTBPfraction("C7+", 3.5, 0.130, 0.76);

        fluid.setMixingRule("classic");

        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        ops.TPflash();

        System.out.println("Gas Condensate Flash at " + fluid.getTemperature() + " K, " 
            + fluid.getPressure() + " bar");
        fluid.prettyPrint();
    }
}

7.4 Heavy Oil Characterization

import neqsim.thermo.system.SystemPrEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class HeavyOilExample {
    public static void main(String[] args) {
        // Use Peng-Robinson for heavy oil
        SystemPrEos fluid = new SystemPrEos(333.15, 10.0);

        // Light ends
        fluid.addComponent("methane", 5.0);
        fluid.addComponent("ethane", 2.0);
        fluid.addComponent("propane", 3.0);
        fluid.addComponent("n-butane", 2.0);
        fluid.addComponent("n-pentane", 3.0);

        // Use heavy oil model
        fluid.getCharacterization().setTBPModel("PedersenPRHeavyOil");

        // Heavy fractions (API ~ 15°, SG ~ 0.96)
        fluid.addTBPfraction("C6-C10", 15.0, 0.120, 0.80);
        fluid.addTBPfraction("C11-C20", 25.0, 0.250, 0.88);
        fluid.addTBPfraction("C21-C30", 20.0, 0.380, 0.92);
        fluid.addTBPfraction("C31+", 25.0, 0.550, 0.96);

        fluid.setMixingRule("classic");

        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        ops.TPflash();

        System.out.println("Heavy Oil Flash at " + fluid.getTemperature() + " K");
        fluid.prettyPrint();
    }
}

8. Python (neqsim-python) Examples

8.1 Basic Usage

from neqsim.thermo import fluid

# Create fluid with TBP model selection
oil = fluid('srk')
oil.getCharacterization().setTBPModel("PedersenSRK")

# Add components
oil.addComponent("methane", 70.0)
oil.addComponent("ethane", 10.0)
oil.addTBPfraction("C7", 5.0, 0.092, 0.73)
oil.addTBPfraction("C10", 3.0, 0.142, 0.78)

oil.setMixingRule("classic")
oil.setTemperature(298.15, "K")
oil.setPressure(50.0, "bara")

# Flash and print
from neqsim.thermodynamicoperations import TPflash
TPflash(oil)
oil.prettyPrint()

8.2 Direct Java API Access

from jpype import JClass

# Import Java classes directly
SystemSrkEos = JClass('neqsim.thermo.system.SystemSrkEos')
TBPfractionModel = JClass('neqsim.thermo.characterization.TBPfractionModel')

# Create fluid
fluid = SystemSrkEos(298.15, 50.0)

# Get model recommendation
tbpModel = TBPfractionModel()
recommended = tbpModel.recommendTBPModel(0.200, 0.85, "SRK")
print(f"Recommended model: {recommended}")

# Set model and add fractions
fluid.getCharacterization().setTBPModel(recommended)
fluid.addTBPfraction("C10", 1.0, 0.142, 0.78)

# Print critical properties
print(f"Tc = {fluid.getComponent(0).getTC()} K")
print(f"Pc = {fluid.getComponent(0).getPC()} bar")

9. Troubleshooting

Common Issues

  1. Unrealistic Tc values (> 1000 K or < 400 K)

    • Check that MW is in kg/mol (not g/mol)
    • Check that density is in g/cm³ (not kg/m³)
    • Try a different model (Pedersen is most robust)
  2. Negative acentric factor

    • Usually indicates incorrect input data
    • Cavett model has built-in bounds checking [0, 1.5]
  3. Flash convergence issues with heavy fractions

    • Use heavy oil variants (PedersenSRKHeavyOil, PedersenPRHeavyOil)
    • Check that fluid composition is normalized
  4. Model not found error

    • Model names are case-sensitive
    • Use TBPfractionModel.getAvailableModels() to see valid names

Input Unit Requirements

Property Required Unit Common Mistake
Molecular Weight kg/mol Using g/mol (divide by 1000)
Density g/cm³ Using kg/m³ (divide by 1000)
Temperature K (internal)
Pressure bar (internal)

10. References

  1. Pedersen, K.S., Thomassen, P., Fredenslund, A. (1984). "Thermodynamics of Petroleum Mixtures Containing Heavy Hydrocarbons." Ind. Eng. Chem. Process Des. Dev., 23, 566-573.

  2. Kesler, M.G., Lee, B.I. (1976). "Improve Prediction of Enthalpy of Fractions." Hydrocarbon Processing, 55(3), 153-158.

  3. Riazi, M.R., Daubert, T.E. (1980). "Simplify Property Predictions." Hydrocarbon Processing, 59(3), 115-116.

  4. Twu, C.H. (1984). "An Internally Consistent Correlation for Predicting the Critical Properties and Molecular Weights of Petroleum and Coal-Tar Liquids." Fluid Phase Equilibria, 16, 137-150.

  5. Cavett, R.H. (1962). "Physical Data for Distillation Calculations, Vapor-Liquid Equilibria." Proc. 27th API Meeting, San Francisco.

  6. Standing, M.B. (1977). "Volumetric and Phase Behavior of Oil Field Hydrocarbon Systems." SPE, Dallas.

  7. Edmister, W.C. (1958). "Applied Hydrocarbon Thermodynamics, Part 4: Compressibility Factors and Equations of State." Petroleum Refiner, 37(4), 173-179.


See Also

PVT Characterization

PVT and Fluid Characterization

Accurate phase behavior predictions start with a realistic fluid description. NeqSim supports full compositional models, TBP cuts, and black-oil style pseudo-components.

Building Compositions

  1. Known components: Add pure components directly using critical-property data from the internal database.
  2. Plus fractions (C7+): Use addPlusFraction(name, moles, molarMass, density) when only overall heavy fraction data are available.
  3. TBP/assay data: Use addTBPfraction(name, moles, density, molarMass) to preserve multiple heavy cuts with their own boiling ranges.
SystemInterface oil = new SystemSrkEos(323.15, 150.0);
oil.createDatabase(true);
oil.addComponent("nitrogen", 0.01);
oil.addComponent("methane", 0.60);
oil.addTBPfraction("C7", 0.08, 0.73, 7.5);
oil.addTBPfraction("C10", 0.10, 0.80, 10.5);
oil.addPlusFraction("C20+", 0.21, 0.92, 22.0);
oil.setMixingRule(2);

Tuning and Regression

Asphaltene Modeling

For fluids with asphaltene precipitation risk, use the PedersenAsphalteneCharacterization class:

import neqsim.thermo.characterization.PedersenAsphalteneCharacterization;

// Create asphaltene characterization
PedersenAsphalteneCharacterization asphChar = new PedersenAsphalteneCharacterization();
asphChar.setAsphalteneMW(750.0);     // Molecular weight g/mol
asphChar.setAsphalteneDensity(1.10); // Density g/cm³

// Add asphaltene as pseudo-component (before mixing rule)
asphChar.addAsphalteneToSystem(oil, 0.02);  // 2 mol% asphaltene
oil.setMixingRule("classic");

// Perform TPflash with asphaltene detection
boolean hasAsphaltene = PedersenAsphalteneCharacterization.TPflash(oil);

NeqSim supports two asphaltene phase types:

PVT Reports

After running ThermodynamicOperations.TPflash(), collect standard PVT outputs:

oil.initProperties();
System.out.println("Bo at separator: " + oil.getPhase("oil").getVolume() / oil.getTotalNumberOfMoles());
System.out.println("GOR at separator: " + oil.getPhase("gas").getNumberOfMoles()/oil.getPhase("oil").getNumberOfMoles());

For multi-stage separators, clone the fluid after each flash and continue flashing at downstream conditions.

Data Management

Characterization Package

Characterization Package

Documentation for plus fraction and asphaltene characterization in NeqSim.

Table of Contents


Overview

Location: neqsim.thermo.characterization

The characterization package handles petroleum plus fraction and asphaltene characterization:


Plus Fraction Methods

Adding Plus Fractions

import neqsim.thermo.system.SystemSrkEos;

SystemSrkEos fluid = new SystemSrkEos(373.15, 100.0);

// Light components
fluid.addComponent("methane", 0.70);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.08);
fluid.addComponent("n-butane", 0.05);

// C7+ as single pseudo-component
fluid.addTBPfraction("C7+", 0.07, 150.0, 0.78);  // name, moles, MW, SG

Multiple Plus Fractions

// Split C7+ into multiple fractions
fluid.addTBPfraction("C7", 0.02, 96.0, 0.727);
fluid.addTBPfraction("C8", 0.015, 107.0, 0.749);
fluid.addTBPfraction("C9", 0.01, 121.0, 0.768);
fluid.addTBPfraction("C10+", 0.025, 180.0, 0.82);

Characterization Approaches

Pedersen Method

import neqsim.thermo.characterization.PedersenCharacterization;

// Characterize using Pedersen correlations
PedersenCharacterization charPedersen = new PedersenCharacterization(fluid);
charPedersen.characterize();

Whitson Gamma Distribution

import neqsim.thermo.characterization.WhitsonCharacterization;

// Characterize using Whitson gamma distribution
WhitsonCharacterization charWhitson = new WhitsonCharacterization(fluid);
charWhitson.setAlpha(1.0);  // Shape parameter
charWhitson.characterize();

TBP Methods

Adding TBP Fractions

// addTBPfraction(name, moles, MW, specificGravity)
fluid.addTBPfraction("C7", moles, 96.0, 0.727);

// addPlusFraction with characterization
fluid.addPlusFraction("C20+", moles, 400.0, 0.90);

Property Estimation

For pseudo-components, critical properties are estimated using correlations:

Correlation Properties Estimated
Twu Tc, Pc, omega from MW, SG
Lee-Kesler Tc, Pc, omega from Tb, SG
Riazi-Daubert Tb from MW, SG
Pedersen Tc, Pc, omega for petroleum

Asphaltene Characterization

Pedersen's Asphaltene Method

The PedersenAsphalteneCharacterization class implements Pedersen's approach for treating asphaltene as a heavy liquid pseudo-component. This enables liquid-liquid equilibrium (LLE) calculations for asphaltene precipitation.

import neqsim.thermo.characterization.PedersenAsphalteneCharacterization;
import neqsim.thermo.system.SystemSrkEos;

// Create fluid system
SystemInterface fluid = new SystemSrkEos(373.15, 50.0);
fluid.addComponent("methane", 0.40);
fluid.addComponent("n-pentane", 0.25);
fluid.addComponent("n-heptane", 0.20);
fluid.addComponent("nC10", 0.10);

// Create and configure asphaltene characterization
PedersenAsphalteneCharacterization asphChar = new PedersenAsphalteneCharacterization();
asphChar.setAsphalteneMW(750.0);     // Molecular weight g/mol
asphChar.setAsphalteneDensity(1.10); // Density g/cm³

// Add asphaltene pseudo-component (BEFORE setting mixing rule)
asphChar.addAsphalteneToSystem(fluid, 0.05);  // 5 mol% asphaltene

// Set mixing rule (AFTER adding all components)
fluid.setMixingRule("classic");

// Print estimated critical properties
System.out.println(asphChar.toString());

Critical Property Correlations

Pedersen's method estimates critical properties from molecular weight (MW) and liquid density (ρ):

Property Correlation
Critical Temperature (Tc) f(MW, ρ)
Critical Pressure (Pc) f(MW, ρ)
Acentric Factor (ω) f(MW, ρ)
Normal Boiling Point (Tb) f(MW, ρ)

Typical values for asphaltene (MW=750 g/mol, ρ=1.10 g/cm³):

Property Value Unit
Tc 996 K
Pc 16.3 bar
ω 0.925 -
Tb 838 K

TPflash with Automatic Asphaltene Detection

The class provides static methods for TPflash with automatic detection of asphaltene-rich phases:

// Static TPflash - marks asphaltene-rich liquid phases as LIQUID_ASPHALTENE
boolean hasAsphaltene = PedersenAsphalteneCharacterization.TPflash(fluid);

// With explicit T,P specification
boolean hasAsphaltene = PedersenAsphalteneCharacterization.TPflash(fluid, 373.15, 50.0);

// Check result
if (hasAsphaltene) {
    System.out.println("Asphaltene-rich liquid phase detected");
    fluid.prettyPrint();  // Shows "ASPHALTENE LIQUID" column
}

Asphaltene Detection Criteria

A liquid phase is marked as PhaseType.LIQUID_ASPHALTENE when:


Examples

Example 1: Natural Gas with C7+

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

SystemSrkEos gas = new SystemSrkEos(373.15, 100.0);

// Wellstream composition
gas.addComponent("nitrogen", 0.015);
gas.addComponent("CO2", 0.020);
gas.addComponent("methane", 0.750);
gas.addComponent("ethane", 0.080);
gas.addComponent("propane", 0.045);
gas.addComponent("i-butane", 0.012);
gas.addComponent("n-butane", 0.020);
gas.addComponent("i-pentane", 0.008);
gas.addComponent("n-pentane", 0.010);
gas.addComponent("n-hexane", 0.015);

// C7+ fraction
gas.addTBPfraction("C7+", 0.025, 145.0, 0.78);

gas.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(gas);
ops.TPflash();

System.out.println("Gas fraction: " + gas.getGasPhase().getBeta());
System.out.println("C7+ in gas: " + gas.getGasPhase().getComponent("C7+_PC").getx());

Example 2: Oil Characterization

// Black oil with detailed C7+ split
SystemSrkEos oil = new SystemSrkEos(350.0, 50.0);

oil.addComponent("methane", 0.40);
oil.addComponent("ethane", 0.08);
oil.addComponent("propane", 0.06);
oil.addComponent("n-butane", 0.04);
oil.addComponent("n-pentane", 0.03);
oil.addComponent("n-hexane", 0.03);

// Detailed C7+ split
oil.addTBPfraction("C7", 0.05, 96.0, 0.727);
oil.addTBPfraction("C8", 0.05, 107.0, 0.749);
oil.addTBPfraction("C9", 0.04, 121.0, 0.768);
oil.addTBPfraction("C10", 0.04, 134.0, 0.782);
oil.addTBPfraction("C11", 0.03, 147.0, 0.793);
oil.addTBPfraction("C12+", 0.15, 250.0, 0.85);

oil.setMixingRule("classic");

Combining Methods

Combining and Re-Characterizing Fluids in NeqSim

This guide explains how to add a fluid into another's characterization framework in NeqSim, making them compatible for use with a common Equation of State (EOS).

Table of Contents

  1. Overview
  2. Mathematical Background
  3. Method 1: Simple Fluid Addition
  4. Method 2: Non-Destructive Addition
  5. Method 3: Unified Pseudo-Components
  6. Method 4: Re-Characterizing to Reference
  7. Method 5: Advanced Options
  8. Best Practices

Overview

When working with multiple reservoir fluids or combining fluids from different sources, it is often necessary to:

  1. Combine fluids with different pseudo-component definitions into a unified system
  2. Re-characterize a fluid to match another fluid's pseudo-component structure
  3. Add fluids together while preserving thermodynamic consistency

NeqSim provides several approaches based on the methods described in Pedersen et al., "Phase Behavior of Petroleum Reservoir Fluids".


Mathematical Background

The mathematical foundations for fluid combining and re-characterization are based on Pedersen et al. (2014), Chapters 5.5 and 5.6.

Mass and Mole Conservation

When combining fluids, total mass and moles must be conserved:

$$n_{total} = \sum_{f=1}^{N_f} \sum_{i=1}^{N_c} n_{f,i}$$

$$m_{total} = \sum_{f=1}^{N_f} \sum_{i=1}^{N_c} n_{f,i} \cdot M_{f,i}$$

where:

Pseudo-Component Property Weighting

For combining pseudo-components from multiple fluids into unified groups, NeqSim uses mass-weighted averaging for intensive properties.

Molar Mass

The combined molar mass of a pseudo-component group $g$ is:

$$M_g = \frac{m_g}{n_g} = \frac{\sum_{j \in g} m_j}{\sum_{j \in g} n_j}$$

where $m_j$ and $n_j$ are the mass and moles of contributing pseudo-component $j$.

Density (Normal Liquid Density)

Combined density uses volume-weighted averaging to ensure volume additivity:

$$\rho_g = \frac{m_g}{V_g} = \frac{\sum_{j \in g} m_j}{\sum_{j \in g} \frac{m_j}{\rho_j}}$$

Critical Temperature, Critical Pressure, and Acentric Factor

These properties use mass-weighted averaging within each group:

$$T_{c,g} = \frac{\sum_{j \in g} m_j \cdot T_{c,j}}{\sum_{j \in g} m_j}$$

$$P_{c,g} = \frac{\sum_{j \in g} m_j \cdot P_{c,j}}{\sum_{j \in g} m_j}$$

$$\omega_g = \frac{\sum_{j \in g} m_j \cdot \omega_j}{\sum_{j \in g} m_j}$$

Normal Boiling Point

$$T_{b,g} = \frac{\sum_{j \in g} m_j \cdot T_{b,j}}{\sum_{j \in g} m_j}$$

Multi-Fluid Mixing (Pedersen Chapter 5.5)

When combining multiple fluids with different pseudo-component structures, a two-level weighting scheme is applied:

  1. Fluid-level weighting: Each fluid contributes proportionally to its total mass
  2. Component-level weighting: Within each fluid, components contribute by their mole fraction

For property $\theta$ (such as $T_c$, $P_c$, $\omega$, or $T_b$) in combined group $g$:

$$\theta_g = \frac{\sum_{f=1}^{N_f} w_f \cdot x_{f,g} \cdot \theta_{f,g}}{\sum_{f=1}^{N_f} w_f \cdot x_{f,g}}$$

where:

Pseudo-Component Boundary Determination

NeqSim determines boundaries between pseudo-component groups using mass-based quantiles of the boiling point (or molar mass) distribution.

For $N_{PC}$ target pseudo-components:

$$M_{cumulative,k} = \frac{k}{N_{PC}} \cdot m_{total,pseudo}$$

where $k = 1, 2, ..., N_{PC}-1$ defines the boundary positions.

The boundary boiling point $T_{b,boundary,k}$ is the boiling point at which cumulative mass equals $M_{cumulative,k}$.

Characterization to Reference (Pedersen Chapter 5.6)

When re-characterizing a source fluid to match a reference fluid's pseudo-component structure:

  1. Extract boundary points from the reference fluid: $$T_{b,boundary,k} = \frac{T_{b,k} + T_{b,k+1}}{2}$$

    where $T_{b,k}$ is the boiling point of reference pseudo-component $k$.

  2. Redistribute source components into reference bins based on these boundaries

  3. Calculate weighted properties for each bin using the mass-weighted formulas above

Volume Shift and Density Consistency

When combining fluids, density is calculated to preserve volume additivity:

$$V_{total} = \sum_{j=1}^{N} \frac{m_j}{\rho_j}$$

$$\rho_{combined} = \frac{\sum_{j=1}^{N} m_j}{\sum_{j=1}^{N} \frac{m_j}{\rho_j}}$$

This is equivalent to the harmonic mean weighted by mass fractions.


Method 1: Using addFluid() for Simple Fluid Addition

The simplest way to add one fluid to another is using the addFluid() method. This works best when:

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermo.system.SystemInterface;

// Create first fluid
SystemInterface fluid1 = new SystemSrkEos(298.15, 50.0);
fluid1.addComponent("methane", 0.6);
fluid1.addComponent("ethane", 0.1);
fluid1.addTBPfraction("C7", 0.2, 0.100, 0.80);
fluid1.addTBPfraction("C10", 0.1, 0.200, 0.85);
fluid1.setMixingRule("classic");

// Create second fluid
SystemInterface fluid2 = new SystemSrkEos(298.15, 50.0);
fluid2.addComponent("methane", 0.4);
fluid2.addComponent("propane", 0.15);
fluid2.addTBPfraction("C8", 0.3, 0.120, 0.82);
fluid2.setMixingRule("classic");

// Add fluid2 to fluid1
fluid1.addFluid(fluid2);

// The combined fluid now contains all components from both fluids
// - Existing components (methane) have moles added together
// - New components (propane, C8_PC) are added with their properties

Key Behaviors of addFluid():


Method 2: Static addFluids() for Creating New Combined Fluid

To create a new fluid without modifying the originals:

import neqsim.thermo.system.SystemInterface;

// Create a new combined fluid (originals are unchanged)
SystemInterface combined = SystemInterface.addFluids(fluid1, fluid2);

This clones fluid1, then adds fluid2 to it.


Method 3: Combining Reservoir Fluids with Unified Pseudo-Components

When combining fluids with different pseudo-component characterizations, use combineReservoirFluids() to redistribute heavy fractions into a common pseudo-component structure:

import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemPrEos;
import neqsim.thermo.component.ComponentInterface;

// Fluid 1: Has C7 and C10 pseudo-components
SystemInterface fluid1 = new SystemPrEos(298.15, 50.0);
fluid1.addComponent("methane", 0.6);
fluid1.addComponent("ethane", 0.1);
fluid1.addTBPfraction("C7", 0.2, 0.100, 0.80);
ComponentInterface c7 = fluid1.getComponent("C7_PC");
c7.setNormalBoilingPoint(350.0);
c7.setTC(540.0);
c7.setPC(28.0);
c7.setAcentricFactor(0.32);

fluid1.addTBPfraction("C10", 0.1, 0.200, 0.85);
ComponentInterface c10 = fluid1.getComponent("C10_PC");
c10.setNormalBoilingPoint(410.0);
c10.setTC(620.0);
c10.setPC(22.0);
c10.setAcentricFactor(0.36);

// Fluid 2: Has C8 and C11 pseudo-components (different structure!)
SystemInterface fluid2 = new SystemPrEos(298.15, 50.0);
fluid2.addComponent("methane", 0.4);
fluid2.addComponent("n-butane", 0.05);
fluid2.addTBPfraction("C8", 0.15, 0.120, 0.82);
ComponentInterface c8 = fluid2.getComponent("C8_PC");
c8.setNormalBoilingPoint(380.0);
c8.setTC(580.0);
c8.setPC(26.0);
c8.setAcentricFactor(0.34);

fluid2.addTBPfraction("C11", 0.05, 0.220, 0.86);
ComponentInterface c11 = fluid2.getComponent("C11_PC");
c11.setNormalBoilingPoint(440.0);
c11.setTC(660.0);
c11.setPC(20.0);
c11.setAcentricFactor(0.38);

// Combine into 2 unified pseudo-components using Pedersen mixing weights
int targetPseudoComponents = 2;
SystemInterface combined = SystemInterface.combineReservoirFluids(
    targetPseudoComponents, 
    fluid1, 
    fluid2
);

// Result: Combined fluid has:
// - methane: 1.0 mol (summed)
// - ethane: 0.1 mol
// - n-butane: 0.05 mol  
// - PC1_PC: Lower boiling pseudo-component (weighted blend)
// - PC2_PC: Higher boiling pseudo-component (weighted blend)

How It Works (Pedersen et al., Chapter 5.5):

  1. Base components are summed directly
  2. All pseudo-components from all fluids are collected
  3. Quantile boundaries are calculated based on boiling points
  4. New pseudo-components are created with mass-weighted properties:
    • Molar mass
    • Density
    • Critical temperature (Tc)
    • Critical pressure (Pc)
    • Acentric factor
    • Normal boiling point

Mathematical Example

Consider combining two fluids with pseudo-components:

Fluid Component Moles MW (kg/mol) Mass (kg) Tb (K)
1 C7_PC 0.20 0.100 0.020 350
1 C10_PC 0.10 0.200 0.020 410
2 C8_PC 0.15 0.120 0.018 380
2 C11_PC 0.05 0.220 0.011 440

Step 1: Sort by boiling point: C7 (350K) → C8 (380K) → C10 (410K) → C11 (440K)

Step 2: For 2 target groups, find mass boundary at 50%:

Step 3: Group 1 (C7 + C8): $$M_1 = \frac{0.020 + 0.018}{0.20 + 0.15} = 0.1086 \text{ kg/mol}$$

$$T_{b,1} = \frac{0.020 \times 350 + 0.018 \times 380}{0.020 + 0.018} = 364.2 \text{ K}$$

Step 4: Group 2 (C10 + C11): $$M_2 = \frac{0.020 + 0.011}{0.10 + 0.05} = 0.207 \text{ kg/mol}$$

$$T_{b,2} = \frac{0.020 \times 410 + 0.011 \times 440}{0.020 + 0.011} = 420.6 \text{ K}$$


Method 4: Re-Characterizing to a Reference Fluid

When you need to make one fluid's characterization match another (e.g., for blending in simulation), use characterizeToReference():

import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemPrEos;

// Reference fluid defines the target pseudo-component structure
SystemInterface reference = new SystemPrEos(298.15, 50.0);
reference.addTBPfraction("C7", 0.10, 0.090, 0.78);
reference.getComponent("C7_PC").setNormalBoilingPoint(340.0);
reference.getComponent("C7_PC").setTC(530.0);
reference.getComponent("C7_PC").setPC(29.0);
reference.getComponent("C7_PC").setAcentricFactor(0.31);

reference.addTBPfraction("C8", 0.12, 0.110, 0.81);
reference.getComponent("C8_PC").setNormalBoilingPoint(360.0);
reference.getComponent("C8_PC").setTC(550.0);
reference.getComponent("C8_PC").setPC(27.0);
reference.getComponent("C8_PC").setAcentricFactor(0.33);

reference.addTBPfraction("C9", 0.15, 0.150, 0.84);
reference.getComponent("C9_PC").setNormalBoilingPoint(380.0);
reference.getComponent("C9_PC").setTC(570.0);
reference.getComponent("C9_PC").setPC(25.0);
reference.getComponent("C9_PC").setAcentricFactor(0.35);

// Source fluid has different characterization
SystemInterface source = new SystemPrEos(298.15, 50.0);
source.addComponent("methane", 0.7);
source.addTBPfraction("S1", 0.05, 0.090, 0.79);
source.addTBPfraction("S2", 0.07, 0.095, 0.80);
source.addTBPfraction("S3", 0.08, 0.120, 0.82);
source.addTBPfraction("S4", 0.09, 0.150, 0.83);

// Re-characterize source to match reference's pseudo-component structure
SystemInterface characterized = SystemInterface.characterizeToReference(source, reference);

// Result: characterized has C7_PC, C8_PC, C9_PC (same names as reference)
// with properties redistributed from S1-S4 components

How It Works (Pedersen et al., Chapter 5.6):

  1. Base components from source are preserved
  2. Reference fluid's pseudo-component cut points define boundaries
  3. Source pseudo-components are redistributed into reference's structure
  4. Mass is conserved; properties are weighted by contribution

Mathematical Formulation

Given a reference fluid with $N$ pseudo-components having boiling points $T_{b,1} < T_{b,2} < ... < T_{b,N}$:

Step 1: Calculate boundary boiling points: $$T_{boundary,k} = \frac{T_{b,k} + T_{b,k+1}}{2}, \quad k = 1, ..., N-1$$

Step 2: Assign source pseudo-components to bins:

Step 3: Calculate properties for each bin using mass-weighted averaging: $$\theta_{bin,k} = \frac{\sum_{j \in bin_k} m_j \cdot \theta_j}{\sum_{j \in bin_k} m_j}$$

The resulting fluid has pseudo-components with the same names as the reference (C7_PC, C8_PC, etc.) but with properties derived from the source fluid's heavy fractions.


Method 5: Advanced Characterization with Options

For more control, use CharacterizationOptions:

import neqsim.thermo.characterization.PseudoComponentCombiner;
import neqsim.thermo.characterization.CharacterizationOptions;

CharacterizationOptions options = new CharacterizationOptions();
options.setTransferBinaryInteractionParameters(true);  // Copy BIPs from reference
options.setNormalizeComposition(true);                  // Normalize mole fractions
options.setGenerateValidationReport(true);              // Log validation info

SystemInterface characterized = PseudoComponentCombiner.characterizeToReference(
    source, 
    reference, 
    options
);

Transferring Binary Interaction Parameters

BIPs are critical for accurate phase behavior. When characterizing to a reference:

import neqsim.thermo.characterization.PseudoComponentCombiner;

// Transfer BIPs from reference to characterized fluid
PseudoComponentCombiner.transferBinaryInteractionParameters(reference, characterized);

// BIPs are matched by:
// - Component name for base components
// - Position for pseudo-components (1st PC to 1st PC, etc.)

Best Practices

1. Always Use the Same EOS Type

// Correct: Both use Peng-Robinson
SystemInterface fluid1 = new SystemPrEos(298.15, 50.0);
SystemInterface fluid2 = new SystemPrEos(298.15, 50.0);

// Caution: Mixing EOS types may cause inconsistencies
// SystemInterface fluid1 = new SystemSrkEos(298.15, 50.0);
// SystemInterface fluid2 = new SystemPrEos(298.15, 50.0);

2. Set Complete Pseudo-Component Properties

fluid.addTBPfraction("C7", moles, molarMass, density);
ComponentInterface pc = fluid.getComponent("C7_PC");
pc.setNormalBoilingPoint(350.0);  // Important for redistribution
pc.setTC(540.0);
pc.setPC(28.0);
pc.setAcentricFactor(0.32);

3. Initialize and Set Mixing Rules

fluid.setMixingRule("classic");
// or
fluid.setMixingRule(2);  // Numeric code for specific mixing rule

4. Validate After Combining

combined.init(0);  // Initialize mole numbers
combined.init(1);  // Initialize thermodynamic properties

// Check total mass conservation
double originalMass = fluid1.getMolarMass() * fluid1.getTotalNumberOfMoles()
                    + fluid2.getMolarMass() * fluid2.getTotalNumberOfMoles();
double combinedMass = combined.getMolarMass() * combined.getTotalNumberOfMoles();
// These should be equal within tolerance

Summary Table

Method Use Case Creates New Fluid? Modifies Original?
fluid1.addFluid(fluid2) Simple addition No Yes (fluid1)
SystemInterface.addFluids(f1, f2) Non-destructive addition Yes No
combineReservoirFluids(n, fluids...) Unified characterization Yes No
characterizeToReference(src, ref) Match reference structure Yes No

References


Appendix: Complete List of Mass-Weighted Properties

When combining pseudo-components, NeqSim calculates mass-weighted averages for the following properties:

Property Symbol Unit Weighting Method
Molar Mass $M$ kg/mol Mass/Moles ratio
Normal Liquid Density $\rho$ kg/m³ Volume-weighted (harmonic)
Normal Boiling Point $T_b$ K Mass-weighted
Critical Temperature $T_c$ K Mass-weighted
Critical Pressure $P_c$ bar Mass-weighted
Acentric Factor $\omega$ - Mass-weighted
Critical Volume $V_c$ m³/kmol Mass-weighted
Rackett Z-factor $Z_{RA}$ - Mass-weighted
Parachor $P$ - Mass-weighted
Critical Viscosity $\mu_c$ Pa·s Mass-weighted
Triple Point Temperature $T_{tp}$ K Mass-weighted
Heat of Fusion $\Delta H_f$ J/mol Mass-weighted
Ideal Gas Enthalpy of Formation $\Delta H_f^{ig}$ J/mol Mass-weighted
Heat Capacity Coefficients $C_{p,A}, C_{p,B}, C_{p,C}, C_{p,D}$ various Mass-weighted
EOS Attractive Term Parameter $m$ - Mass-weighted

See Also

Char Mathematics

Fluid Characterization in NeqSim: Mathematical Foundations

This document provides detailed mathematical documentation of the fluid characterization methods implemented in NeqSim, with emphasis on plus fraction (C7+) modeling, TBP fraction property correlations, and pseudo-component generation.

Table of Contents

  1. Overview
  2. Plus Fraction Distribution Models
  3. Critical Property Correlations
  4. Density Correlations
  5. Boiling Point Correlations
  6. Lumping Methods
  7. Usage Examples
  8. Common Fluid Characterization

Overview

Petroleum fluids contain thousands of hydrocarbon compounds. For practical thermodynamic calculations, heavy fractions (typically C7+) must be characterized using continuous distribution functions or discrete pseudo-components. NeqSim implements industry-standard characterization methods from Whitson (1983), Pedersen et al. (1984), and others.

The characterization workflow consists of three main steps:

  1. Plus Fraction Splitting: Distribute the C7+ fraction into discrete Single Carbon Number (SCN) groups
  2. Property Estimation: Calculate critical properties (Tc, Pc, ω) for each pseudo-component
  3. Lumping: Combine pseudo-components into computationally efficient groups

Plus Fraction Distribution Models

Whitson Gamma Distribution Model

The Whitson gamma distribution (SPE 12233, 1983) is the most widely used continuous distribution for petroleum C7+ fractions.

Probability Density Function

The molar distribution of the plus fraction is described by a three-parameter gamma distribution:

$$p(M) = \frac{(M - \eta)^{\alpha - 1}}{\beta^\alpha \cdot \Gamma(\alpha)} \exp\left(-\frac{M - \eta}{\beta}\right)$$

where:

Gamma Function Approximation

NeqSim uses a polynomial approximation for the gamma function:

$$\Gamma(\alpha) \approx \alpha \cdot \left(1 + \sum_{i=1}^{8} b_i \cdot (\alpha - 1)^i\right)$$

with coefficients:

$i$ $b_i$
1 -0.577191652
2 0.988205891
3 -0.897056937
4 0.918206857
5 -0.756704078
6 0.482199394
7 -0.193527818
8 0.035868343

Parameter Relationships

The mean of the gamma distribution equals the plus fraction average molecular weight:

$$\bar{M}_{C7+} = \eta + \alpha \cdot \beta$$

Therefore, the scale parameter is calculated as:

$$\beta = \frac{\bar{M}_{C7+} - \eta}{\alpha}$$

Cumulative Distribution Functions

For splitting the plus fraction into SCN groups, NeqSim computes:

$$P_0(M_b) = \int_\eta^{M_b} p(M) \, dM = Q(M_b) \cdot S(M_b)$$

where:

$$Q = \frac{\exp(-Y) \cdot Y^\alpha}{\Gamma(\alpha)}, \quad Y = \frac{M_b - \eta}{\beta}$$

$$S = \sum_{j=0}^{\infty} \frac{Y^j}{\prod_{k=0}^{j}(\alpha + k)} = \frac{1}{\alpha} + \frac{Y}{\alpha(\alpha+1)} + \frac{Y^2}{\alpha(\alpha+1)(\alpha+2)} + \cdots$$

The first moment $P_1$ is used for average molecular weight calculation:

$$P_1(M_b) = Q(M_b) \cdot \left(S(M_b) - \frac{1}{\alpha}\right)$$

Mole Fraction of SCN Group

For an SCN group bounded by molecular weights $M_L$ (lower) and $M_U$ (upper):

$$z_i = z_{C7+} \cdot \left[P_0(M_U) - P_0(M_L)\right]$$

Average Molecular Weight of SCN Group

$$\bar{M}_i = \eta + \alpha \cdot \beta \cdot \frac{P_1(M_U) - P_1(M_L)}{P_0(M_U) - P_0(M_L)}$$

Shape Parameter Guidelines

The shape parameter $\alpha$ characterizes the fluid type:

Fluid Type Typical $\alpha$ Range Watson $K_w$
Gas condensates 0.5 - 1.0 > 12.5
Black oils 1.0 - 2.0 11.5 - 12.5
Heavy/naphthenic oils 2.0 - 4.0 < 11.5

Automatic Alpha Estimation

NeqSim can automatically estimate $\alpha$ using the Watson characterization factor:

$$K_w = 4.5579 \cdot M_{C7+}^{0.15178} \cdot \rho_{C7+}^{-1.18241}$$

where $\rho$ is specific gravity (g/cm³). The estimated alpha is:

$$\alpha = \begin{cases} 0.5 + 0.1(K_w - 12.5) & K_w \geq 12.5 \text{ (paraffinic)} \ 1.0 + 0.5(K_w - 11.5) & 11.5 \leq K_w < 12.5 \text{ (mixed)} \ 1.5 + 0.5(K_w - 10.5) & 10.5 \leq K_w < 11.5 \text{ (naphthenic)} \ 2.0 + 0.5(10.5 - K_w) & K_w < 10.5 \text{ (aromatic)} \end{cases}$$


Pedersen Exponential Model

The Pedersen model (Pedersen et al., 1984) uses an exponential distribution:

$$\ln(z_n) = A + B \cdot n$$

where:

The coefficients are determined by matching:

  1. The plus fraction mole fraction $z_{C7+}$
  2. The plus fraction molecular weight $M_{C7+}$
  3. The plus fraction density $\rho_{C7+}$

Density Correlation (Pedersen)

$$\rho_n = C_1 + C_2 \cdot \ln(n)$$

where $C_1$ and $C_2$ are fitted to match the measured plus fraction density.


Critical Property Correlations

Pedersen Correlations

For SRK equation of state, critical properties are calculated using Pedersen et al. correlations:

Critical Temperature (K)

$$T_c = a_0 \cdot \rho + a_1 \cdot \ln(M) + a_2 \cdot M + \frac{a_3}{M}$$

Coefficient Light Oil (M < 1120 g/mol) Heavy Oil
$a_0$ 163.12 830.63
$a_1$ 86.052 17.5228
$a_2$ 0.43475 0.0455911
$a_3$ -1877.4 -11348.4

Critical Pressure (bar)

$$P_c = \exp\left(0.01325 + b_0 + b_1 \cdot \rho^{b_4} + \frac{b_2}{M} + \frac{b_3}{M^2}\right)$$

Coefficient Light Oil Heavy Oil
$b_0$ -0.13408 0.802988
$b_1$ 2.5019 1.78396
$b_2$ 208.46 156.740
$b_3$ -3987.2 -6965.59
$b_4$ 1.0 0.25

Acentric Factor (SRK m-parameter)

$$m = c_0 + c_1 \cdot M + c_2 \cdot \rho + c_3 \cdot M^2$$

For Peng-Robinson, different coefficients are used.


Riazi-Daubert Correlations

Alternative correlations based on molecular weight and specific gravity:

Critical Temperature (K)

$$T_c = \frac{5}{9} \cdot 554.4 \cdot \exp(-1.3478 \times 10^{-4} M - 0.61641 \cdot \rho) \cdot M^{0.2998} \cdot \rho^{1.0555}$$

Critical Pressure (bar)

$$P_c = 0.068947 \cdot 4.5203 \times 10^4 \cdot \exp(-1.8078 \times 10^{-3} M - 0.3084 \cdot \rho) \cdot M^{-0.8063} \cdot \rho^{1.6015}$$


Kesler-Lee Correlations

Acentric Factor

For $T_{br} = T_b/T_c < 0.8$:

$$\omega = \frac{\ln(P_{br}) - 5.92714 + \frac{6.09649}{T_{br}} + 1.28862 \ln(T_{br}) - 0.169347 T_{br}^6}{15.2518 - \frac{15.6875}{T_{br}} - 13.4721 \ln(T_{br}) + 0.43577 T_{br}^6}$$

For $T_{br} \geq 0.8$:

$$\omega = -7.904 + 0.1352 K_w - 0.007465 K_w^2 + 8.359 T_{br} + \frac{1.408 - 0.01063 K_w}{T_{br}}$$

where $P_{br} = 1.01325 / P_c$ and $K_w = T_b^{1/3} / \rho$ is the Watson K-factor.


Density Correlations

Watson K-Factor Method (UOP)

The Universal Oil Products (UOP) characterization assumes constant Watson K for all SCN groups:

$$K_w = 4.5579 \cdot M_{C7+}^{0.15178} \cdot \rho_{C7+}^{-1.18241}$$

Individual SCN densities:

$$\rho_i = 6.0108 \cdot M_i^{0.17947} \cdot K_w^{-1.18241}$$

Limitation: Less accurate for heavy fractions (C20+).


Søreide Correlation

Søreide (1989) developed a more accurate correlation for heavy fractions:

$$SG = 0.2855 + C_f \cdot (M - 66)^{0.13}$$

where $SG$ is specific gravity (same as $\rho$ in g/cm³) and $C_f$ is calculated from the plus fraction:

$$C_f = \frac{SG_{C7+} - 0.2855}{(M_{C7+} - 66)^{0.13}}$$

Individual SCN densities:

$$\rho_i = 0.2855 + C_f \cdot (M_i - 66)^{0.13}$$

Constrained to physical limits: $0.6 \leq \rho_i \leq 1.2$ g/cm³.


Boiling Point Correlations

Søreide Correlation

$$T_b = \frac{1}{1.8}\left(1928.3 - 1.695 \times 10^5 \cdot M^{-0.03522} \cdot \rho^{3.266} \cdot \exp\left(-4.922 \times 10^{-3} M - 4.7685 \rho + 3.462 \times 10^{-3} M \cdot \rho\right)\right)$$

Riazi-Daubert Correlation

$$T_b = \left(\frac{M}{5.805 \times 10^{-5} \cdot \rho^{0.9371}}\right)^{1/2.3776}$$

Polynomial Correlation (Light Components)

For $M < 540$ g/mol:

$$T_b = 2 \times 10^{-6} M^3 - 0.0035 M^2 + 2.4003 M + 171.74$$


Lumping Methods

Standard Weight-Based Lumping

SCN pseudo-components are grouped into $N$ lumps with approximately equal weight fractions:

$$w_{target} = \frac{\sum_i z_i \cdot M_i}{N}$$

For each lump $k$, the properties are averaged:

Mole Fraction

$$z_k = \sum_{i \in k} z_i$$

Molecular Weight

$$M_k = \frac{\sum_{i \in k} z_i \cdot M_i}{z_k}$$

Density (Volume-Weighted)

$$\rho_k = \frac{\sum_{i \in k} z_i \cdot M_i}{\sum_{i \in k} \frac{z_i \cdot M_i}{\rho_i}}$$

Critical Properties

Mixing rules (typically mole-fraction weighted):

$$T_{c,k} = \sum_{i \in k} \frac{z_i}{z_k} \cdot T_{c,i}$$

$$P_{c,k} = \sum_{i \in k} \frac{z_i}{z_k} \cdot P_{c,i}$$

$$\omega_k = \sum_{i \in k} \frac{z_i}{z_k} \cdot \omega_i$$


Usage Examples

Basic Whitson Gamma Characterization

import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemSrkEos;

// Create fluid with plus fraction
SystemInterface fluid = new SystemSrkEos(350.0, 150.0);
fluid.addComponent("methane", 0.70);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.addPlusFraction("C7+", 0.15, 150.0 / 1000.0, 0.82);  // MW in kg/mol, density in g/cm³
fluid.setMixingRule(2);

// Configure Whitson Gamma Model
fluid.getCharacterization()
    .setPlusFractionModel("Whitson Gamma Model")
    .setGammaShapeParameter(1.5)      // alpha for black oil
    .setGammaMinMW(84.0)              // eta for C7+
    .setGammaDensityModel("Soreide"); // Use Søreide density correlation

// Run characterization
fluid.getCharacterization().characterisePlusFraction();

Automatic Alpha Estimation

// Let NeqSim estimate alpha based on Watson K-factor
fluid.getCharacterization()
    .setPlusFractionModel("Whitson Gamma Model")
    .setAutoEstimateGammaAlpha(true)
    .setGammaDensityModel("Soreide");

fluid.getCharacterization().characterisePlusFraction();

// Check the estimated alpha
double alpha = ((PlusFractionModel.WhitsonGammaModel) 
    fluid.getCharacterization().getPlusFractionModel()).getAlpha();
double watsonK = ((PlusFractionModel.WhitsonGammaModel) 
    fluid.getCharacterization().getPlusFractionModel()).getWatsonKFactor();

System.out.println("Estimated alpha: " + alpha);
System.out.println("Watson K-factor: " + watsonK);

Pedersen Model with Lumping

fluid.getCharacterization()
    .setPlusFractionModel("Pedersen")
    .setLumpingModel("Standard");

fluid.getCharacterization().getLumpingModel().setNumberOfPseudoComponents(6);
fluid.getCharacterization().characterisePlusFraction();

Accessing Characterized Data

PlusFractionModelInterface model = fluid.getCharacterization().getPlusFractionModel();

double[] moleFractions = model.getZ();
double[] molecularWeights = model.getM();
double[] densities = model.getDens();

for (int i = model.getFirstPlusFractionNumber(); i < model.getLastPlusFractionNumber(); i++) {
    if (moleFractions[i] > 1e-10) {
        System.out.printf("SCN %d: z=%.6f, M=%.1f g/mol, rho=%.4f g/cm³%n",
            i, moleFractions[i], molecularWeights[i] * 1000, densities[i]);
    }
}

PVT Regression Framework

This section documents the PVT regression framework for automatic EOS model tuning based on experimental PVT report data. The framework is implemented in the neqsim.pvtsimulation.regression package.

Package Overview

Class Description
PVTRegression Main regression framework class
RegressionParameter Enum defining tunable parameters (BIPs, volume shifts, critical properties)
ExperimentType Enum for experiment types (CCE, CVD, DLE, SEPARATOR, etc.)
CCEDataPoint, CVDDataPoint, DLEDataPoint, SeparatorDataPoint Data point classes for each experiment type
RegressionParameterConfig Configuration for each regression parameter with bounds
PVTRegressionFunction Objective function extending LevenbergMarquardtFunction
RegressionResult Result container with tuned fluid and uncertainty analysis
UncertaintyAnalysis Statistical uncertainty quantification

Overview

PVT regression involves adjusting equation of state parameters to minimize the deviation between calculated and experimental properties. The framework handles multiple experiment types simultaneously while maintaining physical consistency.

Multi-Objective Regression

Objective Function

The total objective function combines weighted contributions from different PVT experiments:

$$F_{obj} = \sum_{k} w_k \cdot F_k$$

where $w_k$ are user-defined weights and $F_k$ are individual experiment objective functions.

Constant Composition Expansion (CCE)

$$F_{CCE} = \sum_{i=1}^{N_{CCE}} \left[ \left(\frac{V_{rel,i}^{calc} - V_{rel,i}^{exp}}{V_{rel,i}^{exp}}\right)^2 + \lambda_Y \left(\frac{Y_i^{calc} - Y_i^{exp}}{Y_i^{exp}}\right)^2 \right]$$

where:

Key match points:

Constant Volume Depletion (CVD)

$$F_{CVD} = \sum_{i=1}^{N_{CVD}} \left[ \left(\frac{L_i^{calc} - L_i^{exp}}{L_i^{exp}}\right)^2 + \left(\frac{Z_i^{calc} - Z_i^{exp}}{Z_i^{exp}}\right)^2 + \sum_{j} \left(\frac{y_{j,i}^{calc} - y_{j,i}^{exp}}{y_{j,i}^{exp}}\right)^2 \right]$$

where:

Key match points:

Differential Liberation Expansion (DLE)

$$F_{DLE} = \sum_{i=1}^{N_{DLE}} \left[ \left(\frac{R_{s,i}^{calc} - R_{s,i}^{exp}}{R_{s,i}^{exp}}\right)^2 + \left(\frac{B_{o,i}^{calc} - B_{o,i}^{exp}}{B_{o,i}^{exp}}\right)^2 + \left(\frac{\rho_{o,i}^{calc} - \rho_{o,i}^{exp}}{\rho_{o,i}^{exp}}\right)^2 \right]$$

where:

Key match points:

Separator Test

$$F_{SEP} = \left(\frac{GOR^{calc} - GOR^{exp}}{GOR^{exp}}\right)^2 + \left(\frac{B_o^{calc} - B_o^{exp}}{B_o^{exp}}\right)^2 + \left(\frac{API^{calc} - API^{exp}}{API^{exp}}\right)^2$$

Viscosity (Optional)

$$F_{\mu} = \sum_{i} \left(\frac{\mu_i^{calc} - \mu_i^{exp}}{\mu_i^{exp}}\right)^2$$


Automatic Binary Interaction Parameter (BIP) Optimization

BIP Matrix Structure

For a system with $N_c$ components, the symmetric BIP matrix has $N_c(N_c-1)/2$ independent parameters. To reduce dimensionality, group-based BIPs are used:

Group Pairs Typical Starting $k_{ij}$ Regression Range
CH₄ - C₂-C₆ 0.00 - 0.02 Fixed or narrow
CH₄ - C7+ 0.02 - 0.05 Primary target
CO₂ - HC 0.10 - 0.15 If CO₂ present
N₂ - HC 0.04 - 0.08 If N₂ present
H₂S - HC 0.05 - 0.10 If H₂S present
C7+ - C7+ 0.00 Usually fixed

BIP Correlation (Carbon Number Based)

For C7+ pseudo-components, BIPs can be correlated:

$$k_{ij} = k_{CH_4-C7+} \cdot \left(1 - \left(\frac{2\sqrt{T_{c,i} \cdot T_{c,j}}}{T_{c,i} + T_{c,j}}\right)^n\right)$$

where $n$ is a tunable exponent (typically 0.5-2.0).

Optimization Strategy

  1. Level 1: Tune $k_{CH_4-C7+}$ to match saturation pressure
  2. Level 2: Tune $k_{C_2-C_6, C7+}$ to improve phase envelope shape
  3. Level 3: Tune correlation exponent $n$ for density matching

Volume Translation Parameter Tuning

Peneloux Volume Shift

The Peneloux (1982) volume translation corrects liquid density without affecting VLE:

$$V_{corrected} = V_{EOS} - \sum_i x_i \cdot c_i$$

where $c_i$ is the component volume shift parameter.

Regression Approach

For pseudo-components, express volume shift as:

$$c_i = c_0 + c_1 \cdot M_i + c_2 \cdot M_i^2$$

Objective: Minimize density deviation in single-phase liquid region:

$$F_c = \sum_{i} \left(\frac{\rho_i^{calc} - \rho_i^{exp}}{\rho_i^{exp}}\right)^2$$

Rackett Equation Alternative

$$Z_{RA,i} = Z_{RA}^{ref} + a \cdot (M_i - M_{ref})$$

where $Z_{RA}$ is the Rackett compressibility factor and $a$ is a tunable coefficient.


Critical Property Regression for Pseudo-Components

Parameterization

Instead of regressing individual $T_c$, $P_c$, $\omega$ values, tune the correlation coefficients:

Critical Temperature: $$T_c = (a_0 + \Delta a_0) \cdot \rho + (a_1 + \Delta a_1) \cdot \ln(M) + a_2 \cdot M + \frac{a_3}{M}$$

Critical Pressure: $$\ln(P_c) = (b_0 + \Delta b_0) + b_1 \cdot \rho^{b_4} + \frac{b_2}{M} + \frac{b_3}{M^2}$$

Acentric Factor: $$\omega = (\omega_{base} + \Delta\omega) \cdot f(M, \rho)$$

where $\Delta a_0$, $\Delta a_1$, $\Delta b_0$, $\Delta\omega$ are regression parameters.

Constraints

Physical bounds must be enforced:

$$T_c > T_b > 0$$ $$P_c > 0$$ $$0 < \omega < 2$$ $$\frac{\partial T_c}{\partial M} > 0 \text{ (monotonic increase)}$$


Uncertainty Quantification

Parameter Sensitivity Analysis

Compute the Jacobian matrix at the optimum:

$$J_{ij} = \frac{\partial F_i}{\partial \theta_j}$$

where $F_i$ are individual residuals and $\theta_j$ are parameters.

Covariance Matrix

$$\text{Cov}(\theta) = s^2 \cdot (J^T J)^{-1}$$

where $s^2$ is the residual variance:

$$s^2 = \frac{F_{obj}}{N_{data} - N_{params}}$$

Confidence Intervals

95% confidence interval for parameter $\theta_j$:

$$\theta_j \pm t_{0.975, N-p} \cdot \sqrt{\text{Cov}(\theta)_{jj}}$$

Monte Carlo Uncertainty Propagation

  1. Sample parameters from multivariate normal: $\theta \sim N(\hat{\theta}, \text{Cov}(\theta))$
  2. Run flash calculations for each sample
  3. Compute prediction intervals for properties:

$$P_{95\%} = \left[\mu - 1.96\sigma, \mu + 1.96\sigma\right]$$


API Usage Example

import neqsim.pvtsimulation.regression.*;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemSrkEos;

// Create base fluid
SystemInterface fluid = new SystemSrkEos(373.15, 200.0);
fluid.addComponent("methane", 0.70);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.addComponent("n-pentane", 0.05);
fluid.addPlusFraction("C7+", 0.10, 0.150, 0.82);
fluid.setMixingRule(2);

// Create PVT regression framework
PVTRegression regression = new PVTRegression(fluid);

// Add experimental CCE data
double[] pressures = {300.0, 250.0, 200.0, 150.0, 100.0};
double[] relativeVolumes = {0.98, 1.00, 1.08, 1.25, 1.55};
regression.addCCEData(pressures, relativeVolumes, 373.15);

// Add DLE data
double[] dlePressures = {250.0, 200.0, 150.0, 100.0};
double[] rs = {150.0, 120.0, 85.0, 50.0};
double[] bo = {1.45, 1.38, 1.30, 1.20};
double[] oilDensity = {720.0, 740.0, 760.0, 780.0};
regression.addDLEData(dlePressures, rs, bo, oilDensity, 373.15);

// Configure regression parameters (with custom bounds or use defaults)
regression.addRegressionParameter(RegressionParameter.BIP_METHANE_C7PLUS, 0.0, 0.10, 0.03);
regression.addRegressionParameter(RegressionParameter.VOLUME_SHIFT_C7PLUS);  // Uses defaults

// Set weights for multi-objective optimization
regression.setExperimentWeight(ExperimentType.CCE, 1.0);
regression.setExperimentWeight(ExperimentType.DLE, 1.5);  // Prioritize DLE matching

// Configure optimization
regression.setMaxIterations(100);
regression.setVerbose(true);

// Run regression
RegressionResult result = regression.runRegression();

// Get tuned fluid
SystemInterface tunedFluid = result.getTunedFluid();

// Get optimized parameter values
double optimizedBIP = result.getOptimizedValue(RegressionParameter.BIP_METHANE_C7PLUS);
System.out.println("Optimized BIP (CH4-C7+): " + optimizedBIP);

// Uncertainty analysis
UncertaintyAnalysis uncertainty = result.getUncertainty();
double[] ci = uncertainty.getConfidenceIntervalBounds(0);
System.out.println("95% CI for BIP: [" + ci[0] + ", " + ci[1] + "]");

// Generate summary report
String summary = result.generateSummary();
System.out.println(summary);

Available Regression Parameters

Parameter Description Default Bounds
BIP_METHANE_C7PLUS BIP between methane and C7+ fractions [0.0, 0.10, 0.03]
BIP_C2C6_C7PLUS BIP between C2-C6 and C7+ fractions [0.0, 0.05, 0.01]
BIP_CO2_HC BIP between CO₂ and hydrocarbons [0.08, 0.18, 0.12]
BIP_N2_HC BIP between N₂ and hydrocarbons [0.02, 0.12, 0.05]
VOLUME_SHIFT_C7PLUS Volume shift multiplier for C7+ [0.8, 1.2, 1.0]
TC_MULTIPLIER_C7PLUS Critical temperature multiplier [0.95, 1.05, 1.0]
PC_MULTIPLIER_C7PLUS Critical pressure multiplier [0.95, 1.05, 1.0]
OMEGA_MULTIPLIER_C7PLUS Acentric factor multiplier [0.90, 1.10, 1.0]
PLUS_MOLAR_MASS_MULTIPLIER Plus fraction MW multiplier [0.90, 1.10, 1.0]
GAMMA_ALPHA Gamma distribution shape parameter [0.5, 4.0, 1.0]
GAMMA_ETA Gamma distribution minimum MW [75.0, 95.0, 84.0]

Data Input Format (CSV)

CCE Data:

Pressure(bara),RelativeVolume,YFactor
350.0,0.9850,
300.0,0.9912,
250.0,1.0000,  # Saturation point
200.0,1.0523,1.0234
150.0,1.1876,1.0456

DLE Data:

Pressure(bara),Rs(Sm3/Sm3),Bo(m3/Sm3),OilDensity(kg/m3),GasGravity
250.0,150.5,1.425,725.3,0.85
200.0,120.2,1.380,742.1,0.82
150.0,85.6,1.312,761.5,0.79

Implementation Status

Phase Feature Status
1 CCE/DLE simulation with objective function ✅ Implemented
2 BIP regression for saturation pressure ✅ Implemented
3 Volume translation optimization ✅ Implemented
4 CVD simulation and regression ✅ Implemented
5 Critical property correlation tuning ✅ Implemented
6 Multi-objective optimization framework ✅ Implemented
7 Uncertainty quantification ✅ Implemented
8 GUI/Report generation 🔲 Future work

Common Fluid Characterization (Matching Pseudo-Components)

When working with multiple reservoir fluids in a simulation model (e.g., compositional reservoir simulation, commingled production), all fluids must share the same pseudo-component (PC) structure. NeqSim provides utilities for this workflow based on Pedersen et al. (Chapter 5.5-5.6).

PseudoComponentCombiner Utility

The PseudoComponentCombiner class provides methods for matching fluid characterizations:

import neqsim.thermo.characterization.PseudoComponentCombiner;

// Match source fluid to reference's PC structure
SystemInterface matched = PseudoComponentCombiner.characterizeToReference(
    sourceFluid, referenceFluid);

// Combine multiple fluids with automatic common PC structure
SystemInterface combined = PseudoComponentCombiner.combineReservoirFluids(
    Arrays.asList(fluid1, fluid2, fluid3),
    Arrays.asList(0.5, 0.3, 0.2));  // volume fractions

CharacterizationOptions

For advanced control, use the CharacterizationOptions builder:

import neqsim.thermo.characterization.CharacterizationOptions;
import neqsim.thermo.characterization.CharacterizationOptions.NamingScheme;

CharacterizationOptions options = CharacterizationOptions.builder()
    .transferBinaryInteractionParameters(true)  // Copy BIPs from reference
    .normalizeComposition(true)                 // Ensure mole fractions sum to 1.0
    .namingScheme(NamingScheme.REFERENCE)       // Use reference component names
    .generateValidationReport(true)             // Create before/after comparison
    .build();

SystemInterface matched = PseudoComponentCombiner.characterizeToReference(
    sourceFluid, referenceFluid, options);
Option Description Default
transferBinaryInteractionParameters Copy BIPs from reference fluid false
normalizeComposition Normalize mole fractions to sum to 1.0 true
namingScheme Use SOURCE, REFERENCE, or MERGED names REFERENCE
generateValidationReport Generate validation report false

BIP Transfer

Binary Interaction Parameters (BIPs) can be transferred between fluids:

// Transfer BIPs during characterization
PseudoComponentCombiner.transferBinaryInteractionParameters(
    sourceFluid, referenceFluid);

// Fluent API on Characterise class
SystemInterface fluid = new SystemSrkEos(298, 50);
fluid.addComponent("methane", 0.7);
fluid.addPlusFraction("C7+", 0.3, 0.200, 0.85);
fluid.getCharacterization()
    .setTBPModel("PedersenSRK")
    .characterize()
    .transferBipsFrom(tunedReferenceFluid);

Validation Reports

The CharacterizationValidationReport provides before/after comparison:

CharacterizationValidationReport report = 
    PseudoComponentCombiner.generateValidationReport(sourceFluid, matchedFluid);

System.out.println("Mass conserved: " + report.isMassConserved());
System.out.println("Moles conserved: " + report.isMolesConserved());
System.out.println("PC count before: " + report.getSourcePseudoComponentCount());
System.out.println("PC count after: " + report.getResultPseudoComponentCount());
System.out.println(report.toReportString());

Mathematical Background

When matching a source fluid to a reference PC structure:

  1. Component Mapping: Discrete components (C1, C2, CO₂, etc.) are mapped directly
  2. PC Redistribution: Plus fraction moles are redistributed proportionally across reference PCs:

$$z_i^{matched} = z_{C7+}^{source} \cdot \frac{z_i^{ref}}{\sum_{j \in PC} z_j^{ref}}$$

  1. Mass Conservation: Total mass is preserved through the redistribution
  2. BIP Transfer: For EOS phases, BIPs are copied element-by-element from reference

References

  1. Whitson, C.H. (1983). "Characterizing Hydrocarbon Plus Fractions." SPE Journal, 23(4), 683-694. SPE-12233-PA.

  2. Pedersen, K.S., Thomassen, P., and Fredenslund, A. (1984). "Thermodynamics of Petroleum Mixtures Containing Heavy Hydrocarbons. 1. Phase Envelope Calculations by Use of the Soave-Redlich-Kwong Equation of State." Industrial & Engineering Chemistry Process Design and Development, 23(1), 163-170.

  3. Søreide, I. (1989). "Improved Phase Behavior Predictions of Petroleum Reservoir Fluids from a Cubic Equation of State." Dr.Ing. Thesis, Norwegian Institute of Technology (NTH), Trondheim.

  4. Riazi, M.R. and Daubert, T.E. (1980). "Simplify Property Predictions." Hydrocarbon Processing, 59(3), 115-116.

  5. Kesler, M.G. and Lee, B.I. (1976). "Improve Prediction of Enthalpy of Fractions." Hydrocarbon Processing, 55(3), 153-158.

  6. Whitson, C.H. and Brulé, M.R. (2000). "Phase Behavior." SPE Monograph Series, Vol. 20. Society of Petroleum Engineers.


Appendix A: Unit Conventions in NeqSim

Property Internal Unit Common Input Unit
Molecular weight kg/mol g/mol (÷1000)
Density g/cm³ g/cm³ or kg/m³ (÷1000)
Temperature K °C (+273.15)
Pressure bar bara
Critical temperature K K
Critical pressure bar bar

Note: When using addPlusFraction(), molecular weight should be in kg/mol and density in g/cm³ (specific gravity).


Appendix B: Mathematical Notation Summary

Symbol Description Typical Range
$\alpha$ Gamma shape parameter 0.5 - 4.0
$\beta$ Gamma scale parameter Calculated
$\eta$ Minimum molecular weight 84 - 90 g/mol
$M$ Molecular weight g/mol
$\rho$ Density (specific gravity) 0.6 - 1.0 g/cm³
$K_w$ Watson characterization factor 10 - 13
$T_c$ Critical temperature K
$P_c$ Critical pressure bar
$\omega$ Acentric factor 0.2 - 1.5
$T_b$ Normal boiling point K
$z$ Mole fraction 0 - 1

Chapter 9: Physical Properties

Properties Overview

Physical Property Calculations

NeqSim computes phase and mixture properties after thermodynamic initialization. This guide highlights the most used methods and how to choose appropriate models.

Density and Compressibility

Viscosity

Thermal Conductivity and Heat Capacity

Surface and Interfacial Tension

Diffusion and Mass Transfer

Numerical Tips

Physical Props Module

Physical Properties Package

This documentation covers NeqSim's physical properties calculation system, including transport properties (viscosity, thermal conductivity, diffusivity), interfacial properties (surface tension), and density correlations.

Contents


Package Architecture

The physical properties package follows a modular design with clear separation between:

  1. Property Handlers - Manage which models to use for each phase type
  2. Physical Properties System - Phase-specific property containers
  3. Methods - Individual calculation models (viscosity, conductivity, etc.)
  4. Mixing Rules - Combine pure component properties into mixture properties
  5. Interface Properties - Surface/interfacial tension calculations
physicalproperties/
├── PhysicalPropertyHandler.java    # Main entry point
├── PhysicalPropertyType.java       # Property type enum
├── system/
│   ├── PhysicalProperties.java     # Abstract base class
│   ├── PhysicalPropertyModel.java  # Model selection enum
│   ├── gasphysicalproperties/      # Gas phase implementations
│   ├── liquidphysicalproperties/   # Liquid phase implementations
│   └── solidphysicalproperties/    # Solid phase implementations
├── methods/
│   ├── gasphysicalproperties/
│   │   ├── viscosity/
│   │   ├── conductivity/
│   │   └── diffusivity/
│   ├── liquidphysicalproperties/
│   │   ├── viscosity/
│   │   ├── conductivity/
│   │   ├── diffusivity/
│   │   └── density/
│   └── commonphasephysicalproperties/
│       ├── viscosity/              # Models valid for all phases
│       ├── conductivity/
│       └── diffusivity/
├── mixingrule/
│   └── PhysicalPropertyMixingRule.java
└── interfaceproperties/
    ├── InterfaceProperties.java
    └── surfacetension/

Basic Usage

Initializing Physical Properties

Physical properties are calculated after thermodynamic equilibrium has been established:

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create fluid and run flash
SystemInterface fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.1);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Initialize physical properties
fluid.initPhysicalProperties();

// Access properties
double gasViscosity = fluid.getPhase("gas").getViscosity("kg/msec");
double gasConductivity = fluid.getPhase("gas").getThermalConductivity("W/mK");
double gasDensity = fluid.getPhase("gas").getDensity("kg/m3");

Physical Property Models

NeqSim provides several pre-configured physical property model sets:

Model Description Best For
DEFAULT Standard models for oil/gas General hydrocarbon systems
WATER Water-specific correlations Aqueous systems
SALT_WATER Salt water correlations Brine systems
GLYCOL Glycol-specific models Glycol dehydration
AMINE Amine solution models Gas sweetening
CO2WATER CO₂-water system models CCS applications
BASIC Minimal calculations Fast approximations
// Set physical property model
fluid.initPhysicalProperties("GLYCOL");

// Or use the enum directly
import neqsim.physicalproperties.system.PhysicalPropertyModel;
fluid.initPhysicalProperties(PhysicalPropertyModel.AMINE);

Selecting Individual Models

You can override specific property models while keeping others at defaults:

Viscosity Model Selection

fluid.initPhysicalProperties();

// Set viscosity model for a specific phase
fluid.getPhase("gas").getPhysicalProperties().setViscosityModel("friction theory");
fluid.getPhase("oil").getPhysicalProperties().setViscosityModel("LBC");

Available viscosity models:

Thermal Conductivity Model Selection

fluid.getPhase("gas").getPhysicalProperties().setConductivityModel("Chung");
fluid.getPhase("oil").getPhysicalProperties().setConductivityModel("PFCT");

Available conductivity models:

Diffusivity Model Selection

fluid.getPhase("gas").getPhysicalProperties().setDiffusionCoefficientModel("Wilke Lee");
fluid.getPhase("oil").getPhysicalProperties().setDiffusionCoefficientModel("Siddiqi Lucas");

Available diffusivity models:

Density Model Selection (Liquids)

fluid.getPhase("oil").getPhysicalProperties().setDensityModel("Costald");

Available density models:


Model Tuning

Several models support parameter tuning for better match with experimental data:

LBC Viscosity Tuning

The LBC model has 5 tunable parameters for the dense-fluid contribution:

// Set all parameters at once
double[] lbcParams = {0.1023, 0.023364, 0.058533, -0.040758, 0.0093324};
fluid.getPhase("oil").getPhysicalProperties().setLbcParameters(lbcParams);

// Or set individual parameters
fluid.getPhase("oil").getPhysicalProperties().setLbcParameter(0, 0.105);

Friction Theory Constants

For non-SRK/PR equations of state:

FrictionTheoryViscosityMethod viscModel = 
    (FrictionTheoryViscosityMethod) fluid.getPhase("oil")
        .getPhysicalProperties().getViscosityModel();

viscModel.setFrictionTheoryConstants(
    kapac,    // Attractive constant
    kaprc,    // Repulsive constant
    kaprrc,   // Repulsive-repulsive constant
    kapa,     // Attractive matrix (3x3)
    kapr,     // Repulsive matrix (3x3)
    kaprr     // Repulsive-repulsive constant
);

Accessing Calculated Properties

After initialization, properties are available through the phase interface:

Per-Phase Properties

// Viscosity
double viscosity = fluid.getPhase("gas").getViscosity();           // Pa·s
double viscosity_cP = fluid.getPhase("gas").getViscosity("cP");    // cP

// Thermal conductivity
double k = fluid.getPhase("gas").getThermalConductivity();         // W/(m·K)
double k_alt = fluid.getPhase("gas").getThermalConductivity("W/mK");

// Density
double rho = fluid.getPhase("gas").getDensity();                   // kg/m³
double rho_alt = fluid.getPhase("gas").getDensity("kg/m3");

// Kinematic viscosity
double nu = fluid.getPhase("gas").getKinematicViscosity();         // m²/s

// Binary diffusion coefficients
double[][] Dij = fluid.getPhase("gas").getPhysicalProperties()
    .getDiffusivityCalc().getBinaryDiffusionCoefficients();        // m²/s

Pure Component Properties

// Pure component viscosity (for mixing rule debugging)
double pureVisc = fluid.getPhase("gas").getPhysicalProperties()
    .getPureComponentViscosity(0);

Interfacial Properties

// Surface tension between phases
fluid.initPhysicalProperties();
double sigma = fluid.getInterphaseProperties().getSurfaceTension(0, 1);  // N/m

Adding New Models

To add a custom physical property model:

1. Create the Model Class

package neqsim.physicalproperties.methods.liquidphysicalproperties.viscosity;

import neqsim.physicalproperties.system.PhysicalProperties;

public class MyCustomViscosityModel extends Viscosity {

    public MyCustomViscosityModel(PhysicalProperties phase) {
        super(phase);
    }

    @Override
    public double calcViscosity() {
        // Your implementation here
        double viscosity = 0.0;

        // Access phase properties
        double T = phase.getPhase().getTemperature();  // K
        double P = phase.getPhase().getPressure();     // bar
        double rho = phase.getPhase().getDensity();    // kg/m³

        // Access component properties
        for (int i = 0; i < phase.getPhase().getNumberOfComponents(); i++) {
            double x = phase.getPhase().getComponent(i).getx();
            double Tc = phase.getPhase().getComponent(i).getTC();
            // ... calculate contribution
        }

        return viscosity;  // Pa·s
    }

    @Override
    public double getPureComponentViscosity(int i) {
        // Return pure component viscosity for component i
        return 0.0;
    }
}

2. Register in PhysicalProperties

Add to setViscosityModel() in PhysicalProperties.java:

public void setViscosityModel(String model) {
    // ... existing models ...
    else if ("MyCustomModel".equals(model)) {
        viscosityCalc = new MyCustomViscosityModel(this);
    }
}

3. Use the Model

fluid.getPhase("oil").getPhysicalProperties().setViscosityModel("MyCustomModel");

Performance Considerations

  1. Lazy Initialization: Properties are only calculated when initPhysicalProperties() is called
  2. Selective Updates: Use init(phase, PropertyType) to update only specific properties
  3. Reuse Systems: Clone fluids rather than recreating for sensitivity studies
// Efficient property sweep
SystemInterface baseFluid = createFluid();
baseFluid.initPhysicalProperties();

for (double T : temperatures) {
    SystemInterface fluid = baseFluid.clone();
    fluid.setTemperature(T, "K");
    ops.TPflash();
    fluid.initPhysicalProperties();
    // ... use properties
}

See Also

Viscosity Models

Viscosity Models in NeqSim

NeqSim provides a comprehensive suite of methods for calculating fluid viscosity, ranging from standard empirical correlations to advanced corresponding states models and specialized pure-component equations. This document details the available models, their applications, and how to use them in simulations.

Overview

NeqSim viscosity models are organized into several categories:

Category Models Applicability
General Purpose LBC, PFCT, Friction Theory Hydrocarbon mixtures, reservoir fluids
Pure Component Muzny, MethaneModel, CO2Model, KTA Specialized high-accuracy correlations
Aqueous Systems Salt Water (Laliberté), polynom Brine and water solutions
Heavy Oils PFCT-Heavy-Oil Viscous crude oils, bitumen

Available Models

1. Lohrenz-Bray-Clark (LBC)

The LBC method is the industry-standard correlation for calculating viscosity of reservoir fluids. It combines a low-pressure gas viscosity term with a dense-fluid contribution based on reduced density.

2. Corresponding States Principle (CSP/PFCT)

The CSP method (referred to as PFCT in NeqSim) uses the Corresponding States Principle to relate mixture viscosity to a reference substance (typically Methane) at corresponding thermodynamic conditions.

3. Corresponding States Principle for Heavy Oil

A variant of the CSP model specifically tuned for heavy oil systems with additional terms to represent the viscous behavior of heavy fractions.

4. Friction Theory

The Friction Theory (f-theory) model links viscosity to the equation of state (EOS) by separating total viscosity into a dilute gas contribution and a residual friction contribution.

5. Chung Method (Gas Phase)

The Chung method is a corresponding states correlation for gas-phase viscosity based on the Chapman-Enskog kinetic theory with empirical corrections.

6. Lee-Gonzalez-Eakin Correlation

A simple empirical correlation for natural gas viscosity estimation.


Pure Component Models

7. Muzny Hydrogen Viscosity

High-accuracy correlation for pure hydrogen viscosity based on the work of Muzny et al. Includes dilute-gas, first-density, and higher-density contributions.

8. Muzny Modified Hydrogen Viscosity

Extended version of the Muzny correlation with additional correction terms for improved accuracy at specific conditions.

9. Methane Viscosity Model

Specialized correlation for pure methane viscosity using LBC as base with empirical correction terms.

10. CO2 Viscosity Model

Reference-quality correlation for pure carbon dioxide based on Laesecke et al. (JPCRD 2017).

11. KTA Helium Viscosity

Simple power-law correlation for pure helium viscosity.

12. KTA Modified Helium Viscosity

Extended KTA model with pressure-dependent corrections for improved high-pressure accuracy.


Aqueous System Models

13. Salt Water (Laliberté)

Viscosity correlation for aqueous salt solutions using the Laliberté (2007) model with erratum corrections.

14. Polynomial Liquid Viscosity

General liquid viscosity calculation using the Grunberg-Nissan mixing rule with pure-component correlations.

Usage in NeqSim

To use a specific viscosity model, you must set it on the PhysicalProperties object of a phase. This is typically done after creating the system but before performing calculations.

Java Example

import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class ViscosityExample {
    public static void main(String[] args) {
        // 1. Create a system
        SystemInterface system = new SystemSrkEos(298.15, 100.0); // 298.15 K, 100 bar
        system.addComponent("methane", 0.5);
        system.addComponent("n-heptane", 0.5);

        // 2. Set mixing rule and initialize
        system.setMixingRule("classic");
        ThermodynamicOperations ops = new ThermodynamicOperations(system);
        ops.TPflash();

        // 3. Set Viscosity Model for a specific phase (e.g., oil/liquid)
        // Available options: "LBC", "PFCT", "PFCT-Heavy-Oil", "friction theory"

        // Example: Using LBC
        system.getPhase("oil").getPhysicalProperties().setViscosityModel("LBC");
        system.initProperties();
        double lbcViscosity = system.getPhase("oil").getViscosity();
        System.out.println("LBC Viscosity: " + lbcViscosity + " kg/(m*s)");

        // Example: Using PFCT
        system.getPhase("oil").getPhysicalProperties().setViscosityModel("PFCT");
        system.initProperties();
        double pfctViscosity = system.getPhase("oil").getViscosity();
        System.out.println("PFCT Viscosity: " + pfctViscosity + " kg/(m*s)");

        // Example: Using PFCT for Heavy Oil
        system.getPhase("oil").getPhysicalProperties().setViscosityModel("PFCT-Heavy-Oil");
        system.initProperties();
        double pfctHeavyViscosity = system.getPhase("oil").getViscosity();
        System.out.println("PFCT Heavy Oil Viscosity: " + pfctHeavyViscosity + " kg/(m*s)");

        // Example: Using Friction Theory
        system.getPhase("oil").getPhysicalProperties().setViscosityModel("friction theory");
        system.initProperties();
        double frictionViscosity = system.getPhase("oil").getViscosity();
        System.out.println("Friction Theory Viscosity: " + frictionViscosity + " kg/(m*s)");
    }
}

Tuning LBC dense-fluid parameters

The LBC implementation exposes the dense-fluid polynomial coefficients ("Whitson/Bray-Clark" $a_0 \dots a_4$ parameters) so you can tune the model against laboratory data. After selecting the "LBC" viscosity model, update the coefficients via setLbcParameters or setLbcParameter, then re-initialize properties to apply them:

system.getPhase(1).getPhysicalProperties().setViscosityModel("LBC");
system.initProperties();
double baseViscosity = system.getPhase(1).getViscosity();

double[] tunedCoefficients = new double[] {0.11, 0.030, 0.065, -0.045, 0.010};
system.getPhase(1).getPhysicalProperties().setLbcParameters(tunedCoefficients);
system.getPhase(1).getPhysicalProperties().setLbcParameter(2, 0.070); // tweak a single term
system.initProperties();
double tunedViscosity = system.getPhase(1).getViscosity();

System.out.println("Base viscosity:  " + baseViscosity);
System.out.println("Tuned viscosity: " + tunedViscosity);

Python Example (via JPype)

from neqsim.thermo import SystemSrkEos
from neqsim.thermodynamicoperations import ThermodynamicOperations

# 1. Create system
system = SystemSrkEos(298.15, 100.0)
system.addComponent("methane", 0.5)
system.addComponent("n-heptane", 0.5)
system.setMixingRule("classic")

# 2. Flash
ops = ThermodynamicOperations(system)
ops.TPflash()

# 3. Set Viscosity Model
# Note: Phase index 0 is usually gas, 1 is oil/liquid
system.getPhase(1).getPhysicalProperties().setViscosityModel("LBC")
system.initProperties()
print("LBC Viscosity:", system.getPhase(1).getViscosity(), "kg/(m*s)")

system.getPhase(1).getPhysicalProperties().setViscosityModel("PFCT")
system.initProperties()
print("PFCT Viscosity:", system.getPhase(1).getViscosity(), "kg/(m*s)")

system.getPhase(1).getPhysicalProperties().setViscosityModel("friction theory")
system.initProperties()
print("Friction Theory Viscosity:", system.getPhase(1).getViscosity(), "kg/(m*s)")

Mathematical Details

1. Lohrenz-Bray-Clark (LBC) Model

The LBC model calculates the viscosity of a fluid ($\eta$) as the sum of a low-pressure gas contribution ($\eta^*$) and a dense-fluid contribution ($\eta_{dense}$):

$$ \eta = \eta^* + \frac{\eta_{dense}}{\xi_m} $$

where $\xi_m$ is the mixture viscosity parameter:

$$ \xi_m = \frac{T_{cm}^{1/6}}{M_m^{1/2} P_{cm}^{2/3}} $$

The dense-fluid contribution is a function of the reduced density $\rho_r = \rho_m / \rho_{cm}$:

$$ [(\eta - \eta^*) \xi_m + 10^{-4}]^{1/4} = a_0 + a_1 \rho_r + a_2 \rho_r^2 + a_3 \rho_r^3 + a_4 \rho_r^4 $$

Mixing Rules:

2. Corresponding States Principle (CSP)

The CSP model uses the Corresponding States Principle to relate the viscosity of a mixture to that of a reference substance (typically Methane) at a corresponding state ($T_0, P_0$).

Viscosity Mapping: $$ \eta_{mix}(T, P) = \eta_{ref}(T_0, P_0) \cdot F_{\eta} \cdot \frac{\alpha_{mix}}{\alpha_{ref}} $$

where the scaling factor $F_{\eta}$ is: $$ F_{\eta} = \left(\frac{T_{cm}}{T_{c,ref}}\right)^{-1/6} \left(\frac{P_{cm}}{P_{c,ref}}\right)^{2/3} \left(\frac{M_{mix}}{M_{ref}}\right)^{1/2} $$

Corresponding State ($T_0, P_0$): The reference substance is evaluated at: $$ T_0 = T \cdot \frac{T_{c,ref}}{T_{cm}} \cdot \frac{\alpha_{ref}}{\alpha_{mix}} $$ $$ P_0 = P \cdot \frac{P_{c,ref}}{P_{cm}} \cdot \frac{\alpha_{ref}}{\alpha_{mix}} $$

The parameter $\alpha$ accounts for deviations from the simple CSP and is typically a function of reduced density and molecular weight.

3. Friction Theory (f-theory)

The Friction Theory model separates the total viscosity into a dilute gas term ($\eta_0$) and a friction term ($\eta_f$):

$$ \eta = \eta_0 + \eta_f $$

The friction term is derived from mechanical friction concepts applied to the van der Waals repulsive and attractive pressure terms of the Equation of State (EOS):

$$ \eta_f = \kappa_r P_r + \kappa_a P_a + \kappa_{rr} P_r^2 $$

where:

This approach ensures that the viscosity model is consistent with the thermodynamic behavior predicted by the EOS, making it robust across a wide range of conditions, including high pressure and near-critical regions.

4. Muzny Hydrogen Viscosity Model

The Muzny correlation for pure hydrogen viscosity follows a multi-term structure:

$$ \eta = \eta_0 + \eta_1 \rho + \Delta\eta(\rho_r, T_r) $$

where:

The dilute-gas term is:

$$ \eta_0 = \frac{0.021357 \sqrt{MT}}{\sigma^2 S^*} $$

where $S^*$ is the reduced collision integral and $\sigma = 0.297$ nm is the Lennard-Jones size parameter.

5. CO2 Viscosity Model (Laesecke JPCRD 2017)

The CO2 viscosity correlation consists of dilute-gas and residual terms:

$$ \eta = \eta_0(T) + \Delta\eta(\rho, T) $$

The dilute-gas term follows an empirical correlation, and the residual term is expressed as:

$$ \Delta\eta = \eta_{t,L} \left[ c_1 T_r \rho_r^3 + \frac{\rho_r^2 + \rho_r^\gamma}{T_r - c_2} \right] $$

where $T_t = 216.592$ K is the triple point temperature and $\rho_{t,L} = 1178.53$ kg/m³ is the triple point liquid density.

6. Salt Water (Laliberté Model)

The Laliberté mixture rule for aqueous salt solutions:

$$ \eta_m = \eta_w^{w_w} \prod_i \eta_i^{w_i} $$

where $\eta_w$ is pure-water viscosity and $\eta_i$ are solute viscosities:

$$ \eta_i = \frac{\exp\left[\frac{\nu_1(1-w_w)^{\nu_2} + \nu_3}{\nu_4 t + 1}\right]}{\nu_5(1-w_w)^{\nu_6} + 1} $$

with $w_w$ = water mass fraction and $t$ = temperature in °C.


Quick Reference Table

Model Keyword Applicability Phase Multi-Component
"LBC" Hydrocarbons, reservoir fluids Gas/Liquid Yes
"PFCT" Light-medium hydrocarbons Gas/Liquid Yes
"PFCT-Heavy-Oil" Heavy oils, bitumen Liquid Yes
"friction theory" General fluids, EOS-consistent Gas/Liquid Yes
"polynom" Liquids with database parameters Liquid Yes
"Muzny" Pure hydrogen Gas/Liquid No
"Muzny_mod" Pure hydrogen (extended) Gas/Liquid No
"MethaneModel" Pure methane Gas/Liquid No
"CO2Model" Pure CO2 Gas/Liquid No
"KTA" Pure helium Gas No
"KTA_mod" Pure helium (extended) Gas No
"Salt Water" Brine, salt solutions Liquid Yes (aqueous)

Additional Examples

Using Pure Component Models

// Pure Hydrogen Viscosity
SystemInterface h2System = new SystemSrkEos(300.0, 50.0);
h2System.addComponent("hydrogen", 1.0);
h2System.setMixingRule("classic");
ThermodynamicOperations h2Ops = new ThermodynamicOperations(h2System);
h2Ops.TPflash();

h2System.getPhase(0).getPhysicalProperties().setViscosityModel("Muzny");
h2System.initProperties();
System.out.println("H2 Viscosity (Muzny): " + h2System.getPhase(0).getViscosity() + " Pa·s");

// Pure CO2 Viscosity
SystemInterface co2System = new SystemSrkEos(350.0, 100.0);
co2System.addComponent("CO2", 1.0);
co2System.setMixingRule("classic");
ThermodynamicOperations co2Ops = new ThermodynamicOperations(co2System);
co2Ops.TPflash();

co2System.getPhase(0).getPhysicalProperties().setViscosityModel("CO2Model");
co2System.initProperties();
System.out.println("CO2 Viscosity (Laesecke): " + co2System.getPhase(0).getViscosity() + " Pa·s");

Salt Water Viscosity

// Brine viscosity calculation
SystemInterface brine = new SystemSrkCPAstatoil(323.15, 10.0);
brine.addComponent("water", 0.95);
brine.addComponent("NaCl", 0.05);
brine.setMixingRule(10); // CPA mixing rule

ThermodynamicOperations brineOps = new ThermodynamicOperations(brine);
brineOps.TPflash();

// Set Laliberté salt water model
brine.getPhase("aqueous").getPhysicalProperties().setViscosityModel("Salt Water");
brine.initProperties();
System.out.println("Brine Viscosity: " + brine.getPhase("aqueous").getViscosity() + " Pa·s");

Troubleshooting

Common Issues

  1. "Model only supports PURE X" error: Pure-component models (Muzny, CO2Model, MethaneModel, KTA) only work with single-component systems. Use LBC or PFCT for mixtures.

  2. Unexpected viscosity values: Ensure initProperties() is called after setting the viscosity model and after any flash calculations.

  3. Phase selection: Use getPhase("oil"), getPhase("gas"), or getPhase("aqueous") to select the correct phase, or use phase index (0, 1, 2).

  4. Heavy oil predictions too low: Try "PFCT-Heavy-Oil" or tune LBC parameters using setLbcParameters().


References

  1. Lohrenz, J., Bray, B.G., and Clark, C.R., "Calculating Viscosities of Reservoir Fluids from Their Compositions", JPT, 1964.
  2. Pedersen, K.S., and Fredenslund, A., "An Improved Corresponding States Model for the Prediction of Oil and Gas Viscosities", Chemical Engineering Science, 1987.
  3. Quiñones-Cisneros, S.E., and Deiters, U.K., "Generalization of the Friction Theory for Viscosity Modeling", J. Phys. Chem. B, 2006.
  4. Muzny, C.D., et al., "Correlation for the Viscosity of Normal Hydrogen", J. Chem. Eng. Data, 2013.
  5. Laesecke, A., and Muzny, C.D., "Reference Correlation for the Viscosity of Carbon Dioxide", JPCRD, 2017.
  6. Laliberté, M., "Model for Calculating the Viscosity of Aqueous Solutions", Ind. Eng. Chem. Res., 2007.
  7. Lee, A.L., Gonzalez, M.H., and Eakin, B.E., "The Viscosity of Natural Gases", SPE-1340-PA, 1966.
  8. Chung, T.H., et al., "Generalized Multiparameter Correlation for Nonpolar and Polar Fluid Transport Properties", Ind. Eng. Chem. Res., 1988.

Viscosity Detailed

Viscosity Models

This guide documents the viscosity calculation methods available in NeqSim for gas, liquid, and multiphase systems.

Table of Contents


Overview

Viscosity describes a fluid's resistance to flow. NeqSim provides several viscosity models suitable for different applications:

Units:

Setting a viscosity model:

fluid.initPhysicalProperties();
fluid.getPhase("gas").getPhysicalProperties().setViscosityModel("friction theory");
fluid.getPhase("oil").getPhysicalProperties().setViscosityModel("LBC");

Available Models

LBC (Lohrenz-Bray-Clark)

The Lohrenz-Bray-Clark (1964) method is widely used in reservoir simulation. It combines a dilute gas correlation with a dense fluid polynomial correction.

Class: LBCViscosityMethod

Equation: $$\eta = \eta^* + \frac{(\eta_r - 0.0001)^4}{\xi}$$

where:

The dense fluid contribution uses a polynomial: $$\eta_r = a_0 + a_1\rho_r + a_2\rho_r^2 + a_3\rho_r^3 + a_4\rho_r^4$$

Default parameters: {0.10230, 0.023364, 0.058533, -0.040758, 0.0093324}

Applicable phases: Gas, Oil

Best for:

Usage:

fluid.getPhase("oil").getPhysicalProperties().setViscosityModel("LBC");

Friction Theory

The Quiñones-Cisneros and Firoozabadi (2000) friction theory relates viscosity to the repulsive and attractive pressure contributions from the equation of state.

Class: FrictionTheoryViscosityMethod

Equation: $$\eta = \eta_0 + \kappa_a P_a + \kappa_{aa} P_a^2 + \kappa_r P_r + \kappa_{rr} P_r^2$$

where:

Applicable phases: Gas, Oil (any EoS-based phase)

Best for:

Automatic EoS detection: The method automatically selects SRK or PR constants based on the phase type.

Usage:

fluid.getPhase("oil").getPhysicalProperties().setViscosityModel("friction theory");

Custom constants (for other EoS):

FrictionTheoryViscosityMethod viscModel = 
    (FrictionTheoryViscosityMethod) fluid.getPhase("oil")
        .getPhysicalProperties().getViscosityModel();

// Set custom friction theory constants
viscModel.setFrictionTheoryConstants(kapac, kaprc, kaprrc, kapa, kapr, kaprr);

PFCT (Pedersen)

The Pedersen Friction Corresponding States Theory uses methane as a reference fluid with shape factors for mixture calculations.

Classes:

Equation: $$\eta_{mix} = \eta_{ref} \cdot \frac{f_\eta \cdot \alpha_{mix}}{\alpha_0}$$

where:

Applicable phases: Gas, Oil

Best for:

Usage:

// Standard Pedersen
fluid.getPhase("oil").getPhysicalProperties().setViscosityModel("PFCT");

// Heavy oil variant
fluid.getPhase("oil").getPhysicalProperties().setViscosityModel("PFCT-Heavy-Oil");

Chung Method

The Chung method (1984, 1988) is a corresponding states method for dilute gas and dense fluid viscosity.

Class: ChungViscosityMethod

Equation (dilute gas): $$\eta_0 = \frac{40.785 F_c \sqrt{M T}}{\Omega_v V_c^{2/3}}$$

where:

Applicable phases: Primarily gas phase

Best for:

Usage:

fluid.getPhase("gas").getPhysicalProperties().setViscosityModel("Chung");

Polynomial Correlation

Uses component-specific polynomial coefficients from the database.

Class: Viscosity (liquid), GasViscosity (gas)

Equation: $$\ln(\eta) = A + \frac{B}{T} + C\ln(T) + DT$$

where A, B, C, D are component-specific parameters from COMP database.

Database columns: LIQVISC1, LIQVISC2, LIQVISC3, LIQVISC4

Applicable phases: Primarily liquid

Best for:

Usage:

fluid.getPhase("oil").getPhysicalProperties().setViscosityModel("polynom");

Reference Fluid Methods

Specialized high-accuracy methods for specific fluids:

Methane (Reference)

Class: MethaneViscosityMethod

CO₂

Class: CO2ViscosityMethod

Hydrogen

Classes: MuznyViscosityMethod, MuznyModViscosityMethod

Usage:

fluid.getPhase("gas").getPhysicalProperties().setViscosityModel("MethaneModel");
fluid.getPhase("gas").getPhysicalProperties().setViscosityModel("CO2Model");
fluid.getPhase("gas").getPhysicalProperties().setViscosityModel("Muzny");

Model Selection Guide

Application Recommended Model Notes
Reservoir simulation LBC Tunable, industry standard
Wide P-T range Friction Theory Good near critical
Heavy oils PFCT-Heavy-Oil Extended for high MW
Characterized crudes PFCT Works with pseudo-components
Gas processing Chung Good for gases
Pure CO₂ CO2Model High accuracy
Pure H₂ Muzny Reference accuracy
Aqueous systems Salt Water Water correlation

Tuning Parameters

LBC Parameter Tuning

The LBC method is commonly tuned to match laboratory viscosity data:

// Get current parameters
LBCViscosityMethod lbc = (LBCViscosityMethod) 
    fluid.getPhase("oil").getPhysicalProperties().getViscosityModel();

// Set all 5 parameters
double[] params = {0.1023, 0.023364, 0.058533, -0.040758, 0.0093324};
fluid.getPhase("oil").getPhysicalProperties().setLbcParameters(params);

// Or tune individual parameters
fluid.getPhase("oil").getPhysicalProperties().setLbcParameter(0, 0.105);

Tuning procedure:

  1. Measure viscosity at multiple P-T conditions
  2. Run flash and calculate properties
  3. Adjust parameters to minimize error
  4. Validate at other conditions

Usage Examples

Basic Viscosity Calculation

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create and flash fluid
SystemInterface fluid = new SystemSrkEos(350.0, 150.0);
fluid.addComponent("methane", 0.70);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.addComponent("n-heptane", 0.10);
fluid.addComponent("n-decane", 0.05);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Initialize physical properties
fluid.initPhysicalProperties();

// Get viscosities
double gasVisc = fluid.getPhase("gas").getViscosity("cP");
double oilVisc = fluid.getPhase("oil").getViscosity("cP");

System.out.println("Gas viscosity: " + gasVisc + " cP");
System.out.println("Oil viscosity: " + oilVisc + " cP");

Comparing Viscosity Models

String[] models = {"LBC", "friction theory", "PFCT"};

for (String model : models) {
    SystemInterface fluid = createFluid();
    ops.TPflash();
    fluid.initPhysicalProperties();

    fluid.getPhase("oil").getPhysicalProperties().setViscosityModel(model);
    fluid.initPhysicalProperties();

    double visc = fluid.getPhase("oil").getViscosity("cP");
    System.out.println(model + ": " + visc + " cP");
}

Viscosity vs Temperature

SystemInterface baseFluid = createFluid();
baseFluid.initPhysicalProperties();

double[] temps = {300, 320, 340, 360, 380, 400};  // K

for (double T : temps) {
    SystemInterface fluid = baseFluid.clone();
    fluid.setTemperature(T, "K");

    ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
    ops.TPflash();
    fluid.initPhysicalProperties();

    double visc = fluid.getPhase("oil").getViscosity("cP");
    System.out.println("T=" + (T-273.15) + "°C: " + visc + " cP");
}

Mathematical Details

Dilute Gas Viscosity (Chapman-Enskog)

For dilute gases, viscosity is calculated from kinetic theory:

$$\eta_0 = \frac{5}{16} \frac{\sqrt{\pi m k_B T}}{\pi \sigma^2 \Omega^{(2,2)*}}$$

where:

Mixing Rules

Most models use molar fraction-weighted mixing:

$$\eta_{mix} = \exp\left(\sum_i x_i \ln \eta_i\right)$$

or the more rigorous:

$$\eta_{mix} = \frac{\sum_i x_i \sqrt{M_i} \eta_i}{\sum_i x_i \sqrt{M_i}}$$


References

  1. Lohrenz, J., Bray, B.G., Clark, C.R. (1964). Calculating Viscosities of Reservoir Fluids from Their Compositions. JPT.
  2. Quiñones-Cisneros, S.E., Firoozabadi, A. (2000). One Parameter Friction Theory. AIChE J.
  3. Pedersen, K.S., et al. (1987). Viscosity of Crude Oils. Chem. Eng. Sci.
  4. Chung, T.H., et al. (1988). Generalized Multiparameter Correlation for Nonpolar and Polar Fluid Transport Properties. I&EC Res.

Density Models

Density Models

This guide documents the density correction models available in NeqSim for improving volumetric predictions.

Table of Contents


Overview

Density predictions from cubic equations of state (SRK, PR) often have systematic errors:

NeqSim provides volume translation and correlation-based methods to improve liquid density predictions.

Basic density access:

fluid.init(3);  // Initialize with derivatives
fluid.initPhysicalProperties();

double density = fluid.getPhase(1).getDensity("kg/m3");  // Liquid phase
double molarVolume = fluid.getPhase(1).getMolarVolume();  // m³/mol

Equation of State Density

Direct EoS Calculation

Cubic equations of state calculate compressibility factor $Z$:

$$PV = ZnRT$$

Molar volume is then: $$V_m = \frac{ZRT}{P}$$

Density: $$\rho = \frac{PM_w}{ZRT}$$

Issue with cubic EoS: At the critical point, $Z_c^{SRK} = 0.333$ and $Z_c^{PR} = 0.307$, while real hydrocarbons have $Z_c \approx 0.26$. This causes systematic liquid volume overprediction.


Volume Translation Methods

Peneloux Volume Shift

The Peneloux correction adds a constant shift to the EoS molar volume:

$$V_{corrected} = V_{EoS} - c$$

where $c$ is the volume shift parameter.

Class: Peneloux

Mixture shift: $$c_{mix} = \sum_i x_i c_i$$

Component shift correlation: $$c_i = 0.40768 \frac{RT_{c,i}}{P_{c,i}} \left( 0.29441 - Z_{RA,i} \right)$$

where $Z_{RA}$ is the Rackett compressibility factor (from COMP database).

Setting shift parameters:

// Enable Peneloux correction (default for SRK)
fluid.setDensityModel("Peneloux");

// Or set component-specific shifts
fluid.getPhase(0).getComponent("methane").setVolumeCorrectionConst(0.0);
fluid.getPhase(1).getComponent("n-heptane").setVolumeCorrectionConst(-0.0105);

Advantages:

Limitations:


Component-Specific Corrections

NeqSim stores volume correction constants in the COMP database. For heavy hydrocarbons or polar compounds, these may need tuning.

Accessing correction constants:

// Get current volume correction
double vc = fluid.getPhase(1).getComponent("n-decane").getVolumeCorrectionConst();

// Modify correction
fluid.getPhase(1).getComponent("n-decane").setVolumeCorrectionConst(-0.015);

Temperature-dependent shift (Jhaveri-Youngren): Some systems require temperature-dependent corrections:

$$c(T) = c_0 + c_1 (T - T_{ref})$$

This is implemented in specific component models.


Liquid Density Correlations

Costald

The COSTALD (COrreSponding STAtes Liquid Density) correlation predicts saturated liquid volumes.

Class: Costald

Equation: $$V_s = V^* V_R^{(0)} \left[ 1 - \omega_{SRK} V_R^{(\delta)} \right]$$

where:

Reduced volume functions: $$V_R^{(0)} = 1 + a(1-T_r)^{1/3} + b(1-T_r)^{2/3} + c(1-T_r) + d(1-T_r)^{4/3}$$

$$V_R^{(\delta)} = \frac{e + fT_r + gT_r^2 + hT_r^3}{T_r - 1.00001}$$

Constants:

Mixing rules: $$V^_{mix} = \frac{1}{4} \left[ \sum_i x_i V^_i + 3 \left(\sum_i x_i V^{*2/3}_i\right) \left(\sum_i x_i V^{*1/3}_i\right) \right]$$

$$\omega_{mix} = \sum_i x_i \omega_i$$

Usage:

fluid.setDensityModel("Costald");
fluid.initPhysicalProperties();

double liquidDensity = fluid.getPhase(1).getDensity("kg/m3");

Best for:


Rackett Equation

A simple corresponding states correlation for saturated liquid density.

Equation: $$V_s = \frac{RT_c}{P_c} Z_{RA}^{[1 + (1-T_r)^{2/7}]}$$

where $Z_{RA}$ is the Rackett compressibility factor.

Spencer-Danner modification: Uses optimized $Z_{RA}$ values from experimental data rather than critical compressibility.

For mixtures: $$Z_{RA,mix} = \sum_i x_i Z_{RA,i}$$ $$T_{c,mix} = \sum_i x_i T_{c,i}$$

Usage:

// Access Rackett parameter
double Zra = fluid.getPhase(1).getComponent("n-pentane").getRacketZ();

// Rackett is used internally for volume correction

Usage Examples

Comparing Density Models

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.1);
fluid.addComponent("n-pentane", 0.9);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// EoS density (no correction)
double densityEoS = fluid.getPhase(1).getDensity("kg/m3");
System.out.println("EoS only: " + densityEoS + " kg/m³");

// With Peneloux correction (default for SRK)
fluid.initPhysicalProperties();
double densityPeneloux = fluid.getPhase(1).getDensity("kg/m3");
System.out.println("Peneloux: " + densityPeneloux + " kg/m³");

// With Costald
fluid.setDensityModel("Costald");
fluid.initPhysicalProperties();
double densityCostald = fluid.getPhase(1).getDensity("kg/m3");
System.out.println("Costald: " + densityCostald + " kg/m³");

Tuning Liquid Density

// Create fluid with known experimental density
SystemInterface fluid = new SystemSrkEos(293.15, 1.01325);
fluid.addComponent("n-hexane", 1.0);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
fluid.initPhysicalProperties();

double expDensity = 659.0;  // kg/m³ at 20°C
double calcDensity = fluid.getPhase(1).getDensity("kg/m3");
double error = (calcDensity - expDensity) / expDensity * 100;
System.out.println("Initial error: " + error + "%");

// Adjust volume correction to match experimental
double molarMass = fluid.getPhase(1).getMolarMass() * 1000;  // kg/kmol
double calcMolarVolume = molarMass / calcDensity;  // m³/kmol
double expMolarVolume = molarMass / expDensity;    // m³/kmol
double correction = (calcMolarVolume - expMolarVolume) / 1000;  // m³/mol

fluid.getPhase(1).getComponent("n-hexane").setVolumeCorrectionConst(correction);
fluid.initPhysicalProperties();

double newDensity = fluid.getPhase(1).getDensity("kg/m3");
System.out.println("Tuned density: " + newDensity + " kg/m³");

Density vs Temperature

SystemInterface fluid = new SystemSrkEos(300.0, 10.0);
fluid.addComponent("n-heptane", 1.0);
fluid.setMixingRule("classic");

double[] temps = {280, 300, 320, 340, 360, 380};

for (double T : temps) {
    fluid.setTemperature(T, "K");

    ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
    ops.TPflash();

    if (fluid.getPhase(1).getPhaseTypeName().equals("oil")) {
        fluid.initPhysicalProperties();
        double rho = fluid.getPhase(1).getDensity("kg/m3");
        System.out.println("T=" + T + " K: ρ=" + rho + " kg/m³");
    }
}

High-Pressure Density

// Compressed liquid density at high pressure
SystemInterface fluid = new SystemSrkEos(300.0, 500.0);  // 500 bar
fluid.addComponent("n-decane", 1.0);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
fluid.initPhysicalProperties();

double rho = fluid.getPhase(0).getDensity("kg/m3");
System.out.println("High-P density: " + rho + " kg/m³");

// For high-pressure liquids, Peneloux may be insufficient
// Consider using PC-SAFT or adjusting correction

Model Selection Guide

Situation Recommended Model Notes
General hydrocarbons Peneloux Default, good accuracy
Near saturation Costald Better for sat. liquids
Polar compounds PC-SAFT or CPA Better fundamental basis
High pressure Peneloux with tuning May need adjustment
Critical region GERG-2008 If available
Quick estimate EoS only 5-15% error typical

Expected Accuracy

Method Liquid Density Error Vapor Density Error
SRK (no correction) 5-15% 1-3%
SRK + Peneloux 1-3% 1-3%
PR (no correction) 3-10% 1-3%
PR + Peneloux 1-3% 1-3%
Costald 1-2% N/A
GERG-2008 0.1-0.5% 0.1-0.5%

API Reference

Setting Density Model

// Set density model for all phases
fluid.setDensityModel("Peneloux");  // or "Costald"

// The model affects initPhysicalProperties() calls
fluid.initPhysicalProperties();

Accessing Density

// Mass density
double rhoMass = phase.getDensity("kg/m3");
double rhoMass2 = phase.getDensity("lb/ft3");

// Molar density
double rhoMolar = phase.getDensity("mol/m3");

// Molar volume
double Vm = phase.getMolarVolume();  // m³/mol

Volume Correction Parameters

// Get/set volume correction constant
double c = component.getVolumeCorrectionConst();
component.setVolumeCorrectionConst(newValue);

// Get Rackett parameter
double Zra = component.getRacketZ();

References

  1. Peneloux, A., Rauzy, E., Freze, R. (1982). A Consistent Correction for Redlich-Kwong-Soave Volumes. Fluid Phase Equilib.
  2. Hankinson, R.W., Thomson, G.H. (1979). A New Correlation for Saturated Densities of Liquids and Their Mixtures. AIChE J.
  3. Rackett, H.G. (1970). Equation of State for Saturated Liquids. J. Chem. Eng. Data.
  4. Spencer, C.F., Danner, R.P. (1972). Improved Equation for Prediction of Saturated Liquid Density. J. Chem. Eng. Data.
  5. Jhaveri, B.S., Youngren, G.K. (1988). Three-Parameter Modification of the Peng-Robinson Equation of State. SPE Reservoir Eng.

Thermal Conductivity

Thermal Conductivity Models

This guide documents the thermal conductivity calculation methods available in NeqSim for gas, liquid, and multiphase systems.

Table of Contents


Overview

Thermal conductivity ($\lambda$ or $k$) describes a material's ability to conduct heat. It is essential for:

Units:

Setting a conductivity model:

fluid.initPhysicalProperties();
fluid.getPhase("gas").getPhysicalProperties().setConductivityModel("Chung");
fluid.getPhase("oil").getPhysicalProperties().setConductivityModel("PFCT");

Available Models

PFCT (Pedersen)

The Pedersen Corresponding States method uses methane as a reference fluid with molecular weight corrections.

Class: PFCTConductivityMethodMod86

Principle: Uses corresponding states with methane as reference:

$$\lambda_{mix} = \lambda_{ref}(T_0, P_0) \cdot \frac{\alpha_{mix}}{\alpha_0}$$

where:

Corresponding state mapping: $$T_0 = T \cdot \frac{T_{c,ref}}{T_{c,mix}} \cdot \frac{\alpha_0}{\alpha_{mix}}$$

$$P_0 = P \cdot \frac{P_{c,ref}}{P_{c,mix}} \cdot \frac{\alpha_0}{\alpha_{mix}}$$

Applicable phases: Gas, Oil

Best for:

Usage:

fluid.getPhase("oil").getPhysicalProperties().setConductivityModel("PFCT");

Chung Method

The Chung method (1988) is a corresponding states correlation based on kinetic theory.

Class: ChungConductivityMethod

Equation (dilute gas): $$\lambda_0 = \frac{7.452 \eta_0 \Psi}{M}$$

where:

The correction factor accounts for:

Dense fluid correction: $$\lambda = \lambda_0 \cdot G_2(T^, \rho^) + B_1 q B_2$$

where $G_2$ and $B$ terms account for density effects.

Applicable phases: Primarily gas phase

Best for:

Usage:

fluid.getPhase("gas").getPhysicalProperties().setConductivityModel("Chung");

Polynomial Correlation

Uses component-specific polynomial coefficients from the database.

Class: Conductivity (in liquid package)

Equation: $$\lambda = A + BT + CT^2$$

where A, B, C are component-specific parameters.

Database columns: LIQUIDCONDUCTIVITY1, LIQUIDCONDUCTIVITY2, LIQUIDCONDUCTIVITY3

Mixing rule: $$\lambda_{mix} = \sum_i x_i \lambda_i$$

Applicable phases: Liquid

Best for:

Usage:

fluid.getPhase("oil").getPhysicalProperties().setConductivityModel("polynom");

CO₂ Reference

High-accuracy thermal conductivity for CO₂ based on the Vesovic et al. correlation.

Class: CO2ConductivityMethod

Coverage:

Best for:

Usage:

fluid.getPhase("gas").getPhysicalProperties().setConductivityModel("CO2Model");

Model Selection Guide

Application Recommended Model Notes
Petroleum mixtures PFCT Corresponding states with MW correction
Gas processing Chung Good for gases
Simple liquid mixtures polynom Uses database parameters
Pure CO₂ CO2Model High accuracy
Wide P-T range PFCT Robust extrapolation
Polar systems Chung Includes polar corrections

Usage Examples

Basic Conductivity Calculation

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create and flash fluid
SystemInterface fluid = new SystemSrkEos(350.0, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Initialize physical properties
fluid.initPhysicalProperties();

// Get thermal conductivity
double gasConductivity = fluid.getPhase("gas").getThermalConductivity("W/mK");
System.out.println("Gas thermal conductivity: " + gasConductivity + " W/(m·K)");

Comparing Conductivity Models

String[] models = {"PFCT", "Chung"};

for (String model : models) {
    SystemInterface fluid = createFluid();
    ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
    ops.TPflash();
    fluid.initPhysicalProperties();

    fluid.getPhase("gas").getPhysicalProperties().setConductivityModel(model);
    fluid.initPhysicalProperties();

    double k = fluid.getPhase("gas").getThermalConductivity("W/mK");
    System.out.println(model + ": " + k + " W/(m·K)");
}

Conductivity vs Pressure

SystemInterface baseFluid = new SystemSrkEos(350.0, 10.0);
baseFluid.addComponent("methane", 1.0);
baseFluid.setMixingRule("classic");

double[] pressures = {10, 50, 100, 150, 200};  // bar

for (double P : pressures) {
    SystemInterface fluid = baseFluid.clone();
    fluid.setPressure(P, "bar");

    ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
    ops.TPflash();
    fluid.initPhysicalProperties();

    double k = fluid.getPhase(0).getThermalConductivity("W/mK");
    System.out.println("P=" + P + " bar: " + k + " W/(m·K)");
}

Two-Phase System

SystemInterface fluid = new SystemSrkEos(280.0, 30.0);
fluid.addComponent("methane", 0.5);
fluid.addComponent("n-pentane", 0.5);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
fluid.initPhysicalProperties();

if (fluid.hasPhaseType("gas")) {
    System.out.println("Gas k: " + 
        fluid.getPhase("gas").getThermalConductivity("W/mK") + " W/(m·K)");
}
if (fluid.hasPhaseType("oil")) {
    System.out.println("Oil k: " + 
        fluid.getPhase("oil").getThermalConductivity("W/mK") + " W/(m·K)");
}

Physical Background

Kinetic Theory (Dilute Gas)

For dilute gases, thermal conductivity is related to viscosity through:

$$\lambda = \frac{f \cdot \eta \cdot C_v}{M}$$

where:

Mixing Rules

For mixtures, thermal conductivity is typically calculated using:

Mass fraction weighting: $$\lambda_{mix} = \sum_i w_i \lambda_i$$

Molar weighting with interaction: $$\lambda_{mix} = \sum_i \sum_j \frac{x_i x_j \lambda_{ij}}{\sum_k x_k \phi_{ik}}$$

where $\lambda_{ij}$ is a combining rule and $\phi_{ik}$ is an interaction factor.

Pressure Effects

Thermal conductivity increases with pressure, particularly in dense fluids:

The PFCT method accounts for this through corresponding states mapping to reference fluid behavior.


Temperature and Pressure Dependence

Gases

Liquids


References

  1. Pedersen, K.S., et al. (1989). Thermal Conductivity of Crude Oils. Chem. Eng. Sci.
  2. Chung, T.H., et al. (1988). Generalized Multiparameter Correlation. I&EC Res.
  3. Vesovic, V., et al. (1990). The Transport Properties of Carbon Dioxide. J. Phys. Chem. Ref. Data.
  4. Poling, B.E., et al. (2001). The Properties of Gases and Liquids, 5th Ed.

Diffusivity

Diffusivity Models

This guide documents the diffusion coefficient calculation methods available in NeqSim for gas and liquid systems.

Table of Contents


Overview

Diffusion coefficients describe the rate of molecular transport due to concentration gradients. They are essential for:

Units:

Setting a diffusivity model:

fluid.initPhysicalProperties();
fluid.getPhase("gas").getPhysicalProperties().setDiffusionCoefficientModel("Wilke Lee");
fluid.getPhase("oil").getPhysicalProperties().setDiffusionCoefficientModel("Siddiqi Lucas");

Types of Diffusion Coefficients

Binary Diffusion Coefficient ($D_{ij}$)

The diffusion coefficient for species $i$ moving through species $j$ at infinite dilution.

Effective Diffusion Coefficient ($D_i^{eff}$)

The effective diffusivity of species $i$ in a multicomponent mixture:

$$D_i^{eff} = \frac{1 - x_i}{\sum_{j \neq i} \frac{x_j}{D_{ij}}}$$

Maxwell-Stefan Diffusion Coefficients

The fundamental diffusion coefficients describing molecular interactions, related to Fick diffusion through thermodynamic factors.


Available Models

Wilke-Lee (Gases)

The Wilke-Lee method is based on Chapman-Enskog kinetic theory for gases.

Class: WilkeLeeDiffusivity

Equation: $$D_{ij} = \frac{(1.084 - 0.249\sqrt{1/M_i + 1/M_j}) \times 10^{-4} T^{1.5} \sqrt{1/M_i + 1/M_j}}{P \sigma_{ij}^2 \Omega_D}$$

where:

Collision diameter combining rule: $$\sigma_{ij} = \frac{\sigma_i + \sigma_j}{2}$$

Collision integral approximation: $$\Omega_D = \frac{A}{(T^)^B} + \frac{C}{\exp(DT^)} + \frac{E}{\exp(FT^)} + \frac{G}{\exp(HT^)}$$

where $T^* = k_B T / \epsilon_{ij}$.

Applicable phases: Gas

Best for:

Usage:

fluid.getPhase("gas").getPhysicalProperties().setDiffusionCoefficientModel("Wilke Lee");

Siddiqi-Lucas (Liquids)

The Siddiqi-Lucas method is designed for liquid-phase binary diffusion.

Class: SiddiqiLucasMethod

Aqueous systems: $$D_{ij} = 2.98 \times 10^{-7} \frac{T}{\eta_j^{1.026} V_i^{0.5473}}$$

Non-aqueous systems: $$D_{ij} = 9.89 \times 10^{-8} \frac{T}{\eta_j^{0.907} V_i^{0.45} V_j^{-0.265}}$$

where:

Applicable phases: Liquid (aqueous and organic)

Best for:

Usage:

fluid.getPhase("oil").getPhysicalProperties().setDiffusionCoefficientModel("Siddiqi Lucas");

Corresponding States (CSP)

A generalized corresponding states method for both gas and liquid diffusion.

Class: CorrespondingStatesDiffusivity

Principle: Uses reduced temperature and density to correlate diffusion:

$$D^* = D \cdot \frac{\sigma^2}{(M/N_A) \sqrt{k_B T / M}}$$

The reduced diffusivity is correlated against reduced temperature and density.

Applicable phases: Gas, Liquid

Best for:

Usage:

fluid.getPhase("gas").getPhysicalProperties().setDiffusionCoefficientModel("CSP");

Amine Diffusivity

Specialized correlations for amine solutions used in gas treating.

Class: AmineDiffusivity

Includes:

Applicable phases: Aqueous amine solutions

Best for:

Usage:

fluid.getPhase("aqueous").getPhysicalProperties()
    .setDiffusionCoefficientModel("Alkanol amine");

Model Selection Guide

Application Recommended Model Notes
Gas phase Wilke Lee Based on kinetic theory
Aqueous liquids Siddiqi Lucas Validated for water
Organic liquids Siddiqi Lucas Use non-aqueous correlation
Wide P-T range CSP Corresponding states
Amine systems Alkanol amine Specialized for gas treating
CO₂ in water CO2water model Specific correlation

Usage Examples

Accessing Binary Diffusion Coefficients

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create and flash fluid
SystemInterface fluid = new SystemSrkEos(300.0, 10.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.05);
fluid.addComponent("CO2", 0.05);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Initialize physical properties
fluid.initPhysicalProperties();

// Get binary diffusion coefficients
double[][] Dij = fluid.getPhase("gas").getPhysicalProperties()
    .getDiffusivityCalc().getBinaryDiffusionCoefficients();

// Print diffusion matrix
int n = fluid.getPhase("gas").getNumberOfComponents();
for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        System.out.println("D[" + i + "][" + j + "] = " + Dij[i][j] + " m²/s");
    }
}

Effective Diffusivity

// Get effective diffusion coefficient
double[] Deff = fluid.getPhase("gas").getPhysicalProperties()
    .getDiffusivityCalc().getEffectiveDiffusionCoefficient();

for (int i = 0; i < n; i++) {
    String name = fluid.getPhase("gas").getComponent(i).getName();
    System.out.println("D_eff[" + name + "] = " + Deff[i] + " m²/s");
}

Comparing Gas and Liquid Diffusivity

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("CO2", 0.1);
fluid.addComponent("n-octane", 0.9);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
fluid.initPhysicalProperties();

// Compare gas and liquid diffusivities
if (fluid.hasPhaseType("gas")) {
    double[][] Dgas = fluid.getPhase("gas").getPhysicalProperties()
        .getDiffusivityCalc().getBinaryDiffusionCoefficients();
    System.out.println("Gas D_CO2-octane: " + Dgas[0][1] + " m²/s");
}

if (fluid.hasPhaseType("oil")) {
    double[][] Dliq = fluid.getPhase("oil").getPhysicalProperties()
        .getDiffusivityCalc().getBinaryDiffusionCoefficients();
    System.out.println("Liquid D_CO2-octane: " + Dliq[0][1] + " m²/s");
}
// Gas diffusivity is typically 10,000x larger than liquid

Diffusivity in Amine Solutions

SystemInterface fluid = new SystemSrkCPAstatoil(313.15, 1.0);
fluid.addComponent("CO2", 0.1);
fluid.addComponent("water", 0.7);
fluid.addComponent("MDEA", 0.2);
fluid.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Use amine-specific diffusivity model
fluid.initPhysicalProperties("AMINE");

double[][] D = fluid.getPhase("aqueous").getPhysicalProperties()
    .getDiffusivityCalc().getBinaryDiffusionCoefficients();

System.out.println("D_CO2 in amine: " + D[0][1] + " m²/s");

Physical Background

Pressure Dependence

Gases: $$D \propto \frac{1}{P}$$

At constant temperature, gas diffusivity is inversely proportional to pressure.

Liquids: Weak pressure dependence; can often be neglected.

Temperature Dependence

Gases: $$D \propto T^{1.5}$$

Liquids: $$D \propto T / \eta$$

Since viscosity decreases with temperature, liquid diffusivity increases.

Typical Values

Phase Diffusivity Range
Gas (1 bar) 10⁻⁵ to 10⁻⁴ m²/s
Gas (100 bar) 10⁻⁷ to 10⁻⁶ m²/s
Liquid 10⁻¹⁰ to 10⁻⁹ m²/s
Supercritical 10⁻⁸ to 10⁻⁷ m²/s

Multicomponent Diffusion

For multicomponent systems, the flux of species $i$ depends on gradients of all components:

$$J_i = -c_t \sum_{j=1}^{n} D_{ij} \nabla x_j$$

NeqSim calculates:

  1. Binary Maxwell-Stefan coefficients from pure component properties
  2. Effective diffusivities using the Wilke approximation
  3. Fick diffusion matrix (optional) including thermodynamic factors

References

  1. Wilke, C.R., Lee, C.Y. (1955). Estimation of Diffusion Coefficients for Gases and Vapors. I&EC.
  2. Siddiqi, M.A., Lucas, K. (1986). Correlations for Prediction of Diffusion in Liquids. Can. J. Chem. Eng.
  3. Fuller, E.N., et al. (1966). New Method for Prediction of Binary Gas-Phase Diffusion Coefficients. I&EC.
  4. Poling, B.E., et al. (2001). The Properties of Gases and Liquids, 5th Ed.

Interfacial Props

Interfacial Properties

This guide documents the interfacial property calculations available in NeqSim, including surface tension and related phenomena.

Table of Contents


Overview

Interfacial tension (IFT) describes the energy required to create a unit area of interface between two phases. It is critical for:

Units:

Basic usage:

fluid.initPhysicalProperties();
double sigma = fluid.getInterphaseProperties().getSurfaceTension(0, 1);  // N/m

Surface Tension Models

Parachor (Macleod-Sugden)

The Parachor method is an empirical correlation relating surface tension to density difference and component parachors.

Class: ParachorSurfaceTension

Equation: $$\sigma^{1/4} = \sum_i P_i \left( \frac{\rho_L x_i}{M_{mix,L}} - \frac{\rho_V y_i}{M_{mix,V}} \right)$$

where:

Parachor values:

Applicable interfaces: Gas-liquid, Gas-aqueous

Best for:

Usage:

fluid.getInterphaseProperties().setInterfacialTensionModel("gas", "oil", "Parachor");

Gradient Theory (GT)

The Gradient Theory is a rigorous thermodynamic approach based on density functional theory.

Classes:

Physical basis:

Near an interface, the Helmholtz energy depends on density gradients:

$$A = \int_{-\infty}^{\infty} \left[ a_0(\boldsymbol{n}) + \frac{1}{2}\sum_i\sum_j c_{ij} \frac{dn_i}{dz}\frac{dn_j}{dz} \right] dz$$

where:

Surface tension calculation: $$\sigma = \int_{-\infty}^{\infty} \sum_i\sum_j c_{ij} \frac{dn_i}{dz}\frac{dn_j}{dz} dz$$

Influence parameter correlation: $$c_i = (A_i t_i + B_i) a_i b_i^{2/3}$$

where:

Applicable interfaces: All phase pairs

Best for:

Usage:

// Full gradient theory
fluid.getInterphaseProperties().setInterfacialTensionModel("gas", "oil", "Full Gradient Theory");

// Simplified version (faster)
fluid.getInterphaseProperties().setInterfacialTensionModel("gas", "oil", "Simple Gradient Theory");

Linear Gradient Theory (LGT)

A linearized approximation of gradient theory that is computationally efficient.

Class: LGTSurfaceTension

Approximation: Assumes linear density profile between bulk phases:

$$n_i(z) = n_i^L + \frac{n_i^V - n_i^L}{L} z$$

This allows analytical integration:

$$\sigma = \sum_i\sum_j c_{ij} \frac{(n_i^V - n_i^L)(n_j^V - n_j^L)}{L}$$

where $L$ is optimized to minimize Helmholtz energy.

Applicable interfaces: Gas-liquid

Best for:

Usage:

fluid.getInterphaseProperties().setInterfacialTensionModel("gas", "oil", "Linear Gradient Theory");

Firozabadi-Ramley

A correlation specifically designed for liquid-liquid interfaces (oil-water).

Class: FirozabadiRamleyInterfaceTension

Equation: $$\sigma_{ow} = \sigma_o + \sigma_w - 2\sqrt{\sigma_o \sigma_w} \phi$$

where:

Applicable interfaces: Oil-water (liquid-liquid)

Best for:

Usage:

fluid.getInterphaseProperties().setInterfacialTensionModel("oil", "aqueous", "Firozabadi Ramley");

Model Selection by Interface Type

NeqSim automatically selects models based on phase types, but you can override:

Using Model Numbers

// Set interfacial tension model set by number
fluid.getInterphaseProperties().setInterfacialTensionModel(0);  // Default set
Number Gas-Oil Gas-Aqueous Oil-Aqueous
0 Parachor Parachor Firozabadi-Ramley
1 Full GT Simple GT Simple GT
2 LGT LGT LGT
3 Parachor Parachor Firozabadi-Ramley
4 Simple GT Parachor LGT
5 Parachor Parachor Firozabadi-Ramley

Using Named Models

// Set specific models per interface
fluid.getInterphaseProperties().setInterfacialTensionModel("gas", "oil", "Full Gradient Theory");
fluid.getInterphaseProperties().setInterfacialTensionModel("gas", "aqueous", "Parachor");
fluid.getInterphaseProperties().setInterfacialTensionModel("oil", "aqueous", "Firozabadi Ramley");

Available model names:


Usage Examples

Basic Surface Tension

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create and flash fluid
SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.8);
fluid.addComponent("n-decane", 0.2);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Initialize physical properties
fluid.initPhysicalProperties();

// Get surface tension between phase 0 and 1
double sigma = fluid.getInterphaseProperties().getSurfaceTension(0, 1);
System.out.println("Surface tension: " + sigma * 1000 + " mN/m");

Comparing Surface Tension Models

String[] models = {"Parachor", "Full Gradient Theory", "Linear Gradient Theory"};

SystemInterface baseFluid = createTwoPhaseFluid();
ThermodynamicOperations ops = new ThermodynamicOperations(baseFluid);
ops.TPflash();
baseFluid.initPhysicalProperties();

for (String model : models) {
    SystemInterface fluid = baseFluid.clone();
    fluid.getInterphaseProperties().setInterfacialTensionModel("gas", "oil", model);
    fluid.initPhysicalProperties();

    double sigma = fluid.getInterphaseProperties().getSurfaceTension(0, 1) * 1000;
    System.out.println(model + ": " + sigma + " mN/m");
}

Surface Tension vs Pressure (Approaching Critical)

SystemInterface fluid = new SystemSrkEos(350.0, 10.0);
fluid.addComponent("methane", 0.5);
fluid.addComponent("n-pentane", 0.5);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

double[] pressures = {10, 30, 50, 70, 90, 100, 110};

for (double P : pressures) {
    fluid.setPressure(P, "bar");

    ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
    ops.TPflash();

    if (fluid.getNumberOfPhases() >= 2) {
        fluid.initPhysicalProperties();
        double sigma = fluid.getInterphaseProperties().getSurfaceTension(0, 1) * 1000;
        System.out.println("P=" + P + " bar: σ=" + sigma + " mN/m");
    } else {
        System.out.println("P=" + P + " bar: Single phase");
    }
}
// Surface tension approaches zero at critical point

Three-Phase System

SystemInterface fluid = new SystemSrkCPAstatoil(300.0, 30.0);
fluid.addComponent("methane", 0.6);
fluid.addComponent("n-heptane", 0.3);
fluid.addComponent("water", 0.1);
fluid.setMixingRule(10);
fluid.setMultiPhaseCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
fluid.initPhysicalProperties();

// Get all interfacial tensions
int nPhases = fluid.getNumberOfPhases();
for (int i = 0; i < nPhases; i++) {
    for (int j = i + 1; j < nPhases; j++) {
        double sigma = fluid.getInterphaseProperties().getSurfaceTension(i, j);
        System.out.println("Phase " + i + " - Phase " + j + ": " + 
            sigma * 1000 + " mN/m");
    }
}

Adsorption Calculations

NeqSim also supports adsorption calculations at solid surfaces.

Setup

// Initialize adsorption
fluid.getInterphaseProperties().initAdsorption();

// Set adsorbent material
fluid.getInterphaseProperties().setSolidAdsorbentMaterial("ite");

// Calculate adsorption
fluid.getInterphaseProperties().calcAdsorption();

Access Results

AdsorptionInterface ads = fluid.getInterphaseProperties().getAdsorptionCalc("gas");
// Access adsorption quantities per component

Mathematical Background

Thermodynamic Definition

Surface tension is defined as:

$$\sigma = \left( \frac{\partial G}{\partial A} \right)_{T,P,n}$$

where $G$ is Gibbs energy and $A$ is interfacial area.

Young-Laplace Equation

The pressure difference across a curved interface:

$$\Delta P = \sigma \left( \frac{1}{R_1} + \frac{1}{R_2} \right)$$

where $R_1, R_2$ are the principal radii of curvature.

Temperature Dependence

Surface tension typically decreases with temperature:

$$\sigma = \sigma_0 \left( 1 - T/T_c \right)^n$$

where $n \approx 1.26$ (Guggenheim exponent).

At the critical point: $\sigma \rightarrow 0$.

Pressure Dependence

Surface tension generally decreases with increasing pressure because:

  1. Density difference decreases
  2. Phases become more similar as pressure increases

Near the critical point, $\sigma \propto (\rho_L - \rho_V)^{3.9}$.


Typical Values

Interface Temperature Typical IFT
Methane-Water 25°C, 100 bar 50-70 mN/m
Crude Oil-Gas Reservoir 5-30 mN/m
Crude Oil-Water 25°C 20-30 mN/m
n-Hexane-Air 25°C 18 mN/m
Water-Air 25°C 72 mN/m
Near critical - 0-1 mN/m

References

  1. Macleod, D.B. (1923). On a Relation between Surface Tension and Density. Trans. Faraday Soc.
  2. Sugden, S. (1924). The Variation of Surface Tension with Temperature and Some Related Functions. J. Chem. Soc.
  3. Cahn, J.W., Hilliard, J.E. (1958). Free Energy of a Nonuniform System. J. Chem. Phys.
  4. Miqueu, C., et al. (2004). Modelling of the Surface Tension of Pure Components with the Gradient Theory. Fluid Phase Equilib.
  5. Firozabadi, A., Ramey, H.J. (1988). Surface Tension of Water-Hydrocarbon Systems at Reservoir Conditions. JCPT.

Scale Potential

Scale Potential Calculation in NeqSim

Introduction

Scale formation is a critical challenge in oil and gas production, water treatment, and geothermal systems. When water becomes supersaturated with certain minerals, precipitation (scaling) can occur on equipment surfaces, leading to reduced flow, equipment damage, and costly interventions.

NeqSim provides tools to predict scale potential by calculating the saturation ratio (SR) for various mineral salts. This document covers the theory, implementation, usage, and best practices for scale potential calculations.

Theory

Saturation Ratio

The saturation ratio (also called saturation index or relative solubility) is the key parameter for assessing scale potential:

$$SR = \frac{IAP}{K_{sp}}$$

Where:

Interpretation

SR Value State Meaning
SR < 1 Undersaturated Salt will dissolve; no scaling risk
SR = 1 Saturated Equilibrium; at solubility limit
SR > 1 Supersaturated Precipitation thermodynamically favored
SR >> 1 Highly supersaturated High scaling risk

Note: SR > 1 indicates thermodynamic driving force for precipitation, but kinetics determine actual precipitation rate.

Ion Activity Product (IAP)

For a salt dissociating as:

$$M_{\nu_+}A_{\nu_-} \rightleftharpoons \nu_+ M^{z+} + \nu_- A^{z-}$$

The Ion Activity Product is:

$$IAP = a_{M^{z+}}^{\nu_+} \cdot a_{A^{z-}}^{\nu_-} = (\gamma_+ m_+)^{\nu_+} \cdot (\gamma_- m_-)^{\nu_-}$$

Where:

Solubility Product (K_sp)

The solubility product is the equilibrium constant for salt dissolution. In NeqSim, K_sp is calculated from temperature-dependent correlations:

$$\ln(K_{sp}) = \frac{A}{T} + B + C \cdot \ln(T) + D \cdot T + \frac{E}{T^2}$$

Where T is temperature in Kelvin. The coefficients A, B, C, D, E are stored in the database.

Common Scales in Oil & Gas

Carbonate Scales

Mineral Formula K_sp (25°C) Conditions
Calcite CaCO3 10^-8.48 Most common; pressure/CO2 dependent
Siderite FeCO3 10^-10.89 Iron-rich waters, CO2 systems
Magnesite MgCO3 10^-7.46 High Mg waters
Strontianite SrCO3 10^-9.27 Associated with barite

Sulfate Scales

Mineral Formula K_sp (25°C) Conditions
Gypsum CaSO4·2H2O 10^-4.58 T < 40°C, lower salinity
Anhydrite CaSO4 10^-4.36 T > 40°C, high salinity
Barite BaSO4 10^-9.97 Seawater mixing; very insoluble
Celestite SrSO4 10^-6.63 Associated with barite

Chloride Scales

Mineral Formula K_sp (25°C) Conditions
Halite NaCl 10^+1.58 Highly soluble; evaporative systems
Sylvite KCl 10^+0.85 Very soluble

Sulfide Scales

Mineral Formula K_sp (25°C) Conditions
Iron sulfide FeS varies Sour (H2S) systems

Implementation in NeqSim

Core Classes

  1. CheckScalePotential (neqsim.thermodynamicoperations.flashops.saturationops.CheckScalePotential)

    • Main calculation class
    • Reads salt data from COMPSALT database
    • Calculates SR for all applicable salts
  2. COMPSALT.csv (src/main/resources/data/COMPSALT.csv)

    • Database of salt properties
    • Contains ion pairs, stoichiometry, and K_sp correlation coefficients

Database Structure

The COMPSALT.csv file contains:

Column Description Example
ID Unique identifier 5
SaltName Mineral name CaSO4_A
ion1 Cation species Ca++
ion2 Anion species SO4--
stoc1 Cation stoichiometry 1.0
stoc2 Anion stoichiometry 1.0
Kspwater Coefficient A -19966.8
Kspwater2 Coefficient B 454.86
Kspwater3 Coefficient C -69.84
Kspwater4 Coefficient D 0.0
Kspwater5 Coefficient E 0.0

Supported Salts

NeqSim currently supports 21 salts:

Category Salts
Chlorides NaCl, KCl, HgCl2
Carbonates CaCO3, FeCO3, MgCO3, BaCO3, SrCO3, Na2CO3, K2CO3
Bicarbonates NaHCO3, KHCO3, Mg(HCO3)2
Sulfates CaSO4_A, CaSO4_G, BaSO4, SrSO4
Hydroxides Mg(OH)2
Sulfides FeS
Complex Hydromagnesite

Usage

Basic Example

import neqsim.thermo.system.SystemElectrolyteCPAstatoil;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create electrolyte system at 25°C, 1 bar
SystemInterface system = new SystemElectrolyteCPAstatoil(298.15, 1.0);

// Add water (1 kg basis)
system.addComponent("water", 1.0, "kg/sec");

// Add ions (molality = mol/kg water)
system.addComponent("Ca++", 0.01);   // 10 mmol/kg calcium
system.addComponent("SO4--", 0.01);  // 10 mmol/kg sulfate
system.addComponent("Na+", 0.5);     // 500 mmol/kg sodium
system.addComponent("Cl-", 0.5);     // 500 mmol/kg chloride

// Initialize system
system.chemicalReactionInit();
system.createDatabase(true);
system.setMixingRule(10);  // Electrolyte mixing rule

// Run flash calculation
ThermodynamicOperations ops = new ThermodynamicOperations(system);
ops.TPflash();
system.init(1);

// Calculate scale potential
int aqueousPhase = system.getPhaseNumberOfPhase("aqueous");
ops.checkScalePotential(aqueousPhase);

// Get results
String[][] results = ops.getResultTable();

// Print results (skip header row)
System.out.println("Salt\t\tSaturation Ratio");
for (int i = 1; i < results.length && results[i][0] != null && !results[i][0].isEmpty(); i++) {
    System.out.println(results[i][0] + "\t\t" + results[i][1]);
}

Example Output

Salt            Saturation Ratio
NaCl            0.00607
CaSO4_A         1.864
CaSO4_G         3.115

Interpreting Results

From the output above:

Temperature Effects

// Check scale potential at different temperatures
double[] temperatures = {283.15, 298.15, 323.15, 348.15, 373.15}; // 10-100°C

for (double T : temperatures) {
    SystemInterface sys = new SystemElectrolyteCPAstatoil(T, 1.0);
    sys.addComponent("water", 1.0, "kg/sec");
    sys.addComponent("Ca++", 0.01);
    sys.addComponent("SO4--", 0.01);

    sys.chemicalReactionInit();
    sys.createDatabase(true);
    sys.setMixingRule(10);

    ThermodynamicOperations ops = new ThermodynamicOperations(sys);
    ops.TPflash();
    sys.init(1);

    int aqPhase = sys.getPhaseNumberOfPhase("aqueous");
    ops.checkScalePotential(aqPhase);

    // Extract CaSO4 results...
}

Oilfield Brine Example

// Typical formation water composition
SystemInterface brine = new SystemElectrolyteCPAstatoil(353.15, 100.0); // 80°C, 100 bar

brine.addComponent("water", 1.0, "kg/sec");
brine.addComponent("Na+", 2.5);      // 2500 mmol/kg
brine.addComponent("Cl-", 2.8);      // 2800 mmol/kg
brine.addComponent("Ca++", 0.025);   // 25 mmol/kg
brine.addComponent("Mg++", 0.015);   // 15 mmol/kg
brine.addComponent("Ba++", 0.0001);  // 0.1 mmol/kg
brine.addComponent("Sr++", 0.002);   // 2 mmol/kg
brine.addComponent("SO4--", 0.001);  // 1 mmol/kg (low - formation water)
brine.addComponent("HCO3-", 0.005);  // 5 mmol/kg

brine.chemicalReactionInit();
brine.createDatabase(true);
brine.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(brine);
ops.TPflash();
brine.init(1);

int aqPhase = brine.getPhaseNumberOfPhase("aqueous");
ops.checkScalePotential(aqPhase);

Algorithm Details

Calculation Flow

  1. Load salt database - Read COMPSALT.csv
  2. For each salt in database:
    • Check if both ions are present in the system
    • Calculate K_sp from temperature correlation
    • Get ion mole fractions and convert to molality
    • Get activity coefficients from thermodynamic model
    • Calculate IAP = (γ₁·m₁)^ν₁ · (γ₂·m₂)^ν₂
    • Calculate SR = IAP / K_sp
  3. Return result table with salt names and SR values

Special Cases

NaCl

NaCl uses a polynomial correlation for K_sp instead of the standard ln(K_sp) form:

ksp = -814.18 + 7.4685*T - 2.3262e-2* + 3.0536e-5* - 1.4573e-8*T⁴

FeS (Iron Sulfide)

FeS calculation includes pH correction:

ksp *= [H3O+]  // Accounts for H2S dissociation equilibrium

Hydromagnesite

Complex mineral 3MgCO₃·Mg(OH)₂·3H₂O requires special handling with water and hydroxide activities.

MEG Systems

When MEG (monoethylene glycol) is present, the algorithm temporarily replaces MEG with water for the calculation to maintain consistent molality basis.

Accuracy and Limitations

Concentration Ranges

Concentration Expected Accuracy Notes
Dilute (< 0.1 mol/kg) ±10-20% Best accuracy range
Moderate (0.1-1.0 mol/kg) ±20-50% Good for most applications
Concentrated (> 1 mol/kg) > 50% Activity coefficients less accurate

Temperature Ranges

Temperature K_sp Accuracy Notes
0-50°C Good Well-calibrated correlations
50-100°C Moderate Some extrapolation
> 100°C Variable Validate against literature

Known Limitations

  1. Activity Coefficient Asymmetry

    • At high ionic strength (> 1 mol/kg), the electrolyte-CPA model may give asymmetric activity coefficients for cations and anions
    • Example: γ(Na+) = 0.29, γ(Cl-) = 9.27 at NaCl saturation (6.15 mol/kg)
    • Literature expects γ± ≈ 0.65-0.70
  2. Pressure Effects

    • Current correlations are for atmospheric pressure
    • High-pressure corrections not implemented
    • For deep-sea/downhole: consider pressure correction factor
  3. Complex Brines

    • Multi-component brines may cause matrix singularity in chemical equilibrium solver
    • Simplify to dominant ions if errors occur
  4. Mixed-Solvent Systems

    • MEG/water mixtures: algorithm applies water-based K_sp
    • Accuracy decreases at high MEG concentrations

K_sp Correlation Sources

The K_sp correlations in COMPSALT.csv are derived from:

Source Salts Reference
WATEQ4F Most minerals Nordstrom & Munoz (1990)
Langmuir CaSO4_A, MgCO3 Langmuir (1997)
Plummer & Busenberg CaCO3 Geochim. Cosmochim. Acta 46:1011 (1982)
NIST KCl Standard Reference Database 46
Pitzer NaCl Activity Coefficients in Electrolyte Solutions (1991)

Best Practices

1. Use Appropriate Concentration Units

Always specify ion concentrations in molality (mol/kg water) for consistency:

// Correct: explicit molality
system.addComponent("Ca++", 0.01);  // 0.01 mol/kg water

// For mass-based input, calculate molality
double CaPpm = 400;  // mg/L
double CaMolality = (CaPpm / 1000.0) / 40.08;  // Ca molar mass = 40.08
system.addComponent("Ca++", CaMolality);

2. Maintain Electroneutrality

Total positive charges should equal total negative charges:

// Check charge balance
double positiveCharge = 2*[Ca++] + 2*[Mg++] + [Na+] + [K+];
double negativeCharge = [Cl-] + 2*[SO4--] + [HCO3-] + 2*[CO3--];
// These should be approximately equal

3. Include Major Ions

Include all major ions even if not interested in their scales:

// Even if only checking CaSO4, include NaCl for ionic strength
system.addComponent("Na+", 0.5);
system.addComponent("Cl-", 0.5);
system.addComponent("Ca++", 0.01);
system.addComponent("SO4--", 0.01);

4. Validate at Known Conditions

Test the model at known saturation conditions:

// At NaCl saturation (6.15 mol/kg), SR should be ~1.0
// At BaSO4 saturation (~1e-5 mol/kg), SR should be ~1.0

5. Consider Kinetics

SR > 1 means precipitation is thermodynamically possible, not that it will occur immediately:

Troubleshooting

Common Issues

Issue Likely Cause Solution
Matrix is singular error Complex multi-ion system Simplify to major ions
Very high SR (> 10^10) Incorrect K_sp correlation Check database values
SR always 0 Ions not found in database Check ion names (e.g., "Ca++" not "Ca2+")
No results returned Ions not in system Verify addComponent calls
Negative SR Numerical error Check activity coefficient calculation

Ion Name Convention

Use NeqSim ion naming convention:

Ion NeqSim Name Common Alternatives (don't use)
Calcium Ca++ Ca2+, Ca(2+)
Magnesium Mg++ Mg2+, Mg(2+)
Sodium Na+ Na(+), Na1+
Barium Ba++ Ba2+, Ba(2+)
Sulfate SO4-- SO4(2-), SO42-
Carbonate CO3-- CO3(2-), CO32-
Bicarbonate HCO3- HCO3(-), HCO31-
Chloride Cl- Cl(-), Cl1-

Verification Results

K_sp Correlation Accuracy at 25°C (298.15 K)

All K_sp correlations have been verified against literature values:

Salt Calculated log₁₀(K_sp) Literature log₁₀(K_sp) Error Source
CaSO4_A (Anhydrite) -4.356 -4.360 0.004 Langmuir 1997
CaSO4_G (Gypsum) -4.581 -4.580 0.001 WATEQ4F
SrSO4 (Celestite) -6.631 -6.630 0.001 WATEQ4F
BaSO4 (Barite) -9.970 -9.970 0.000 WATEQ4F
KCl (Sylvite) 0.851 0.850 0.001 NIST
NaCl (Halite) 1.582 1.580 0.002 Pitzer 1991
MgCO3 (Magnesite) -7.491 -7.460 0.031 Langmuir 1997
CaCO3 (Calcite) -8.531 -8.480 0.051 Plummer 1982
FeCO3 (Siderite) -10.89 -10.89 0.000 WATEQ4F
Mg(OH)₂ (Brucite) -11.16 -11.16 0.000 WATEQ4F

All errors are < 0.1 in log₁₀(K_sp), corresponding to < 25% error in K_sp.

Saturation Ratio Validation (BaSO4)

BaSO4 was tested at moderate concentrations where the electrolyte model is most accurate:

Ba²⁺ Molality (mol/kg) SO₄²⁻ Molality (mol/kg) Calculated SR Expected SR Accuracy
5.0×10⁻⁶ 5.0×10⁻⁶ 0.21 ~0.25 ✓ Good
1.0×10⁻⁵ 1.0×10⁻⁵ 0.85 ~1.0 ✓ Good
2.0×10⁻⁵ 2.0×10⁻⁵ 3.38 ~4.0 ✓ Good
5.0×10⁻⁵ 5.0×10⁻⁵ 21.1 ~25 ✓ Good
1.0×10⁻⁴ 1.0×10⁻⁴ 84.5 ~100 ✓ Good

The SR scales correctly with concentration squared (as expected for a 1:1 salt where SR ∝ [Ba²⁺][SO₄²⁻]).

Activity Coefficient Verification

At moderate ionic strength (< 0.1 mol/kg), activity coefficients are accurate:

System Ionic Strength Calculated γ± Literature γ± Error
NaCl 0.1 mol/kg 0.1 0.778 0.778 < 1%
CaCl₂ 0.01 mol/kg 0.03 0.732 0.729 < 1%
BaSO4 at saturation ~2×10⁻⁵ 0.92 ~0.90 ~2%

Note: At high ionic strength (> 1 mol/kg), activity coefficients become less accurate due to model limitations.

Recent Improvements (2025)

Fixed K_sp Correlations

Several salt correlations were corrected:

Salt Previous Error Status
CaSO4_A (Anhydrite) 56 orders of magnitude ✅ Fixed
SrSO4 (Celestite) 7 orders of magnitude ✅ Fixed
KCl (Sylvite) 2.6 orders of magnitude ✅ Fixed
MgCO3 (Magnesite) 2.3 orders of magnitude ✅ Fixed

Bug Fixes

References

  1. Nordstrom, D.K. and Munoz, J.L. (1990). Geochemical Thermodynamics, 2nd ed. Blackwell Scientific.

  2. Langmuir, D. (1997). Aqueous Environmental Geochemistry. Prentice Hall.

  3. Plummer, L.N. and Busenberg, E. (1982). "The solubilities of calcite, aragonite and vaterite in CO2-H2O solutions." Geochimica et Cosmochimica Acta 46:1011-1040.

  4. Pitzer, K.S. (1991). Activity Coefficients in Electrolyte Solutions, 2nd ed. CRC Press.

  5. NIST Standard Reference Database 46 - Critically Selected Stability Constants of Metal Complexes.

  6. Appelo, C.A.J. and Postma, D. (2005). Geochemistry, Groundwater and Pollution, 2nd ed. Balkema.

See Also

Steam Tables

IF97 Steam Tables

This page documents the basic equations implemented in Iapws_if97.

Saturation equations

For the saturation line (Region 4) the following equations are used:

Pressure as function of temperature:

[ \ln(p) = 4 \cdot \ln\left(\frac{2 C}{-B + \sqrt{B^2-4 A C}}\right) ]

where

[ A = \theta^2 + 1167.0521452767\,\theta - 724213.16703206\ B = -17.073846940092\,\theta^2 + 12020.82470247\,\theta - 3232555.0322333\ C = 14.91510861353\,\theta^2 - 4823.2657361591\,\theta + 405113.40542057\ \theta = T - \frac{0.23855557567849}{T-650.17534844798} ]

Temperature as function of pressure is obtained by solving the inverse relation.

Region 1 and 2

The specific Gibbs free energy is expressed with dimensionless variables (\pi) and (\tau). For region 1

[ \gamma(\pi,\tau)=\sum n_i (7.1-\pi)^{I_i} (\tau-1.222)^{J_i} ]

while region 2 uses an ideal and residual part

[ \gamma(\pi,\tau)=\ln\pi + \sum n_i^0\tau^{J_i^0} + \sum n_i^r\pi^{I_i^r}(\tau-0.5)^{J_i^r} ]

Thermodynamic properties follow from derivatives of (\gamma):

[ v = \frac{R T}{p}\,\pi\, \gamma_{\pi} \quad\quad h = R T\tau\, \gamma_{\tau} \ s = R(\tau\gamma_{\tau}-\gamma) ]

where (R=0.461526\,\mathrm{kJ\,kg^{-1}\,K^{-1}}).

Thermodynamic Workflows

Thermodynamic Workflows

Use these recipes to configure fluids and run equilibrium calculations with NeqSim. The Java snippets mirror the workflow used in other language bindings.

1. Build a Fluid

SystemInterface fluid = new SystemPrEos(313.15, 80.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.05);
fluid.addTBPfraction("C7+", 0.10, 0.45, 8.0); // name, moles, density [g/cc], MW
fluid.createDatabase(true); // enable access to component data
fluid.setMixingRule(1); // classical van der Waals mixing rule
fluid.init(0);

Tips:

2. Choose a Model and Mixing Rule

For lean gas, start with PR and kij from correlations; for rich liquids or polar systems, move to SRK-Twu + Huron–Vidal or CPA-SRK.

3. Run Thermodynamic Operations

Instantiate ThermodynamicOperations with the configured fluid to access flash and envelope tools:

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
fluid.initProperties();
System.out.println("Vapor fraction: " + fluid.getPhaseFraction(0));

Common operations include:

4. Save and Reuse States

Export an EOS state to JSON or clone fluids when sweeping conditions:

SystemInterface clone = fluid.clone();
clone.setTemperature(280.0);
clone.setPressure(10.0);
new ThermodynamicOperations(clone).TPflash();

5. Debugging and Validation

Interaction Tables

INTER Table: Binary Interaction Coefficients Database

This guide documents the INTER table in NeqSim, which contains binary interaction parameters (BIPs) for thermodynamic models including equations of state (EoS), activity coefficient models, and CPA association parameters.

Table of Contents


Overview

The INTER table is the central repository for all binary interaction parameters in NeqSim. It stores parameters for:

  1. Cubic EoS - Classical $k_{ij}$ values for SRK and PR
  2. CPA EoS - Association and $k_{ij}$ for CPA model
  3. Huron-Vidal - $\alpha$, $g_{ij}$, $g_{ji}$ parameters
  4. Wong-Sandler - Integration with UNIFAC
  5. NRTL - $\alpha$, $g_{ij}$, $g_{ji}$ for activity coefficients
  6. Soreide-Whitson - Oil-water systems
  7. Desmukh-Mather - Amine systems

Location: src/main/resources/data/INTER.csv

Database table name: INTER (or INTERTEMP for temporary tables)


Table Structure

The INTER table contains one row per component pair. Each row has parameters for multiple thermodynamic models, allowing the same fluid definition to be used with different mixing rules.

Example entry:

ID,COMP1,COMP2,HVTYPE,KIJSRK,KIJTSRK,KIJTType,KIJPR,KIJTPR,KIJPCSAFT,...
6950,CO2,methane,Classic,0.0973,0,0,0.0973,0,...

Column Reference

Component Pair Identification

Column Type Description
ID Integer Unique row identifier
COMP1 String First component name (must match COMP table)
COMP2 String Second component name (must match COMP table)

Important: Component pairs are symmetric. NeqSim searches for both (COMP1=A, COMP2=B) and (COMP1=B, COMP2=A).


EoS Interaction Parameters

These parameters are used in the classical van der Waals mixing rules for cubic equations of state.

Column Type Description Used By
KIJSRK Double Binary interaction parameter for SRK EoS SystemSrkEos
KIJTSRK Double Temperature correction to $k_{ij}$ for SRK SRK with T-dependent kij
KIJTType Integer Type of temperature dependence (0, 1, 2) All EoS
KIJPR Double Binary interaction parameter for PR EoS SystemPrEos
KIJTPR Double Temperature correction to $k_{ij}$ for PR PR with T-dependent kij
KIJPCSAFT Double Binary interaction for PC-SAFT EoS SystemPCSAFT

Temperature dependence types (KIJTType):

Mathematical role:

The $k_{ij}$ parameter appears in the classical mixing rule for the EoS attractive parameter:

$$a_{mix} = \sum_i \sum_j x_i x_j \sqrt{a_i a_j} (1 - k_{ij})$$

A positive $k_{ij}$ reduces the attractive interactions between unlike molecules.


Huron-Vidal Parameters

Huron-Vidal mixing rules combine an EoS with an activity coefficient model.

Column Type Description
HVTYPE String Type of mixing rule ("Classic" or "HV")
HVALPHA Double NRTL non-randomness parameter ($\alpha$)
HVGIJ Double NRTL interaction parameter $g_{ij}$ (J/mol)
HVGJI Double NRTL interaction parameter $g_{ji}$ (J/mol)
HVGIJT Double Temperature derivative of $g_{ij}$
HVGJIT Double Temperature derivative of $g_{ji}$

Note: When HVTYPE = "Classic", the Huron-Vidal parameters are not used.

Mathematical formulation:

$$\tau_{ij} = \frac{g_{ij} - g_{jj}}{RT} = \frac{\Delta g_{ij}}{RT}$$

$$G_{ij} = \exp(-\alpha_{ij} \tau_{ij})$$


Wong-Sandler Parameters

Wong-Sandler mixing rules provide thermodynamic consistency between high and low pressure limits.

Column Type Description
WSTYPE String Type ("Classic" or "WS")
KIJWS Double Wong-Sandler interaction parameter
KIJWSunifac Double WS parameter for UNIFAC integration
CalcWij Integer Flag for calculated vs fitted W parameters
W1, W2, W3 Double Second virial coefficient parameters
WSGIJT Double Temperature derivative for WS $g_{ij}$
WSGJIT Double Temperature derivative for WS $g_{ji}$

NRTL Parameters

Non-Random Two-Liquid parameters for activity coefficient calculations.

Column Type Description
NRTLALPHA Double Non-randomness parameter $\alpha_{ij}$ (typically 0.2-0.47)
NRTLGIJ Double Interaction parameter $g_{ij}$ (J/mol)
NRTLGJI Double Interaction parameter $g_{ji}$ (J/mol)

NRTL Activity Coefficient:

$$\ln \gamma_i = \frac{\sum_j x_j \tau_{ji} G_{ji}}{\sum_k x_k G_{ki}} + \sum_j \frac{x_j G_{ij}}{\sum_k x_k G_{kj}} \left( \tau_{ij} - \frac{\sum_m x_m \tau_{mj} G_{mj}}{\sum_k x_k G_{kj}} \right)$$


CPA Association Parameters

These parameters control cross-association in the CPA (Cubic Plus Association) equation of state.

Column Type Description
cpakij_SRK Double SRK-CPA binary interaction parameter
cpakijT_SRK Double Temperature correction for CPA $k_{ij}$
cpakijx_SRK Double Composition-dependent $k_{ij}$ (asymmetric)
cpakjix_SRK Double Composition-dependent $k_{ji}$ (asymmetric)
cpakij_PR Double PR-CPA binary interaction parameter
cpaAssosiationType Integer Association scheme type (0, 1, 2, ...)
cpaBetaCross Double Cross-association volume parameter $\beta^{AB}$
cpaEpsCross Double Cross-association energy parameter $\epsilon^{AB}$ (K)

Association scheme types:

Cross-association combining rules:

When cpaAssosiationType = 1 (CR1/Elliott rule): $$\epsilon^{AB}_{ij} = \frac{\epsilon^{AA}_{ii} + \epsilon^{BB}_{jj}}{2}$$ $$\beta^{AB}_{ij} = \sqrt{\beta^{AA}_{ii} \beta^{BB}_{jj}}$$

When explicit values are provided in cpaBetaCross and cpaEpsCross, they override the combining rules.


Physical Property Parameters

Column Type Description
GIJVISC Double Viscosity mixing parameter
KIJWhitsonSoriede Double Soreide-Whitson correlation parameter

Desmukh-Mather Parameters

For amine-gas systems with the Desmukh-Mather model.

Column Type Description
aijDesMath Double Desmukh-Mather $a_{ij}$ parameter
bijDesMath Double Desmukh-Mather $b_{ij}$ parameter

Relation to Mixing Rules

The INTER table parameters are loaded when you call setMixingRule(). The mixing rule number determines which columns are read:

Mixing Rule Number Parameters Used
Classic (kij=0) 1 None (all kij set to 0)
Classic (from database) 2 KIJSRK/KIJPR
Classic + T-dependent 3 KIJSRK, KIJTSRK, KIJTType
Huron-Vidal 4 HVALPHA, HVGIJ, HVGJI
Wong-Sandler 5 KIJWS, W1-W3, NRTL params
CPA 7 cpakij_SRK, cpaBetaCross, cpaEpsCross
CPA + T-dependent 9 + cpakijT_SRK
CPA + composition-dependent 10 + cpakijx_SRK, cpakjix_SRK

Example:

// Classic with database kij
fluid.setMixingRule(2);  // or fluid.setMixingRule("classic");

// Uses KIJSRK/KIJPR columns from INTER table

Usage Examples

Accessing Binary Interaction Parameters

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermo.phase.PhaseEos;

SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.8);
fluid.addComponent("CO2", 0.2);
fluid.setMixingRule("classic");  // Loads from INTER table

// Get kij value
PhaseEos phase = (PhaseEos) fluid.getPhase(0);
double kij = phase.getMixingRule().getBinaryInteractionParameter(0, 1);
System.out.println("kij(methane-CO2) = " + kij);  // 0.0973

Setting Custom Binary Parameters

// Method 1: Using component names (recommended)
fluid.setBinaryInteractionParameter("methane", "CO2", 0.10);

// Method 2: Using component indices
((PhaseEos) fluid.getPhase(0)).getMixingRule()
    .setBinaryInteractionParameter(0, 1, 0.10);
((PhaseEos) fluid.getPhase(1)).getMixingRule()
    .setBinaryInteractionParameter(0, 1, 0.10);

Setting Temperature-Dependent Parameters

// Set temperature coefficient
((PhaseEos) fluid.getPhase(0)).getMixingRule()
    .setBinaryInteractionParameterT1(0, 1, -0.001);

// kij(T) = kij + kijT * T  (when KIJTType=2)

Getting All Binary Parameters

double[][] kijMatrix = ((PhaseEos) fluid.getPhase(0))
    .getMixingRule().getBinaryInteractionParameters();

// Print matrix
for (int i = 0; i < fluid.getNumberOfComponents(); i++) {
    for (int j = 0; j < fluid.getNumberOfComponents(); j++) {
        System.out.print(kijMatrix[i][j] + " ");
    }
    System.out.println();
}

CPA Cross-Association Example

SystemInterface fluid = new SystemSrkCPAstatoil(300.0, 50.0);
fluid.addComponent("methane", 0.7);
fluid.addComponent("water", 0.2);
fluid.addComponent("MEG", 0.1);
fluid.setMixingRule(10);  // CPA with composition-dependent kij

// Cross-association parameters are loaded automatically:
// - cpaBetaCross for methane-water
// - cpaEpsCross for water-MEG association

Setting Custom Parameters

Adding New Component Pairs

To add interaction parameters for a new component pair:

  1. Add to INTER.csv:
99999,newcomp1,newcomp2,Classic,0.05,0,0,0.05,0,0,0,0,0,0,0,0,0,Classic,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.05
  1. Or set programmatically:
fluid.setBinaryInteractionParameter("newcomp1", "newcomp2", 0.05);

Asymmetric Parameters

Some mixing rules support asymmetric $k_{ij} \neq k_{ji}$:

// Set kij (component i with j)
((PhaseEos) fluid.getPhase(0)).getMixingRule()
    .setBinaryInteractionParameterij(i, j, 0.05);

// Set kji (component j with i)
((PhaseEos) fluid.getPhase(0)).getMixingRule()
    .setBinaryInteractionParameterji(i, j, 0.03);

TBP Fractions and Default Values

For undefined pseudo-components (TBP fractions), NeqSim uses correlations or default values:

Default kij Values for TBP Fractions

Component TBP Fraction Default $k_{ij}$
CO2 All 0.10
N2 All 0.08
H2O All 0.20
MEG All 0.20

Calculated Parameters

When setCalcEOSInteractionParameters(true) is called, NeqSim calculates $k_{ij}$ from critical volumes:

$$k_{ij} = 1 - \left( \frac{2 \sqrt[3]{V_{c,i} V_{c,j}}}{V_{c,i}^{1/3} + V_{c,j}^{1/3}} \right)^n$$

where $n$ is configurable (default = 6).


Column Summary Table

Category Columns
Identification ID, COMP1, COMP2
SRK EoS KIJSRK, KIJTSRK
PR EoS KIJPR, KIJTPR
PC-SAFT KIJPCSAFT
Temperature Type KIJTType
Huron-Vidal HVTYPE, HVALPHA, HVGIJ, HVGJI, HVGIJT, HVGJIT
Wong-Sandler WSTYPE, KIJWS, KIJWSunifac, CalcWij, W1, W2, W3, WSGIJT, WSGJIT
NRTL NRTLALPHA, NRTLGIJ, NRTLGJI
CPA cpakij_SRK, cpakijT_SRK, cpakijx_SRK, cpakjix_SRK, cpakij_PR, cpaAssosiationType, cpaBetaCross, cpaEpsCross
Physical Properties GIJVISC
Soreide-Whitson KIJWhitsonSoriede
Desmukh-Mather aijDesMath, bijDesMath

Typical Parameter Values

Common $k_{ij}$ Values (SRK/PR)

Pair $k_{ij}$ Notes
CH4 - C2H6 0.003 Very similar molecules
CH4 - CO2 0.097 Significant non-ideality
CH4 - H2S 0.08 Acid gas
CH4 - N2 0.032 Typical
CH4 - H2O 0.45-0.65 Polar-nonpolar (CPA: ~-0.08)
CO2 - H2S 0.10-0.12 Acid gas pair
CO2 - C3H8 0.12-0.14
H2O - MEG 0.13 CPA model

CPA Association Parameters

Pair $\beta^{AB}$ $\epsilon^{AB}$ (K)
H2O - MEG 0.055 2000-2500
H2O - methanol 0.039 2000-2500
CO2 - H2O 0.085 0 (solvation)

References

  1. Soave, G. (1972). Equilibrium Constants from a Modified Redlich-Kwong Equation of State. Chem. Eng. Sci.
  2. Peng, D.Y., Robinson, D.B. (1976). A New Two-Constant Equation of State. Ind. Eng. Chem. Fundam.
  3. Huron, M.J., Vidal, J. (1979). New Mixing Rules in Simple Equations of State. Fluid Phase Equilib.
  4. Wong, D.S.H., Sandler, S.I. (1992). A Theoretically Correct Mixing Rule. AIChE J.
  5. Kontogeorgis, G.M., et al. (2006). Multicomponent Phase Equilibrium Calculations for Water-Methanol-Alkane Mixtures. Fluid Phase Equilib.
  6. Soreide, I., Whitson, C.H. (1992). Peng-Robinson Predictions for Hydrocarbons, CO2, N2, and H2S with Pure Water and NaCl Brine. Fluid Phase Equilib.

Chapter 10: Hydrates & Flow Assurance

Hydrate Models

Hydrate Models in NeqSim

This document describes the gas hydrate thermodynamic models implemented in NeqSim for predicting hydrate formation, stability, and phase equilibrium.

Table of Contents


Overview

Gas hydrates (clathrate hydrates) are ice-like crystalline compounds formed when water molecules create cage structures that encapsulate gas molecules (guest molecules) under high pressure and low temperature conditions. NeqSim provides comprehensive models for:


Hydrate Structures

NeqSim supports two common hydrate crystal structures:

Structure I (sI)

Property Small Cavity (5¹²) Large Cavity (5¹²6²)
Coordination Number 20 24
Cavity Radius (Å) 3.95 4.33
Number per Unit Cell 2 6
Water per Cavity 1/23 3/23

Typical Guest Molecules: Methane, ethane, CO₂, H₂S

Unit Cell Formula: 46 H₂O · 8 guest molecules (2 small + 6 large cavities)

Structure II (sII)

Property Small Cavity (5¹²) Large Cavity (5¹²6⁴)
Coordination Number 20 28
Cavity Radius (Å) 3.91 4.73
Number per Unit Cell 16 8
Water per Cavity 2/17 1/17

Typical Guest Molecules: Propane, isobutane, natural gas mixtures

Unit Cell Formula: 136 H₂O · 24 guest molecules (16 small + 8 large cavities)

Structure Selection

The algorithm automatically selects the most stable structure based on Gibbs energy minimization. For mixed gases, the structure depends on composition:

// Get the stable hydrate structure (1 = sI, 2 = sII)
int structure = fluid.getPhase(PhaseType.HYDRATE).getComponent("methane").getHydrateStructure();

Thermodynamic Framework

van der Waals-Platteeuw Theory

NeqSim implements the classical van der Waals-Platteeuw (vdWP) statistical thermodynamic model as the foundation for hydrate calculations.

The chemical potential difference between water in hydrate and empty hydrate lattice:

$$\Delta\mu_w^H = -RT \sum_{i=1}^{N_{cav}} \nu_i \ln\left(1 - \sum_{j=1}^{N_g} Y_{ij}\right)$$

where:

Cavity Occupancy (Langmuir Adsorption)

The cavity occupancy follows the Langmuir isotherm:

$$Y_{ij} = \frac{C_{ij} f_j}{1 + \sum_{k=1}^{N_g} C_{ik} f_k}$$

where:

Langmuir Constants

The Langmuir constants are calculated from the cell potential:

$$C_{ij}(T) = \frac{4\pi}{k_B T} \int_0^{R_{cell}} \exp\left(-\frac{w(r)}{k_B T}\right) r^2 dr$$

where $w(r)$ is the spherically averaged Kihara cell potential.


Available Hydrate Models

CPA Hydrate Model (Statoil/Equinor)

Class: ComponentHydrateGF, ComponentHydrateStatoil

The CPA (Cubic Plus Association) hydrate model is the recommended model for systems containing polar components like water, MEG, and methanol. It uses the CPA equation of state for fugacity calculations.

Key Features:

Usage:

SystemInterface fluid = new SystemSrkCPAstatoil(273.15, 100.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("water", 0.1);
fluid.setMixingRule(10);  // CPA mixing rule
fluid.setHydrateCheck(true);

PVTsim Hydrate Model

Class: ComponentHydratePVTsim

The default hydrate model based on the PVTsim approach, suitable for hydrocarbon-water systems.

Key Features:

Usage:

SystemInterface fluid = new SystemSrkEos(273.15, 100.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("water", 0.1);
fluid.setMixingRule("classic");
fluid.setHydrateCheck(true);

Ballard Model

Class: ComponentHydrateBallard

Based on the work of Ballard (2002), this model uses an improved approach for Langmuir constant calculation.

Kluda Model

Class: ComponentHydrateKluda

Alternative hydrate model with different parameterization for specific applications.


Component Parameters

Hydrate-specific parameters are stored in the component database:

Parameter Description Unit
LJdiameterHYDRATE Lennard-Jones diameter for hydrate cavity Å
LJepsHYDRATE Lennard-Jones energy parameter K
sphericalCoreRadius Kihara spherical core radius Å

Hydrate Formers

Components that can occupy hydrate cavities (hydrate formers):

Component Structure Small Cavity Large Cavity
Methane sI, sII
Ethane sI -
Propane sII -
i-Butane sII -
CO₂ sI
H₂S sI, sII
Nitrogen sII

Check if a component is a hydrate former:

boolean isFormer = fluid.getPhase(0).getComponent("methane").isHydrateFormer();

Hydrate Inhibitors

NeqSim supports thermodynamic hydrate inhibitors that shift the hydrate equilibrium curve:

Supported Inhibitors

Inhibitor Common Name Effect
MEG Monoethylene glycol Lowers hydrate temperature
TEG Triethylene glycol Lowers hydrate temperature
methanol Methanol Strong temperature depression
ethanol Ethanol Moderate temperature depression
NaCl Salt Salinity effect

Inhibitor Calculations

// Calculate required MEG concentration for target temperature
SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 5.0, 80.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.05);
fluid.addComponent("water", 0.08);
fluid.addComponent("MEG", 0.02);
fluid.setMixingRule(10);
fluid.setHydrateCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);

// Calculate inhibitor concentration needed to prevent hydrate at 5°C
ops.hydrateInhibitorConcentration("MEG", 273.15 + 5.0);
double requiredMEGwt = fluid.getPhase("aqueous").getComponent("MEG").getwtfrac();

Usage Examples

Basic Hydrate Check

import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create fluid at potential hydrate conditions
SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 5.0, 100.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.04);
fluid.addComponent("water", 0.03);
fluid.setMixingRule(10);
fluid.setHydrateCheck(true);  // Enable hydrate phase

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);

// Calculate hydrate formation temperature
ops.hydrateFormationTemperature();
System.out.println("Hydrate formation T: " + fluid.getTemperature("C") + " °C");

Hydrate Equilibrium Curve

// Generate hydrate PT curve
double[] pressures = {10, 20, 50, 100, 150, 200}; // bar
System.out.println("P (bar)\tT_hydrate (°C)");

for (double P : pressures) {
    fluid.setPressure(P);
    fluid.setTemperature(280.0);  // Initial guess
    ops.hydrateFormationTemperature();
    System.out.println(P + "\t" + fluid.getTemperature("C"));
}

Cavity Occupancy

// Get cavity occupancy for each guest molecule
PhaseInterface hydratePhase = fluid.getPhase(PhaseType.HYDRATE);

for (int i = 0; i < hydratePhase.getNumberOfComponents(); i++) {
    if (hydratePhase.getComponent(i).isHydrateFormer()) {
        ComponentHydrate comp = (ComponentHydrate) hydratePhase.getComponent(i);
        double smallCavity = comp.calcYKI(0, 0, hydratePhase);  // Structure I, small cavity
        double largeCavity = comp.calcYKI(0, 1, hydratePhase);  // Structure I, large cavity
        System.out.println(comp.getName() + 
            " - Small: " + smallCavity + ", Large: " + largeCavity);
    }
}

References

  1. van der Waals, J.H., Platteeuw, J.C. (1959). "Clathrate Solutions." Advances in Chemical Physics, 2, 1-57.

  2. Sloan, E.D., Koh, C.A. (2008). Clathrate Hydrates of Natural Gases, 3rd ed. CRC Press.

  3. Ballard, A.L., Sloan, E.D. (2002). "The next generation of hydrate prediction: I. Hydrate standard states and incorporation of spectroscopy." Fluid Phase Equilibria, 194-197, 371-383.

  4. Kontogeorgis, G.M., et al. (2006). "Ten Years with the CPA (Cubic-Plus-Association) Equation of State." Industrial & Engineering Chemistry Research, 45, 4855-4868.

  5. Munck, J., Skjold-Jørgensen, S., Rasmussen, P. (1988). "Computations of the formation of gas hydrates." Chemical Engineering Science, 43, 2661-2672.


Hydrate Flash

Hydrate Flash Operations in NeqSim

This document provides comprehensive documentation for hydrate phase equilibrium flash calculations in NeqSim.

Table of Contents


Overview

NeqSim provides specialized flash calculations for systems containing gas hydrates. These operations extend the standard thermodynamic operations to include hydrate phase equilibrium.

Key Classes:


Hydrate Flash Types

Hydrate TPflash

Performs a temperature-pressure flash calculation including hydrate phase equilibrium. This is the main method for calculating hydrate phase fraction and composition at given T and P.

Method: ThermodynamicOperations.hydrateTPflash()

Algorithm:

  1. Perform standard TPflash (gas/liquid/aqueous equilibrium)
  2. Calculate hydrate water fugacity using vdWP model
  3. Compare with fluid water fugacity
  4. If hydrate is stable (lower fugacity), calculate hydrate fraction
  5. Update system with hydrate phase

Example:

SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 5.0, 100.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.04);
fluid.addComponent("water", 0.03);
fluid.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.hydrateTPflash();

// Check results
System.out.println("Number of phases: " + fluid.getNumberOfPhases());
System.out.println("Has hydrate: " + fluid.hasHydratePhase());
if (fluid.hasHydratePhase()) {
    System.out.println("Hydrate fraction: " + fluid.getBeta(PhaseType.HYDRATE));
}
fluid.prettyPrint();

Output Phases:

Phase Type Description
0 GAS Vapor phase with dissolved water
1 AQUEOUS Water-rich phase
2+ HYDRATE Clathrate hydrate phase

Gas-Hydrate TPflash

Specialized flash targeting gas-hydrate equilibrium without aqueous phase. This is useful for systems with trace water where all water can be consumed by hydrate formation.

Method: ThermodynamicOperations.gasHydrateTPflash()

When to Use:

Example:

// Dry gas with trace water
SystemInterface fluid = new SystemSrkCPAstatoil(273.15 - 15.0, 250.0);
fluid.addComponent("methane", 0.9998);
fluid.addComponent("water", 0.0002);  // 200 ppm water
fluid.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.gasHydrateTPflash();

// Result: GAS + HYDRATE phases (no AQUEOUS)
boolean hasAqueous = false;
for (int i = 0; i < fluid.getNumberOfPhases(); i++) {
    if (fluid.getPhase(i).getType() == PhaseType.AQUEOUS) {
        hasAqueous = true;
    }
}
System.out.println("Has aqueous phase: " + hasAqueous);  // false

Algorithm:

  1. Perform standard TPflash
  2. Enable gas-hydrate-only mode
  3. Calculate hydrate equilibrium from gas phase fugacity
  4. Remove aqueous phase if all water consumed by hydrate
  5. Redistribute phase fractions

Hydrate Formation Temperature

Calculates the temperature at which hydrate first forms at given pressure.

Methods:

void hydrateFormationTemperature()
void hydrateFormationTemperature(double initialGuess)
void hydrateFormationTemperature(int structure)  // 0=ice, 1=sI, 2=sII

Example:

SystemInterface fluid = new SystemSrkCPAstatoil(280.0, 100.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.05);
fluid.addComponent("CO2", 0.02);
fluid.addComponent("water", 0.03);
fluid.setMixingRule(10);
fluid.setHydrateCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.hydrateFormationTemperature();

System.out.println("Hydrate formation T: " + fluid.getTemperature("C") + " °C");
System.out.println("At pressure: " + fluid.getPressure("bara") + " bara");

Hydrate Formation Pressure

Calculates the pressure at which hydrate first forms at given temperature.

Method:

void hydrateFormationPressure()
void hydrateFormationPressure(int structure)

Example:

fluid.setTemperature(278.15);  // 5°C
ops.hydrateFormationPressure();
System.out.println("Hydrate formation P: " + fluid.getPressure("bara") + " bara");

Hydrate Inhibitor Calculations

Calculate required inhibitor concentration to prevent hydrate formation.

Methods:

// Calculate inhibitor needed for target temperature
void hydrateInhibitorConcentration(String inhibitor, double targetTemperature)

// Set inhibitor weight fraction and calculate effect
void hydrateInhibitorConcentrationSet(String inhibitor, double wtFraction)

Supported Inhibitors:

Example:

SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 5.0, 100.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("water", 0.10);
fluid.addComponent("MEG", 0.05);
fluid.setMixingRule(10);
fluid.setHydrateCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);

// What MEG concentration prevents hydrate at 5°C?
ops.hydrateInhibitorConcentration("MEG", 273.15 + 5.0);
System.out.println("Required MEG (wt%): " + 
    fluid.getPhase("aqueous").getComponent("MEG").getwtfrac() * 100);

Hydrate Equilibrium Line

Generate the complete hydrate equilibrium curve (P-T diagram).

Class: HydrateEquilibriumLine

Example:

SystemInterface fluid = new SystemSrkCPAstatoil(280.0, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("water", 0.1);
fluid.setMixingRule(10);
fluid.setHydrateCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.calcHydrateEquilibriumLine();

// Get curve data
double[][] curve = ops.getOperation().get2DData();
// curve[0] = temperatures (K)
// curve[1] = pressures (bar)

Multi-Phase Equilibrium

Gas-Aqueous-Hydrate

The most common scenario: gas in equilibrium with water and hydrate.

SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 2.0, 80.0);
fluid.addComponent("methane", 0.80);
fluid.addComponent("ethane", 0.05);
fluid.addComponent("propane", 0.03);
fluid.addComponent("n-butane", 0.01);
fluid.addComponent("CO2", 0.01);
fluid.addComponent("water", 0.10);
fluid.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.hydrateTPflash();

// Expected phases: GAS, AQUEOUS, HYDRATE
fluid.prettyPrint();

Gas-Oil-Aqueous-Hydrate

Four-phase equilibrium with condensate/oil phase.

// Rich gas condensate with water
SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 4.0, 100.0);

// Gas components
fluid.addComponent("methane", 0.70);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.05);
fluid.addComponent("n-butane", 0.02);
fluid.addComponent("n-pentane", 0.01);

// Oil/condensate components
fluid.addComponent("n-hexane", 0.01);
fluid.addComponent("n-heptane", 0.02);
fluid.addComponent("n-octane", 0.01);

// Water
fluid.addComponent("water", 0.10);

fluid.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.hydrateTPflash();

// Expected phases: GAS, OIL, AQUEOUS, HYDRATE
System.out.println("Number of phases: " + fluid.getNumberOfPhases());
for (int i = 0; i < fluid.getNumberOfPhases(); i++) {
    System.out.println("Phase " + i + ": " + fluid.getPhase(i).getType() + 
        ", beta = " + fluid.getBeta(i));
}

Gas-Hydrate Only (No Aqueous)

For systems with very low water content where hydrate consumes all water.

// 500 ppm water at extreme conditions
SystemInterface fluid = new SystemSrkCPAstatoil(273.15 - 20.0, 300.0);
fluid.addComponent("methane", 0.9995);
fluid.addComponent("water", 0.0005);
fluid.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.gasHydrateTPflash();

// Expected phases: GAS, HYDRATE (no AQUEOUS)
boolean hasAqueous = false;
for (int i = 0; i < fluid.getNumberOfPhases(); i++) {
    if (fluid.getPhase(i).getType() == PhaseType.AQUEOUS) {
        hasAqueous = true;
    }
}
System.out.println("Has aqueous: " + hasAqueous);  // false

API Reference

ThermodynamicOperations Methods

Method Description
hydrateTPflash() TP flash with hydrate equilibrium
hydrateTPflash(boolean checkForSolids) TP flash with solid check
gasHydrateTPflash() TP flash targeting gas-hydrate equilibrium
hydrateFormationTemperature() Calculate hydrate formation T
hydrateFormationTemperature(double guess) With initial guess
hydrateFormationTemperature(int structure) For specific structure
hydrateFormationPressure() Calculate hydrate formation P
hydrateFormationPressure(int structure) For specific structure
hydrateInhibitorConcentration(String, double) Calculate inhibitor needed
hydrateInhibitorConcentrationSet(String, double) Set inhibitor and calculate
calcHydrateEquilibriumLine() Generate PT curve

SystemInterface Methods

Method Description
setHydrateCheck(boolean) Enable/disable hydrate phase
hasHydratePhase() Check if hydrate exists
getHydrateFraction() Get hydrate mole fraction

TPHydrateFlash Methods

Method Description
isHydrateFormed() Check if hydrate formed
getHydrateFraction() Get hydrate beta value
getStableHydrateStructure() Get structure (1 or 2)
getCavityOccupancy(String, int, int) Get cavity occupancy
setGasHydrateOnlyMode(boolean) Enable gas-hydrate mode
isGasHydrateOnlyMode() Check if mode enabled

Usage Examples

Complete Workflow Example

import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.thermo.phase.PhaseType;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class HydrateAnalysis {
    public static void main(String[] args) {
        // 1. Create production fluid
        SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 5.0, 100.0);
        fluid.addComponent("methane", 0.80);
        fluid.addComponent("ethane", 0.06);
        fluid.addComponent("propane", 0.04);
        fluid.addComponent("CO2", 0.02);
        fluid.addComponent("water", 0.08);
        fluid.setMixingRule(10);
        fluid.setHydrateCheck(true);

        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);

        // 2. Calculate hydrate formation temperature
        ops.hydrateFormationTemperature();
        double Thyd = fluid.getTemperature("C");
        System.out.println("Hydrate formation temperature: " + Thyd + " °C");

        // 3. At 5°C, check if hydrate exists
        fluid.setTemperature(273.15 + 5.0);
        ops.hydrateTPflash();

        if (fluid.hasHydratePhase()) {
            System.out.println("Hydrate forms at 5°C, 100 bar");

            // Get phase fractions
            for (int i = 0; i < fluid.getNumberOfPhases(); i++) {
                System.out.printf("Phase %d (%s): %.4f mol%%\n", 
                    i, fluid.getPhase(i).getType(), fluid.getBeta(i) * 100);
            }
        }

        // 4. Calculate MEG needed to prevent hydrate
        fluid.addComponent("MEG", 0.05);  // Add MEG
        fluid.createDatabase(true);
        ops.hydrateInhibitorConcentration("MEG", 273.15 + 5.0);
        double megWt = fluid.getPhase("aqueous").getComponent("MEG").getwtfrac();
        System.out.println("Required MEG in aqueous phase: " + megWt * 100 + " wt%");
    }
}

Process Integration Example

import neqsim.process.equipment.stream.Stream;
import neqsim.process.measurementdevice.HydrateEquilibriumTemperatureAnalyser;

// Create stream with hydrate checking
SystemInterface gas = new SystemSrkCPAstatoil(280.0, 100.0);
gas.addComponent("methane", 0.9);
gas.addComponent("water", 0.1);
gas.setMixingRule(10);
gas.setHydrateCheck(true);

Stream gasStream = new Stream("Gas Feed", gas);
gasStream.setFlowRate(100.0, "kg/hr");
gasStream.run();

// Add hydrate analyser
HydrateEquilibriumTemperatureAnalyser hydrateAnalyser = 
    new HydrateEquilibriumTemperatureAnalyser("Hydrate Monitor", gasStream);
hydrateAnalyser.run();

double hydrateT = hydrateAnalyser.getMeasuredValue("C");
System.out.println("Hydrate equilibrium temperature: " + hydrateT + " °C");

Best Practices

1. Always Set Mixing Rule

For CPA-based hydrate calculations, use mixing rule 10:

fluid.setMixingRule(10);

2. Enable Hydrate Check

Before hydrate calculations:

fluid.setHydrateCheck(true);

3. Verify Mass Conservation

After hydrate flash, verify beta sum equals 1.0:

double betaSum = 0.0;
for (int i = 0; i < fluid.getNumberOfPhases(); i++) {
    betaSum += fluid.getBeta(i);
}
assert Math.abs(betaSum - 1.0) < 1e-6 : "Mass conservation violated";

4. Check Phase Types

Always verify expected phases exist:

boolean hasHydrate = fluid.hasHydratePhase();
boolean hasAqueous = false;
for (int i = 0; i < fluid.getNumberOfPhases(); i++) {
    if (fluid.getPhase(i).getType() == PhaseType.AQUEOUS) {
        hasAqueous = true;
    }
}

5. Use Appropriate Flash Method

Scenario Method
Normal water content (> 1%) hydrateTPflash()
Trace water (< 1%) gasHydrateTPflash()
Find formation T hydrateFormationTemperature()
Find formation P hydrateFormationPressure()

Troubleshooting

Hydrate Not Forming When Expected

  1. Check if hydrate check is enabled: fluid.setHydrateCheck(true)
  2. Verify conditions are below hydrate curve
  3. Ensure water component is present
  4. Check mixing rule is set correctly

Convergence Issues

  1. Provide good initial temperature guess
  2. Reduce step size for near-critical conditions
  3. Check component fugacity calculations

Unexpected Phase Fractions

  1. Verify input composition sums to 1.0
  2. Check for negative mole numbers
  3. Use prettyPrint() to inspect phase compositions

No Aqueous Phase with Low Water

This is expected behavior. Use gasHydrateTPflash() for systems with trace water to achieve gas-hydrate equilibrium directly.


Chapter 11: Process Fundamentals

Process Overview

Process Simulation Package

The process package provides process equipment, unit operations, controllers, and process system management for building complete flowsheets.

Table of Contents


Overview

Location: neqsim.process

Purpose:


Documentation Structure

This documentation is organized into the following sections:

Section Description
equipment/ Equipment documentation (separators, compressors, etc.)
processmodel/ ProcessSystem and flowsheet management
safety/ Safety systems (PSV, ESD, blowdown)
controllers.md Process controllers and logic

Process Design Guide

Document Description
process_design_guide.md Complete guide to process design workflow using NeqSim

Mechanical Design Documentation

Document Description
mechanical_design_standards.md Design standards (NORSOK, ASME, API, DNV, etc.)
mechanical_design_database.md Data sources, database schemas, and CSV configuration
torg_integration.md Technical Requirements Documents (TORG) integration
field_development_orchestration.md Complete design workflow orchestration

Equipment Categories

Category Documentation Classes
Streams streams.md Stream, EnergyStream, VirtualStream
Separators separators.md Separator, ThreePhaseSeparator, GasScrubber
Heat Exchangers heat_exchangers.md Heater, Cooler, HeatExchanger
Compressors compressors.md Compressor, CompressorChart
Pumps pumps.md Pump, PumpChart
Expanders expanders.md Expander, TurboExpanderCompressor
Valves valves.md ThrottlingValve, SafetyValve, BlowdownValve
Distillation distillation.md DistillationColumn, SimpleTray
Absorbers absorbers.md SimpleAbsorber, SimpleTEGAbsorber
Ejectors ejectors.md Ejector
Membranes membranes.md MembraneSeparator
Flares flares.md Flare, FlareStack
Electrolyzers electrolyzers.md Electrolyzer, CO2Electrolyzer
Filters filters.md Filter, CharCoalFilter
Reactors reactors.md GibbsReactor
Pipelines pipelines.md Pipeline, AdiabaticPipe
Tanks tanks.md Tank, VesselDepressurization
Wells wells.md Well equipment
Mixers/Splitters mixers_splitters.md Mixer, Splitter
Utility util/ Adjuster, Recycle, Calculator

Package Structure

process/
├── SimulationBaseClass.java         # Base class for simulations
├── SimulationInterface.java         # Simulation interface
│
├── equipment/                        # Process equipment
│   ├── ProcessEquipmentBaseClass.java
│   ├── ProcessEquipmentInterface.java
│   ├── TwoPortEquipment.java         # Equipment with inlet/outlet
│   ├── EquipmentFactory.java         # Factory for creating equipment
│   │
│   ├── stream/                       # Streams
│   │   ├── Stream.java
│   │   ├── StreamInterface.java
│   │   ├── EnergyStream.java
│   │   └── VirtualStream.java
│   │
│   ├── separator/                    # Separators
│   │   ├── Separator.java
│   │   ├── ThreePhaseSeparator.java
│   │   ├── GasScrubber.java
│   │   └── SeparatorInterface.java
│   │
│   ├── heatexchanger/               # Heat transfer
│   │   ├── Heater.java
│   │   ├── Cooler.java
│   │   ├── HeatExchanger.java
│   │   ├── NeqHeater.java
│   │   └── Condenser.java
│   │
│   ├── compressor/                  # Compression
│   │   ├── Compressor.java
│   │   ├── CompressorInterface.java
│   │   └── CompressorChartInterface.java
│   │
│   ├── pump/                        # Pumps
│   │   ├── Pump.java
│   │   └── PumpInterface.java
│   │
│   ├── expander/                    # Expanders
│   │   ├── Expander.java
│   │   └── ExpanderInterface.java
│   │
│   ├── valve/                       # Valves
│   │   ├── ThrottlingValve.java
│   │   ├── ValveInterface.java
│   │   └── SafetyValve.java
│   │
│   ├── mixer/                       # Mixers
│   │   ├── Mixer.java
│   │   ├── StaticMixer.java
│   │   └── MixerInterface.java
│   │
│   ├── splitter/                    # Splitters
│   │   ├── Splitter.java
│   │   └── SplitterInterface.java
│   │
│   ├── distillation/                # Distillation
│   │   ├── DistillationColumn.java
│   │   ├── SimpleTray.java
│   │   ├── Condenser.java
│   │   └── Reboiler.java
│   │
│   ├── reactor/                     # Reactors
│   │   ├── Reactor.java
│   │   └── PFReactor.java
│   │
│   ├── absorber/                    # Absorption
│   │   ├── Absorber.java
│   │   └── SimpleTEGAbsorber.java
│   │
│   ├── pipeline/                    # Pipelines
│   │   ├── Pipeline.java
│   │   └── PipelineInterface.java
│   │
│   ├── well/                        # Wells
│   │   ├── SimpleWell.java
│   │   └── WellFlow.java
│   │
│   ├── tank/                        # Tanks and vessels
│   │   ├── Tank.java
│   │   └── ProcessVessel.java
│   │
│   ├── filter/                      # Filters
│   │   └── Filter.java
│   │
│   ├── membrane/                    # Membranes
│   │   └── Membrane.java
│   │
│   ├── ejector/                     # Ejectors
│   │   └── Ejector.java
│   │
│   ├── electrolyzer/                # Electrolyzers
│   │   └── PEM_Electrolyzer.java
│   │
│   └── util/                        # Utility equipment
│       ├── Adjuster.java
│       ├── Recycle.java
│       ├── Calculator.java
│       ├── Setter.java
│       └── MoleFractionSetter.java
│
├── processmodel/                    # Process system
│   ├── ProcessSystem.java
│   ├── ProcessModule.java
│   └── graph/                       # Graph-based execution
│       ├── ProcessGraph.java
│       └── ProcessGraphBuilder.java
│
├── controllerdevice/                # Controllers
│   ├── ControllerDevice.java
│   └── PIDController.java
│
├── measurementdevice/               # Measurements
│   ├── MeasurementDevice.java
│   ├── TemperatureMeasurement.java
│   ├── PressureMeasurement.java
│   └── FlowMeasurement.java
│
├── logic/                           # Process logic
│   ├── ProcessLogicController.java
│   └── ConditionalLogic.java
│
├── alarm/                           # Alarm system
│   └── ProcessAlarmManager.java
│
├── safety/                          # Safety systems
│   ├── PSV/
│   ├── ESD/
│   └── Blowdown/
│
├── calibration/                     # Equipment calibration
├── conditionmonitor/                # Condition monitoring
├── costestimation/                  # Cost estimation
├── mechanicaldesign/                # Mechanical design calculations
│   ├── separator/                   # Separator vessel design
│   ├── compressor/                  # Compressor design (API 617)
│   ├── valve/                       # Valve body, sizing, actuator
│   └── designstandards/             # ASME, API, IEC standards
├── mpc/                             # Model predictive control
├── ml/                              # Machine learning
└── streaming/                       # Data streaming

Mechanical Design Documentation

Equipment Documentation Standards
Separators See SeparatorMechanicalDesign ASME, BS 5500
Compressors CompressorMechanicalDesign.md API 617, API 672
Valves ValveMechanicalDesign.md IEC 60534, ANSI/ISA-75, ASME B16.34

ProcessSystem

The ProcessSystem class is the container for building and running process flowsheets.

Basic Usage

import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.equipment.valve.ThrottlingValve;

// Create process system
ProcessSystem process = new ProcessSystem("Gas Processing Plant");

// Create feed stream
SystemInterface feed = new SystemSrkEos(300.0, 80.0);
feed.addComponent("methane", 0.85);
feed.addComponent("ethane", 0.08);
feed.addComponent("propane", 0.05);
feed.addComponent("n-butane", 0.02);
feed.setMixingRule("classic");

Stream feedStream = new Stream("Feed", feed);
feedStream.setFlowRate(1000.0, "kg/hr");

// Add equipment to process
process.add(feedStream);

// Letdown valve
ThrottlingValve valve = new ThrottlingValve("Inlet Valve", feedStream);
valve.setOutletPressure(40.0, "bara");
process.add(valve);

// Separator
Separator separator = new Separator("HP Separator", valve.getOutletStream());
process.add(separator);

// Run process (recommended - auto-optimized)
process.runOptimized();

// Get results
System.out.println("Separator gas rate: " + 
    separator.getGasOutStream().getFlowRate("kg/hr") + " kg/hr");
System.out.println("Separator liquid rate: " + 
    separator.getLiquidOutStream().getFlowRate("kg/hr") + " kg/hr");

Execution Strategies

NeqSim provides multiple execution strategies for optimal performance:

Method Best For Speedup
run() General use baseline
runOptimized() Recommended 28-40%
runParallel() Feed-forward (no recycles) 40-57%
runHybrid() Complex recycle processes 38%
// Recommended - auto-selects best strategy
process.runOptimized();

// Or use specific strategies:
process.run();           // Sequential (default)
process.runParallel();   // Parallel (feed-forward only)
process.runHybrid();     // Hybrid (parallel + iterative)

Analyze Process Topology

// Check for recycles
boolean hasRecycles = process.hasRecycleLoops();

// Get detailed execution analysis
System.out.println(process.getExecutionPartitionInfo());

Key ProcessSystem Methods

Method Description
add(equipment) Add equipment to process
run() Run sequential simulation
runOptimized() Run with auto-optimized strategy
runParallel() Run with parallel execution
runHybrid() Run with hybrid execution
runTransient(time, dt) Run transient simulation
getUnit(name) Get equipment by name
hasRecycleLoops() Check for recycle loops
getExecutionPartitionInfo() Get execution analysis
copy() Clone the process system
getReport() Get process report
display() Display process summary

Equipment Categories

Streams

// Material stream
Stream gas = new Stream("Natural Gas", fluid);
gas.setFlowRate(5000.0, "Sm3/hr");
gas.setTemperature(25.0, "C");
gas.setPressure(100.0, "bara");
gas.run();

// Energy stream
EnergyStream heat = new EnergyStream("Heating Duty");
heat.setEnergyFlow(1000.0, "kW");

Separators

// Two-phase separator
Separator sep2p = new Separator("V-100", inletStream);
sep2p.run();
Stream gas = sep2p.getGasOutStream();
Stream liquid = sep2p.getLiquidOutStream();

// Three-phase separator
ThreePhaseSeparator sep3p = new ThreePhaseSeparator("V-200", inletStream);
sep3p.run();
Stream gas = sep3p.getGasOutStream();
Stream oil = sep3p.getOilOutStream();
Stream water = sep3p.getWaterOutStream();

Heat Exchangers

// Heater (duty specified)
Heater heater = new Heater("E-100", inletStream);
heater.setOutTemperature(80.0, "C");
heater.run();
System.out.println("Duty: " + heater.getDuty() + " W");

// Cooler
Cooler cooler = new Cooler("E-200", inletStream);
cooler.setOutTemperature(30.0, "C");
cooler.run();

// Shell-tube heat exchanger
HeatExchanger hx = new HeatExchanger("E-300", hotStream, coldStream);
hx.setUAvalue(5000.0);  // W/K
hx.run();

Compressors

// Compressor with polytropic efficiency
Compressor comp = new Compressor("K-100", inletStream);
comp.setOutletPressure(80.0, "bara");
comp.setPolytropicEfficiency(0.75);
comp.setUsePolytropicCalc(true);
comp.run();

System.out.println("Power: " + comp.getPower("kW") + " kW");
System.out.println("Outlet T: " + comp.getOutletStream().getTemperature("C") + " °C");

Valves

// Throttling valve (Joule-Thomson)
ThrottlingValve valve = new ThrottlingValve("FV-100", inletStream);
valve.setOutletPressure(50.0, "bara");
valve.run();

// Valve with Cv
valve.setCv(100.0, "US");
valve.setPercentValveOpening(50.0);

Distillation

// Simple distillation column
DistillationColumn column = new DistillationColumn("T-100", 10, true, true);
column.addFeedStream(feedStream, 5);
column.setCondenserTemperature(40.0, "C");
column.setReboilerTemperature(120.0, "C");
column.run();

Stream overhead = column.getGasOutStream();
Stream bottoms = column.getLiquidOutStream();

Controllers and Logic

Adjusters

Adjust a parameter to meet a specification.

// Adjust heater duty to achieve target temperature
Adjuster tempAdjuster = new Adjuster("TC-100");
tempAdjuster.setAdjustedVariable(heater, "duty");
tempAdjuster.setTargetVariable(heater.getOutletStream(), "temperature", 80.0, "C");
process.add(tempAdjuster);

Recycles

Handle recycle loops in the process.

Recycle recycle = new Recycle("Recycle");
recycle.addStream(recycleStream);
recycle.setOutletStream(recycleInletStream);
recycle.setTolerance(1e-6);
process.add(recycle);

Calculators

Perform custom calculations.

Calculator calc = new Calculator("MW Calculator");
calc.addInputVariable(stream);
calc.setOutputVariable(heater, "duty");
calc.setExpression("molarMass * 1000");
process.add(calc);

Safety Systems

Pressure Safety Valves

SafetyValve psv = new SafetyValve("PSV-100", vessel);
psv.setSetPressure(120.0, "bara");
psv.setBlowdownPressure(0.1);  // 10% blowdown
process.add(psv);

Blowdown Systems

See Safety Simulation Roadmap for detailed safety system documentation.


Dynamic Simulation

// Run transient simulation
double simulationTime = 3600.0;  // 1 hour
double timeStep = 1.0;           // 1 second

process.setTimeStep(timeStep);
for (double t = 0; t < simulationTime; t += timeStep) {
    process.runTransient();

    // Log data
    System.out.println(t + ", " + 
        separator.getPressure() + ", " +
        separator.getGasOutStream().getFlowRate("kg/hr"));
}

Process Reports

// Get JSON report
String jsonReport = process.getReport_json();

// Get tabular report
String[][] table = process.getUnitOperationsAsTable();

// Display to console
process.display();

Best Practices

  1. Use unique names for all equipment
  2. Set flow rate and conditions before running
  3. Add equipment in flow order for clarity
  4. Use Recycle for recycle loops
  5. Check mass balance after simulation
  6. Clone streams before branching to avoid shared state

Future Infrastructure

NeqSim includes foundational infrastructure to support the future of process simulation:

Capability Documentation Description
Lifecycle Management lifecycle/ Model versioning, state export/import, lifecycle tracking
Emissions Tracking sustainability/ CO2e accounting, regulatory reporting
Advisory Systems advisory/ Look-ahead predictions with uncertainty
ML Integration ml/ Surrogate models, physics constraint validation
Safety Scenarios safety/scenario-generation.md Automatic failure scenario generation
Batch Studies optimization/batch-studies.md Parallel parameter studies

See Future Infrastructure Overview for complete documentation.


Process Guide

Introduction to Process Simulation in NeqSim

NeqSim provides a powerful framework for modeling chemical and petroleum process plants. By connecting unit operations (separators, compressors, heat exchangers, valves) with streams, you can build complete process flowsheets for steady-state and dynamic simulation.

Table of Contents


Why NeqSim for Process Simulation?

NeqSim combines rigorous thermodynamic calculations with flexible process modeling:

Feature Description
Rigorous Thermodynamics Equations of state (SRK, PR, CPA, GERG-2008) with accurate phase equilibria
Comprehensive Equipment 50+ unit operation types including specialized oil & gas equipment
Dynamic Simulation Time-stepping for transient analysis, blowdown, and startup/shutdown
Control Integration PID controllers, adjusters, and recycle solvers built-in
Safety Systems PSV, ESD, HIPPS modeling for hazard analysis
Extensibility Java API allows custom equipment and integration with external systems

Core Architecture

Key Classes

Class Purpose
ProcessSystem Container for all equipment; manages execution order and convergence
Stream Fluid flow with thermodynamic state (T, P, composition, flow rate)
ProcessEquipmentInterface Base interface for all unit operations
Recycle Handles iterative convergence for recycle loops
Adjuster Adjusts variables to meet specifications
Calculator Custom calculations with lambda expressions

Equipment Hierarchy

ProcessEquipmentBaseClass
├── TwoPortEquipment (single inlet/outlet)
│   ├── ThrottlingValve
│   ├── Compressor
│   ├── Pump
│   ├── Heater / Cooler
│   └── AdiabaticPipe
├── Separator (multiple outlets)
│   ├── ThreePhaseSeparator
│   └── GasScrubber
├── Mixer / Splitter
├── DistillationColumn
└── Specialized Equipment
    ├── Ejector
    ├── MembraneSeparator
    └── Electrolyzer

Execution Flow

1. Define fluid (thermodynamic system)
2. Create feed stream(s)
3. Instantiate equipment and connect streams
4. Add all units to ProcessSystem
5. Run simulation (process.run())
6. Retrieve results from streams/equipment

Quick Start Example

A minimal working example - gas separation at reduced pressure:

import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.valve.ThrottlingValve;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;

// 1. Define fluid
SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.90);
fluid.addComponent("ethane", 0.05);
fluid.addComponent("propane", 0.03);
fluid.addComponent("n-heptane", 0.02);
fluid.setMixingRule("classic");

// 2. Create feed stream
Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(1000.0, "kg/hr");
feed.setTemperature(30.0, "C");
feed.setPressure(50.0, "bara");

// 3. Add equipment
ThrottlingValve valve = new ThrottlingValve("JT Valve", feed);
valve.setOutletPressure(10.0, "bara");

Separator separator = new Separator("HP Separator", valve.getOutletStream());

// 4. Build process system
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(valve);
process.add(separator);

// 5. Run simulation
process.run();

// 6. Get results
System.out.println("Gas rate: " + separator.getGasOutStream().getFlowRate("kg/hr") + " kg/hr");
System.out.println("Liquid rate: " + separator.getLiquidOutStream().getFlowRate("kg/hr") + " kg/hr");
System.out.println("Separator temp: " + separator.getTemperature("C") + " °C");

Step-by-Step Guide

1. Define the Fluid

Create a thermodynamic system using an equation of state:

// SRK equation of state
SystemInterface fluid = new SystemSrkEos(298.15, 50.0); // T(K), P(bar)
fluid.addComponent("methane", 90.0);  // mole fraction or moles
fluid.addComponent("ethane", 5.0);
fluid.addComponent("propane", 3.0);
fluid.addComponent("n-heptane", 2.0);
fluid.setMixingRule("classic");

// Or use CPA for polar components (water, MEG, methanol)
SystemInterface cpaFluid = new SystemSrkCPAstatoil(298.15, 50.0);
cpaFluid.addComponent("methane", 0.9);
cpaFluid.addComponent("water", 0.05);
cpaFluid.addComponent("MEG", 0.05);
cpaFluid.setMixingRule(10);  // CPA mixing rule

2. Create the Feed Stream

Stream feedStream = new Stream("Feed Stream", fluid);
feedStream.setFlowRate(1000.0, "kg/hr");    // or "kmol/hr", "MSm3/day"
feedStream.setTemperature(30.0, "C");        // or "K"
feedStream.setPressure(50.0, "bara");        // or "barg", "Pa"

3. Connect Equipment

Most equipment takes an inlet stream in the constructor:

// Valve - reduces pressure
ThrottlingValve valve = new ThrottlingValve("Inlet Valve", feedStream);
valve.setOutletPressure(30.0, "bara");

// Separator - splits phases
Separator separator = new Separator("Test Separator", valve.getOutletStream());

// Compressor - increases pressure
Compressor compressor = new Compressor("Export Compressor", separator.getGasOutStream());
compressor.setOutletPressure(100.0, "bara");
compressor.setIsentropicEfficiency(0.75);

// Heater - adds heat
Heater heater = new Heater("Gas Heater", compressor.getOutletStream());
heater.setOutTemperature(50.0, "C");

4. Build the Process System

ProcessSystem process = new ProcessSystem();
process.add(feedStream);
process.add(valve);
process.add(separator);
process.add(compressor);
process.add(heater);

Note: Equipment names must be unique within a ProcessSystem.

5. Run the Simulation

// Steady-state run
process.run();

// For debugging, run with UUID tracking
UUID id = UUID.randomUUID();
process.run(id);

6. Retrieve Results

// Stream properties
double gasRate = separator.getGasOutStream().getFlowRate("kg/hr");
double gasTemp = separator.getGasOutStream().getTemperature("C");
double gasPressure = separator.getGasOutStream().getPressure("bara");

// Equipment performance
double compressorPower = compressor.getPower("kW");
double compressorHead = compressor.getPolytropicHead("kJ/kg");

// Composition
double methaneInGas = separator.getGasOutStream()
    .getFluid().getComponent("methane").getMoleFraction();

Process Modules

Process modules are pre-configured collections of unit operations designed to perform standard processing tasks. They encapsulate complex logic for reuse.

Built-in Modules

Module Purpose
GlycolDehydrationlModule TEG/MEG dehydration systems
SeparationTrainModule Multi-stage separation
CompressionModule Multi-stage compression with intercooling

Using a Standard Module

import neqsim.process.processmodel.processmodules.GlycolDehydrationlModule;

// Initialize the module
GlycolDehydrationlModule tegModule = new GlycolDehydrationlModule("TEG Plant");
tegModule.addInputStream("GasFeed", separator.getGasOutStream());
tegModule.addInputStream("TEGFeed", tegFeedStream);

// Configure module parameters
tegModule.setSpecification("water content", 50.0); // ppm target

// Add to process system
process.add(tegModule);
process.run();

Creating Custom Modules

Extend ProcessModuleBaseClass to create reusable process blocks:

public class MyCustomModule extends ProcessModuleBaseClass {
    public MyCustomModule(String name) {
        super(name);
    }

    @Override
    public void initializeModule() {
        // Define internal units and connections
        Separator sep = new Separator("Internal Sep", getInputStream("feed"));
        Compressor comp = new Compressor("Internal Comp", sep.getGasOutStream());

        getOperations().add(sep);
        getOperations().add(comp);

        // Set output streams
        setOutputStream("gas", comp.getOutletStream());
        setOutputStream("liquid", sep.getLiquidOutStream());
    }
}

Advanced Features

Recycle Loops

Handle closed loops with the Recycle class:

Recycle recycle = new Recycle("Recycle Controller");
recycle.addStream(separator.getLiquidOutStream());
recycle.setTolerance(1e-6);
recycle.setMaximumIterations(50);
process.add(recycle);

See Advanced Process Simulation for complete examples.

Controllers (PID)

Automate equipment operation with PID controllers:

ControllerDeviceBaseClass flowController = new ControllerDeviceBaseClass();
flowController.setTransmitter(flowTransmitter);
flowController.setControllerSetPoint(50.0);
flowController.setControllerParameters(0.5, 100.0, 0.0); // Kp, Ti, Td
valve.setController(flowController);

Adjusters

Adjust a variable to meet a target specification:

Adjuster adj = new Adjuster("Pressure Adjuster");
adj.setAdjustedVariable(valve, "opening");
adj.setTargetVariable(separator, "pressure", 30.0, "bara");
process.add(adj);

Dynamic Simulation

Run time-stepping simulations for transient analysis:

process.setTimeStep(0.1);  // seconds
for (int i = 0; i < 1000; i++) {
    process.runTransient();
    // Log or record results
}

Functional Interfaces

Use lambda expressions for flexible calculations:

// Calculator with lambda
Calculator calc = new Calculator("Energy Balance");
calc.addInputVariable(inlet);
calc.setOutputVariable(outlet);
calc.setCalculationMethod((inputs, output) -> {
    double totalEnthalpy = inputs.stream()
        .mapToDouble(e -> ((Stream)e).getThermoSystem().getEnthalpy())
        .sum();
    // Apply to output...
});

// Adjuster with lambda
adjuster.setTargetValueCalculator(equipment -> {
    return ((Stream) equipment).getFlowRate("kg/hr") * 0.1;
});

See Logical Unit Operations for complete functional interface documentation.


Next Steps

Topic Documentation
Advanced Topics Advanced Process Simulation
Control Logic Logical Unit Operations
Equipment Details Process Equipment
Dynamic Simulation Process Transient Guide
Safety Systems Integrated Safety Systems
Bottleneck Analysis Bottleneck Analysis
Examples Usage Examples

For equipment-specific documentation, see the equipment documentation.

Advanced Process

Advanced Process Simulation

This guide covers advanced features of NeqSim's process simulation capabilities, including execution optimization, recycles, control systems, and dynamic simulation.

1. Execution Optimization

NeqSim provides multiple execution strategies to optimize simulation performance. The recommended approach is to use runOptimized() which automatically analyzes your process and selects the best strategy.

Execution Strategies

Method Best For Typical Speedup
run() Simple processes baseline
runOptimized() All processes (recommended) 28-40%
runParallel() Feed-forward (no recycles) 40-57%
runHybrid() Complex recycle processes 38%

Quick Start

// Recommended - auto-selects best strategy
process.runOptimized();

How runOptimized() Works

The method analyzes your process topology and selects the appropriate strategy:

  1. No recycles detected → Uses runParallel() for maximum speed
  2. Recycles detected → Uses runHybrid() which:
    • Phase 1: Runs feed-forward units in parallel
    • Phase 2: Iterates on recycle section until convergence

Analyzing Process Topology

// Check if process has recycles
boolean hasRecycles = process.hasRecycleLoops();

// Get detailed execution partition analysis
System.out.println(process.getExecutionPartitionInfo());

Example output:

=== Execution Partition Analysis ===
Total units: 40
Has recycle loops: true
Parallel levels: 29
Max parallelism: 6
Units in recycle loops: 30

=== Hybrid Execution Strategy ===
Phase 1 (Parallel): 4 levels, 8 units
Phase 2 (Iterative): 25 levels, 32 units

Execution levels:
  Level 0 [PARALLEL]: feed TP setter, first stage oil reflux
  Level 1 [PARALLEL]: 1st stage separator
  --- Recycle Section Start (iterative) ---
  Level 4: oil heater second stage [RECYCLE]
  ...

Graph-Based Execution

For complex processes, graph-based execution optimizes unit ordering:

// Enable graph-based execution
process.setUseGraphBasedExecution(true);
process.run();

// Or use runOptimized() which handles this automatically
process.runOptimized();

Asynchronous Execution

Run simulations in background threads:

// Run in background
Future<?> task = process.runAsTask();

// Do other work...

// Wait for completion
task.get();

2. Recycles

In process simulation, a recycle loop occurs when a downstream stream is fed back to an upstream unit. NeqSim handles this using the Recycle class, which iterates until the properties of the recycled stream converge.

How it Works

The Recycle unit operation compares the properties (flow rate, composition, temperature, pressure) of the stream from the previous iteration with the current iteration. If the difference is within a specified tolerance, the loop is considered converged.

Example Usage

import neqsim.process.equipment.util.Recycle;
import neqsim.process.equipment.mixer.StaticMixer;
// ... other imports

// 1. Create Feed and Recycle Streams
Stream feed = new Stream("Feed", fluid);
Stream recycleStream = new Stream("Recycle Stream", fluid.clone()); // Initial guess

// 2. Mix Feed and Recycle
StaticMixer mixer = new StaticMixer("Mixer");
mixer.addStream(feed);
mixer.addStream(recycleStream);

// ... Process units (e.g., Compressor, Cooler, Separator) ...
Separator separator = new Separator("Separator", cooler.getOutletStream());

// 3. Define the Recycle Unit
// The input to the Recycle unit is the stream you want to recycle (e.g., liquid from separator)
Recycle recycle = new Recycle("Recycle Controller");
recycle.addStream(separator.getLiquidOutStream());

// 4. Connect Recycle Output back to Mixer
// IMPORTANT: The output of the Recycle unit is what you connect to the upstream mixer
recycleStream = recycle.getOutletStream(); 
mixer.replaceStream(1, recycleStream); // Or set it up initially if possible

// 5. Add to ProcessSystem
process.add(feed);
process.add(mixer);
// ... add other units ...
process.add(recycle); // Add recycle last or where appropriate in sequence

// 6. Run
process.run();

Tuning

You can adjust the tolerance and maximum iterations:

recycle.setTolerance(1e-6);
recycle.setMaximumIterations(50);

3. Controllers (PID)

NeqSim allows you to add PID controllers to automate the operation of equipment, such as valves or compressors, to maintain a specific setpoint (e.g., pressure, flow, level).

Components

Example: Flow Control

import neqsim.process.controllerdevice.ControllerDeviceBaseClass;
import neqsim.process.measurementdevice.VolumeFlowTransmitter;
import neqsim.process.equipment.valve.ThrottlingValve;

// 1. Create the Stream and Valve
Stream stream = new Stream("Stream", fluid);
ThrottlingValve valve = new ThrottlingValve("Control Valve", stream);

// 2. Create a Transmitter
// Measures flow rate of the stream
VolumeFlowTransmitter flowTransmitter = new VolumeFlowTransmitter(stream);
flowTransmitter.setUnit("kg/hr");
flowTransmitter.setMaximumValue(100.0);
flowTransmitter.setMinimumValue(0.0);

// 3. Create the Controller
ControllerDeviceBaseClass flowController = new ControllerDeviceBaseClass();
flowController.setTransmitter(flowTransmitter);
flowController.setReverseActing(true); // Action depends on process physics
flowController.setControllerSetPoint(50.0); // Target Flow
flowController.setControllerParameters(0.5, 100.0, 0.0); // Kp, Ti, Td

// 4. Assign Controller to Valve
valve.setController(flowController);

// 5. Add to ProcessSystem
process.add(stream);
process.add(valve);
process.add(flowTransmitter); // Transmitter must be added to system

// 6. Run
process.run();

4. Dynamic Simulation

NeqSim supports dynamic (transient) simulation, allowing you to model how the process changes over time. This is useful for studying startup/shutdown, control system tuning, and buffer tank sizing.

Setup for Dynamics

  1. Enable Dynamic Calculation: Some units need explicit flags (e.g., separator.setCalculateSteadyState(false)).
  2. Geometry: Units like separators and tanks require physical dimensions (diameter, length) to calculate volume and levels.
  3. Time Step: Set the simulation time step.

Example Loop

// ... Setup system with valves, separators, controllers ...

// Configure unit for dynamics
separator.setCalculateSteadyState(false); // Enable dynamic level calculation
separator.setInternalDiameter(2.0);
separator.setSeparatorLength(5.0);

// Run steady state first to get initial condition
process.run();

// Dynamic Loop
double timeStep = 10.0; // seconds
process.setTimeStep(timeStep);

for (int i = 0; i < 100; i++) {
    process.runTransient();

    // Log results
    double time = i * timeStep;
    double level = separator.getLiquidLevel();
    double pressure = separator.getGasOutStream().getPressure();

    System.out.println("Time: " + time + " s, Level: " + level + ", Pressure: " + pressure);
}

Key Methods

5. Combining Process Systems (ProcessModel)

For large simulations, it is often better to split the plant into smaller, manageable ProcessSystem objects (e.g., "Inlet Separation", "Gas Compression", "Oil Stabilization") and then combine them into a single ProcessModel.

Benefits

Example

import neqsim.process.processmodel.ProcessModel;
import neqsim.process.processmodel.ProcessSystem;

// 1. Create Individual Process Systems
ProcessSystem inletSystem = new ProcessSystem();
inletSystem.setName("Inlet Section");
// ... add units to inletSystem ...

ProcessSystem compressionSystem = new ProcessSystem();
compressionSystem.setName("Compression Section");
// ... add units to compressionSystem ...

// 2. Connect Systems
// Typically, a stream from the first system is used as input to the second
Stream gasFromInlet = (Stream) inletSystem.getUnit("Inlet Separator").getGasOutStream();
Compressor compressor = new Compressor("1st Stage Compressor", gasFromInlet);
compressionSystem.add(compressor);

// 3. Create ProcessModel
ProcessModel plantModel = new ProcessModel();
plantModel.add("Inlet", inletSystem);
plantModel.add("Compression", compressionSystem);

// 4. Run the Full Model (uses optimized execution by default)
plantModel.run();

// Or disable optimized execution if needed
plantModel.setUseOptimizedExecution(false);
plantModel.run();

The ProcessModel will execute the added systems in the order they were added. By default, each ProcessSystem uses runOptimized() which auto-selects the best execution strategy (parallel for feed-forward, hybrid for recycle processes).

Analyzing ProcessModel Execution

// Get execution analysis for all ProcessSystems
System.out.println(plantModel.getExecutionPartitionInfo());

Example output:

=== ProcessModel Execution Analysis ===
Total ProcessSystems: 2
Optimized execution: enabled

--- ProcessSystem: Inlet ---
Units: 15
Has recycles: true
Strategy: Hybrid (parallel + iterative)

--- ProcessSystem: Compression ---
Units: 8
Has recycles: false
Strategy: Parallel

6. Reusable templates and composition helpers

To reduce boilerplate when assembling larger flowsheets, reuse the Recycle, controller, and ProcessModel patterns as pre-made building blocks. The following templates can be copied as-is or combined inside a ProcessModel catalog to let automated agents stitch together flowsheets without rewiring every unit manually.

Template: Inlet separator train with recycle

Building blocks: feed stream → choke valve (optional) → inlet cooler → three-phase separator → level/pressure controllers → recycle loop.

Why this helps: captures the standard inlet handling motif (cooling, phase split, level trim) while providing a ready-made recycle loop for gas reprocessing or compressor suction stabilization.

// Streams
Stream feed = new Stream("Feed", feedFluid);
Stream recycleStream = new Stream("Recycle Seed", feedFluid.clone());

// Front-end conditioning
ThrottlingValve choke = new ThrottlingValve("Choke", feed);
Cooler inletCooler = new Cooler("Inlet Cooler", choke.getOutletStream());
Separator inletSep = new Separator("Inlet Separator", inletCooler.getOutletStream());

// Controllers
LevelTransmitter levelTI = new LevelTransmitter(inletSep);
ControllerDeviceBaseClass levelController = new ControllerDeviceBaseClass();
levelController.setTransmitter(levelTI);
levelController.setControllerSetPoint(0.6); // 60% level
levelController.setReverseActing(true);
ThrottlingValve levelValve = new ThrottlingValve("Liquid LV", inletSep.getLiquidOutStream());
levelValve.setController(levelController);

PressureTransmitter pTI = new PressureTransmitter(inletSep);
ControllerDeviceBaseClass pressureController = new ControllerDeviceBaseClass();
pressureController.setTransmitter(pTI);
pressureController.setControllerSetPoint(50.0); // bara
pressureController.setReverseActing(false);
ThrottlingValve pressureValve = new ThrottlingValve("Gas PCV", inletSep.getGasOutStream());
pressureValve.setController(pressureController);

// Recycle
Recycle recycle = new Recycle("Separator Gas Recycle");
recycle.addStream(pressureValve.getOutletStream());
recycle.setTolerance(1e-6);
recycle.setMaximumIterations(50);

// Stitch recycle back to the front-end mixer (or directly to choke/cooler)
StaticMixer frontMixer = new StaticMixer("Front Mixer");
frontMixer.addStream(feed);
frontMixer.addStream(recycle.getOutletStream());
choke.setOutletStream(frontMixer.getOutStream());

// Register in a ProcessSystem
ProcessSystem inletSystem = new ProcessSystem();
inletSystem.add(feed, recycleStream, choke, inletCooler, inletSep, levelTI, levelValve, pTI, pressureValve, recycle, frontMixer);

Composition hints:

Template: Two-stage compression with interstage cooling

Building blocks: gas feed → stage 1 compressor → interstage cooler + separator (optional) → stage 2 compressor → aftercooler → pressure controller or recycle.

Why this helps: standardizes multi-stage compression including thermal conditioning between stages and hooks for surge/recycle control.

// Assume gasFeed is provided by an upstream template (e.g., inlet separator train)
Compressor comp1 = new Compressor("1st Stage", gasFeed);
Cooler intercooler = new Cooler("Interstage Cooler", comp1.getOutletStream());
Separator interSep = new Separator("Interstage Separator", intercooler.getOutletStream());

Compressor comp2 = new Compressor("2nd Stage", interSep.getGasOutStream());
Cooler afterCooler = new Cooler("Aftercooler", comp2.getOutletStream());

// Discharge pressure control via recycle
PressureTransmitter dischargePT = new PressureTransmitter(afterCooler);
ControllerDeviceBaseClass dischargePC = new ControllerDeviceBaseClass();
dischargePC.setTransmitter(dischargePT);
dischargePC.setControllerSetPoint(100.0); // bara
dischargePC.setReverseActing(false);

ThrottlingValve recycleValve = new ThrottlingValve("Discharge Recycle Valve", afterCooler.getOutletStream());
recycleValve.setController(dischargePC);

Recycle dischargeRecycle = new Recycle("Compression Recycle");
dischargeRecycle.addStream(recycleValve.getOutletStream());
dischargeRecycle.setTolerance(1e-7);
dischargeRecycle.setMaximumIterations(75);

// Tie recycle back to first-stage suction
StaticMixer suctionMixer = new StaticMixer("Suction Mixer");
suctionMixer.addStream(gasFeed);
suctionMixer.addStream(dischargeRecycle.getOutletStream());
comp1.setInletStream(suctionMixer.getOutStream());

// Organize as a ProcessSystem for catalog reuse
ProcessSystem compressionSystem = new ProcessSystem();
compressionSystem.add(comp1, intercooler, interSep, comp2, afterCooler, dischargePT, recycleValve, dischargeRecycle, suctionMixer);

Composition hints:

Logical Operations

Logical Unit Operations

NeqSim provides several "logical" unit operations that do not represent physical equipment but are used to control the simulation, transfer data, or perform calculations. These include Calculator, Adjuster, SetPoint, and Recycle.

Calculator

The Calculator unit operation allows for custom calculations and data manipulation within a process simulation. It is useful for calculating derived properties or implementing simple control logic. Custom lambdas are the preferred hook for AI-generated logic because they let you keep the same simulator graph while swapping in new behavior at runtime.

Usage

  1. Create: Calculator calc = new Calculator("name");
  2. Add Inputs: calc.addInputVariable(inputUnit);
  3. Set Output: calc.setOutputVariable(outputUnit);
  4. Define Logic: Use setCalculationMethod with a lambda expression.

Example

Calculator energyCalc = new Calculator("Energy Calc");
energyCalc.addInputVariable(inletStream);
energyCalc.setOutputVariable(outletStream);

energyCalc.setCalculationMethod((inputs, output) -> {
    Stream in = (Stream) inputs.get(0);
    Stream out = (Stream) output;
    double energy = in.LCV() * in.getFlowRate("Sm3/hr");
    // Adjust outlet temperature based on energy
    out.setTemperature(300.0 + energy / 1e5, "K");
});

Declarative presets (energy balance, dew point targeting)

For frequently reused logic you can rely on CalculatorLibrary presets instead of hand-written lambdas. This makes it easier to reference calculations declaratively (e.g., from an AI agent or configuration file):

Calculator presetCalc = new Calculator("dew point targeter");
presetCalc.addInputVariable(feedStream);
presetCalc.setOutputVariable(targetStream);

// Apply by enum or by name
presetCalc.setCalculationMethod(CalculatorLibrary.preset(CalculatorLibrary.Preset.DEW_POINT_TARGETING));
// presetCalc.setCalculationMethod(CalculatorLibrary.byName("dewPointTargeting"));

Available presets:

Adjuster

The Adjuster is used to vary a parameter in one unit operation (the "adjusted variable") to achieve a specific value in another unit operation (the "target variable"). It is essentially a single-variable solver. Use lambdas for the getters/setters to keep the hook flexible for AI-generated control logic.

Standard Usage

You can specify standard properties like "pressure", "temperature", "flow", etc.

Adjuster adjuster = new Adjuster("Pressure Adjuster");
adjuster.setAdjustedVariable(inletStream, "flow", "kg/hr");
adjuster.setTargetVariable(outletStream, "pressure", 50.0, "bara");

Custom Target Calculation

You can also define a custom function to calculate the target value from the target equipment. This is useful if the variable you want to control is not a standard property.

adjuster.setTargetValueCalculator((equipment) -> {
    Stream s = (Stream) equipment;
    // Control based on a custom metric, e.g., Flow * Temperature
    return s.getFlowRate("kg/hr") * s.getTemperature("K");
});

Custom Adjusted Variable

You can also define custom logic for how to read and write the adjusted variable. This allows you to manipulate parameters that are not standard properties.

// Define how to read the current value of the adjusted variable
adjuster.setAdjustedValueGetter((equipment) -> {
    return ((Stream) equipment).getTemperature("K");
});

// Define how to set the new value of the adjusted variable
adjuster.setAdjustedValueSetter((equipment, val) -> {
    ((Stream) equipment).setTemperature(val, "K");
});

SetPoint

The SetPoint unit operation sets the value of a variable in a target unit operation equal to the value of a variable in a source unit operation. It is used for feed-forward control or copying values between equipment.

Standard Usage

SetPoint setPoint = new SetPoint("Pressure Copy");
setPoint.setSourceVariable(sourceStream, "pressure");
setPoint.setTargetVariable(targetStream, "pressure");

Supported Target Variables

Equipment Type Supported Variables
Stream pressure, temperature
ThrottlingValve pressure (outlet)
Compressor pressure (outlet)
Pump pressure (outlet)
Heater/Cooler pressure, temperature

Functional Interface Mode

Use setSourceValueCalculator to define a custom function that calculates the value to set on the target equipment. This provides full flexibility for non-linear relationships, unit conversions, or conditional logic.

Method Signature

Method Type Description
setSourceValueCalculator Function<ProcessEquipmentInterface, Double> Custom function to compute the value to set

Basic Example

SetPoint setPoint = new SetPoint("Custom SetPoint");
setPoint.setSourceVariable(sourceStream);
setPoint.setTargetVariable(targetStream, "pressure");

// Set target pressure based on source temperature: P = T / 10.0
setPoint.setSourceValueCalculator((equipment) -> {
    Stream s = (Stream) equipment;
    return s.getTemperature("K") / 10.0;
});

setPoint.run();
// Target pressure is now 30.0 bara (if source temp = 300 K)

Percentage Scaling Example

setPoint.setSourceValueCalculator((equipment) -> {
    Stream s = (Stream) equipment;
    // Set target pressure to be 10% of source pressure
    return s.getPressure("bara") * 0.1;
});

Computed Ratio Example

// Set compressor outlet pressure based on inlet conditions
SetPoint pressureRatio = new SetPoint("Pressure Ratio Control");
pressureRatio.setSourceVariable(compressorInlet);
pressureRatio.setTargetVariable(compressor, "pressure");

pressureRatio.setSourceValueCalculator((equipment) -> {
    Stream inlet = (Stream) equipment;
    double inletP = inlet.getPressure("bara");
    double inletT = inlet.getTemperature("K");
    // Higher inlet temperature = lower pressure ratio
    double ratio = 4.0 - (inletT - 300.0) * 0.01;
    return inletP * Math.max(ratio, 2.0);
});

When to Use Functional Mode

Use Case Example
Non-linear relationships Pressure = f(temperature, flow)
Unit conversions Convert from source units to target units
Computed ratios Set valve to percentage of max flow
Conditional logic Different values based on operating mode
Multi-variable calculations Value depends on multiple stream properties

Recycle

The Recycle unit operation is used to close loops in a process simulation. It compares the inlet and outlet streams of the recycle block and iterates until they converge within a specified tolerance.

Recycle recycle = new Recycle("Recycle");
recycle.addStream(recycleStream);
recycle.setTolerance(1e-6);

Process Design

Process Design Guide for NeqSim

Introduction

This guide describes the complete process design workflow using NeqSim, from initial process simulation through mechanical design and final validation. NeqSim provides an integrated framework for:

Process Design Workflow Overview

┌─────────────────────────────────────────────────────────────────────────────────┐
│                        PROCESS DESIGN WORKFLOW                                   │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌────────────┐ │
│  │   1. DEFINE  │────▶│  2. PROCESS  │────▶│ 3. MECHANICAL│────▶│ 4. VALIDATE│ │
│  │    SYSTEM    │     │  SIMULATION  │     │    DESIGN    │     │  & REPORT  │ │
│  └──────────────┘     └──────────────┘     └──────────────┘     └────────────┘ │
│        │                    │                    │                    │         │
│        ▼                    ▼                    ▼                    ▼         │
│  ┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌────────────┐ │
│  │ • Fluid      │     │ • Run cases  │     │ • Apply      │     │ • Check    │ │
│  │   composition│     │ • Calculate  │     │   standards  │     │   compliance│ │
│  │ • Equipment  │     │   properties │     │ • Size       │     │ • Generate │ │
│  │ • Flowsheet  │     │ • Heat/mass  │     │   equipment  │     │   reports  │ │
│  │ • TORG       │     │   balance    │     │ • Materials  │     │            │ │
│  └──────────────┘     └──────────────┘     └──────────────┘     └────────────┘ │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

Step 1: Define the System

1.1 Create the Fluid System

Define the thermodynamic system with appropriate equation of state:

import neqsim.thermo.system.SystemSrkEos;

// Create fluid with SRK equation of state
SystemSrkEos fluid = new SystemSrkEos(280.0, 50.0);  // T(K), P(bar)
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.04);
fluid.addComponent("n-butane", 0.02);
fluid.addComponent("CO2", 0.01);
fluid.setMixingRule("classic");
fluid.createDatabase(true);

1.2 Build the Process Flowsheet

Create equipment and connect into a process system:

import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.equipment.compressor.Compressor;

ProcessSystem process = new ProcessSystem();

// Feed stream
Stream feed = new Stream("Well Feed", fluid);
feed.setFlowRate(50000.0, "kg/hr");
feed.setTemperature(60.0, "C");
feed.setPressure(50.0, "bara");
process.add(feed);

// HP Separator
Separator hpSeparator = new Separator("HP Separator", feed);
process.add(hpSeparator);

// Export Compressor
Compressor exportCompressor = new Compressor("Export Compressor", hpSeparator.getGasOutStream());
exportCompressor.setOutletPressure(150.0, "bara");
process.add(exportCompressor);

1.3 Load Project TORG

Load the Technical Requirements Document governing design standards:

import neqsim.process.mechanicaldesign.torg.TorgManager;
import neqsim.process.mechanicaldesign.torg.CsvTorgDataSource;

TorgManager torgManager = new TorgManager();
torgManager.addDataSource(new CsvTorgDataSource("project_torg.csv"));

Optional<TechnicalRequirementsDocument> optTorg = torgManager.load("TROLL-WEST-2025");

📖 See: TORG Integration for detailed TORG configuration


Step 2: Process Simulation

2.1 Run Base Case Simulation

// Run the process simulation
process.run();

// Access results
double gasRate = hpSeparator.getGasOutStream().getFlowRate("MSm3/day");
double liquidRate = hpSeparator.getLiquidOutStream().getFlowRate("m3/hr");
double compressorPower = exportCompressor.getPower("MW");

System.out.println("Gas rate: " + gasRate + " MSm3/day");
System.out.println("Liquid rate: " + liquidRate + " m3/hr");
System.out.println("Compressor power: " + compressorPower + " MW");

2.2 Run Multiple Design Cases

Evaluate different operating scenarios:

import neqsim.process.mechanicaldesign.designstandards.DesignCase;

// Define cases to evaluate
List<DesignCase> designCases = Arrays.asList(
    DesignCase.NORMAL,
    DesignCase.MAXIMUM,
    DesignCase.MINIMUM,
    DesignCase.UPSET
);

Map<DesignCase, Double> separatorPressures = new HashMap<>();

for (DesignCase designCase : designCases) {
    // Adjust feed based on case
    double loadFactor = designCase.getTypicalLoadFactor();
    feed.setFlowRate(50000.0 * loadFactor, "kg/hr");

    // Run simulation
    process.run();

    // Store results
    separatorPressures.put(designCase, hpSeparator.getPressure("bara"));
}

📖 See: Field Development Orchestration for design case details


Step 3: Mechanical Design

3.1 Apply Design Standards

Apply appropriate international standards to equipment:

import neqsim.process.mechanicaldesign.designstandards.StandardType;
import neqsim.process.mechanicaldesign.designstandards.StandardRegistry;

// Apply standards to individual equipment
StandardRegistry.applyStandardToEquipment(hpSeparator, StandardType.NORSOK_P002);
StandardRegistry.applyStandardToEquipment(exportCompressor, StandardType.API_617);

// Or apply TORG to entire system (applies all project standards)
if (optTorg.isPresent()) {
    torgManager.apply(optTorg.get(), process);
}

📖 See: Mechanical Design Standards for available standards

3.2 Run Mechanical Design Calculations

import neqsim.process.mechanicaldesign.MechanicalDesign;

// Get mechanical design for separator
MechanicalDesign sepDesign = hpSeparator.getMechanicalDesign();
sepDesign.calcDesign();

// Access design results
double designPressure = sepDesign.getDesignPressure();
double designTemperature = sepDesign.getDesignTemperature();
double wallThickness = sepDesign.getWallThickness();
double weight = sepDesign.getWeightTotal();
String materialGrade = sepDesign.getMaterialDesignStandard().getMaterialGrade();

System.out.println("Design Pressure: " + designPressure + " barg");
System.out.println("Design Temperature: " + designTemperature + " °C");
System.out.println("Wall Thickness: " + wallThickness + " mm");
System.out.println("Weight: " + weight + " kg");
System.out.println("Material: " + materialGrade);

3.3 Design All Equipment in System

// Calculate mechanical design for all equipment
for (ProcessEquipmentInterface equipment : process.getUnitOperations()) {
    MechanicalDesign mechDesign = equipment.getMechanicalDesign();
    if (mechDesign != null) {
        mechDesign.calcDesign();
    }
}

📖 See: Mechanical Design Database for data sources


Step 4: Validate and Report

4.1 Validate Design Compliance

import neqsim.process.mechanicaldesign.designstandards.DesignValidationResult;

DesignValidationResult validation = new DesignValidationResult();

// Check each equipment
for (ProcessEquipmentInterface equipment : process.getUnitOperations()) {
    MechanicalDesign design = equipment.getMechanicalDesign();
    if (design != null && design.hasDesignStandard()) {
        // Validate against TORG requirements
        if (optTorg.isPresent()) {
            TechnicalRequirementsDocument torg = optTorg.get();

            // Check corrosion allowance
            double requiredCA = torg.getSafetyFactors().getCorrosionAllowance();
            if (design.getCorrosionAllowance() < requiredCA) {
                validation.addWarning(equipment.getName() + 
                    ": Corrosion allowance below TORG requirement");
            }
        }
        validation.addInfo(equipment.getName() + " design validated");
    }
}

// Check results
if (validation.isValid()) {
    System.out.println("All equipment meets design requirements");
} else {
    System.out.println("Design issues found:");
    for (var msg : validation.getMessages()) {
        System.out.println("  " + msg.getSeverity() + ": " + msg.getMessage());
    }
}

4.2 Generate Design Report

StringBuilder report = new StringBuilder();
report.append("Process Design Report\n");
report.append("=====================\n\n");

// TORG Information
if (optTorg.isPresent()) {
    TechnicalRequirementsDocument torg = optTorg.get();
    report.append("Project: ").append(torg.getProjectName()).append("\n");
    report.append("TORG Revision: ").append(torg.getRevision()).append("\n");
    report.append("Design Life: ").append(torg.getDesignLifeYears()).append(" years\n\n");
}

// Equipment Summary
report.append("Equipment Summary\n");
report.append("-----------------\n");
for (ProcessEquipmentInterface equipment : process.getUnitOperations()) {
    MechanicalDesign design = equipment.getMechanicalDesign();
    if (design != null) {
        report.append("\n").append(equipment.getName()).append(":\n");
        report.append("  Design Pressure: ").append(design.getDesignPressure()).append(" barg\n");
        report.append("  Design Temperature: ").append(design.getDesignTemperature()).append(" °C\n");
        report.append("  Weight: ").append(design.getWeightTotal()).append(" kg\n");
    }
}

System.out.println(report);

Using the Field Development Orchestrator

For complex projects, use the FieldDevelopmentDesignOrchestrator to coordinate the entire workflow:

import neqsim.process.mechanicaldesign.designstandards.FieldDevelopmentDesignOrchestrator;
import neqsim.process.mechanicaldesign.designstandards.DesignPhase;
import neqsim.process.mechanicaldesign.designstandards.DesignCase;

// Create orchestrator
FieldDevelopmentDesignOrchestrator orchestrator = 
    new FieldDevelopmentDesignOrchestrator(process);

// Configure design phase
orchestrator.setDesignPhase(DesignPhase.FEED);  // ±15-20% accuracy

// Add design cases
orchestrator.addDesignCase(DesignCase.NORMAL);
orchestrator.addDesignCase(DesignCase.MAXIMUM);
orchestrator.addDesignCase(DesignCase.MINIMUM);
orchestrator.addDesignCase(DesignCase.UPSET);
orchestrator.addDesignCase(DesignCase.EARLY_LIFE);
orchestrator.addDesignCase(DesignCase.LATE_LIFE);

// Load TORG
orchestrator.loadTorg(torgManager, "TROLL-WEST-2025");

// Run complete workflow
orchestrator.runCompleteDesignWorkflow();

// Get results
DesignValidationResult validation = orchestrator.validateDesign();
String report = orchestrator.generateDesignReport();

System.out.println(report);

📖 See: Field Development Orchestration for complete workflow details


Design Phases and Accuracy

Choose the appropriate design phase based on project stage:

Phase Use Case Accuracy Full Mechanical Design
SCREENING Early opportunity evaluation ±40-50% No
CONCEPT_SELECT Concept comparison ±30% No
PRE_FEED Preliminary engineering ±25% No
FEED Front-end engineering ±15-20% Yes
DETAIL_DESIGN Detailed engineering ±10% Yes
AS_BUILT Verification ±5% Yes
DesignPhase phase = DesignPhase.FEED;

// Check phase requirements
if (phase.requiresFullMechanicalDesign()) {
    // Run detailed calculations
    runFullMechanicalDesign(process);
} else {
    // Use simplified estimates
    runQuickEstimates(process);
}

Supported Design Standards

NeqSim supports 30+ international standards:

Category Standards
Pressure Vessels ASME VIII Div 1/2, PD 5500, EN 13445
Process Design NORSOK P-001, NORSOK P-002
Piping ASME B31.3, NORSOK L-001
Pipelines DNV-OS-F101, API 5L
Compressors API 617, API 618
Heat Exchangers TEMA, API 660
Materials ASTM, NACE MR0175
Safety API 521, ISO 23251

📖 See: Mechanical Design Standards for complete list


Data Sources

Design parameters can be loaded from:

  1. CSV Files - Simple configuration files
  2. Database - NeqSim thermodynamic database
  3. Custom Sources - Implement MechanicalDesignDataSource
// CSV data source
StandardBasedCsvDataSource csvSource = 
    new StandardBasedCsvDataSource(StandardType.NORSOK_P002, "norsok_p002.csv");

// Register with registry
StandardRegistry.registerDataSource(StandardType.NORSOK_P002, csvSource);

📖 See: Mechanical Design Database for data configuration


Complete Example

import neqsim.thermo.system.SystemSrkEos;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.mechanicaldesign.designstandards.*;
import neqsim.process.mechanicaldesign.torg.*;

public class ProcessDesignExample {

    public static void main(String[] args) {
        // ===== STEP 1: Define System =====

        // Create fluid
        SystemSrkEos fluid = new SystemSrkEos(280.0, 50.0);
        fluid.addComponent("methane", 0.85);
        fluid.addComponent("ethane", 0.08);
        fluid.addComponent("propane", 0.04);
        fluid.addComponent("n-butane", 0.03);
        fluid.setMixingRule("classic");

        // Build flowsheet
        ProcessSystem process = new ProcessSystem();

        Stream feed = new Stream("Well Feed", fluid);
        feed.setFlowRate(50000.0, "kg/hr");
        process.add(feed);

        Separator hpSep = new Separator("HP Separator", feed);
        process.add(hpSep);

        Compressor compressor = new Compressor("Export Compressor", hpSep.getGasOutStream());
        compressor.setOutletPressure(150.0, "bara");
        process.add(compressor);

        // ===== STEP 2: Configure Orchestrator =====

        FieldDevelopmentDesignOrchestrator orchestrator = 
            new FieldDevelopmentDesignOrchestrator(process);

        orchestrator.setDesignPhase(DesignPhase.FEED);
        orchestrator.addDesignCase(DesignCase.NORMAL);
        orchestrator.addDesignCase(DesignCase.MAXIMUM);
        orchestrator.addDesignCase(DesignCase.UPSET);

        // Load TORG
        TorgManager torgManager = new TorgManager();
        torgManager.addDataSource(new CsvTorgDataSource("project_torg.csv"));
        orchestrator.loadTorg(torgManager, "PROJECT-001");

        // ===== STEP 3: Run Workflow =====

        orchestrator.runCompleteDesignWorkflow();

        // ===== STEP 4: Validate and Report =====

        DesignValidationResult validation = orchestrator.validateDesign();

        if (validation.isValid()) {
            System.out.println("Design PASSED");
            System.out.println(orchestrator.generateDesignReport());
        } else {
            System.out.println("Design FAILED");
            for (var msg : validation.getMessagesBySeverity(
                    DesignValidationResult.Severity.ERROR)) {
                System.err.println("ERROR: " + msg.getMessage());
            }
        }
    }
}

Document Description
Mechanical Design Standards StandardType enum, StandardRegistry, applying standards
Mechanical Design Database Data sources, schemas, CSV configuration
TORG Integration Technical requirements documents
Field Development Orchestration Design phases, cases, orchestrator

Quick Reference

Key Classes

Class Purpose
ProcessSystem Container for process flowsheet
MechanicalDesign Base class for equipment mechanical design
StandardType Enum of supported design standards
StandardRegistry Factory for creating and applying standards
TechnicalRequirementsDocument TORG representation
TorgManager Loads and applies TORG
FieldDevelopmentDesignOrchestrator Workflow coordinator
DesignPhase Project lifecycle phases
DesignCase Operating scenarios
DesignValidationResult Validation messages and results

Key Packages

neqsim.process.processmodel          - ProcessSystem
neqsim.process.equipment             - All equipment types
neqsim.process.mechanicaldesign      - MechanicalDesign base
neqsim.process.mechanicaldesign.designstandards - Standards framework
neqsim.process.mechanicaldesign.torg - TORG framework
neqsim.process.mechanicaldesign.data - Data sources

Chapter 12: Process Systems & Models

ProcessModel Overview

Process System and Flowsheet Management

This folder contains documentation for process system and flowsheet management in NeqSim.

Contents

Document Description
ProcessSystem Main process system class and execution strategies
ProcessModel Multi-process coordination and management
ProcessModule Modular process units
Graph-Based Simulation Graph-based execution and optimization
PFD Diagram Export Professional process flow diagram generation
Architecture & DEXPI Diagram architecture and DEXPI integration
Process Serialization Saving and loading process models

Overview

The processmodel package provides the framework for building and executing process simulations:


Execution Strategies

NeqSim provides multiple execution strategies optimized for different process types:

Method Best For Description
run() General use Sequential execution (or optimized if enabled)
runOptimized() Recommended Auto-selects best strategy based on topology
runParallel() Feed-forward processes Maximum parallelism for no-recycle processes
runHybrid() Complex processes Parallel feed-forward + iterative recycle

Enabling Optimized Execution by Default

For best performance, enable optimized execution so run() automatically uses the best strategy:

process.setUseOptimizedExecution(true);
process.run();  // Now uses runOptimized() internally

Typical performance improvements:


Quick Start

import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.separator.Separator;

// Create process system
ProcessSystem process = new ProcessSystem();

// Add equipment
process.add(feedStream);
process.add(separator);
process.add(compressor);

// Run simulation (recommended - auto-optimized)
process.runOptimized();

// Get results
process.display();

Analyzing Process Topology

// Check if process has recycles
boolean hasRecycles = process.hasRecycleLoops();

// Get execution partition analysis
System.out.println(process.getExecutionPartitionInfo());

Saving and Loading

ProcessSystem and ProcessModel support saving to compressed .neqsim files and JSON state files:

// Save process
process.saveToNeqsim("my_process.neqsim");

// Load process
ProcessSystem loaded = ProcessSystem.loadFromNeqsim("my_process.neqsim");

// Save multi-process model
model.saveToNeqsim("field_model.neqsim");
ProcessModel loaded = ProcessModel.loadFromNeqsim("field_model.neqsim");

For full documentation, see Process Serialization Guide.


ProcessSystem

ProcessSystem Class

Documentation for the ProcessSystem class in NeqSim.

Table of Contents


Overview

Location: neqsim.process.processmodel.ProcessSystem

The ProcessSystem class is the main container for building and running process flowsheets. It:


Creating a Process

Basic Constructor

import neqsim.process.processmodel.ProcessSystem;

// Create empty process system
ProcessSystem process = new ProcessSystem();

// Create with name
ProcessSystem process = new ProcessSystem("Gas Processing Plant");

Adding Equipment

Basic Addition

// Add equipment in sequence
process.add(feedStream);
process.add(heater);
process.add(separator);
process.add(compressor);

Equipment Order

Equipment is typically added in flow order, but the ProcessSystem handles dependencies automatically:

// ProcessSystem resolves dependencies
process.add(stream);        // First
process.add(heater);        // Uses stream as input
process.add(separator);     // Uses heater output
process.add(compressor);    // Uses separator gas output

Unique Names

All equipment must have unique names:

Stream stream1 = new Stream("Feed", fluid1);
Stream stream2 = new Stream("Feed", fluid2);  // ERROR: Duplicate name!

// Use unique names
Stream stream1 = new Stream("Feed-1", fluid1);
Stream stream2 = new Stream("Feed-2", fluid2);

Running Simulations

Execution Methods Overview

Method Best For Description
run() General use Sequential execution in insertion order
runOptimized() Recommended Auto-selects best strategy based on topology
runParallel() Feed-forward processes Maximum parallelism for no-recycle processes
runHybrid() Complex processes Parallel feed-forward + iterative recycle

The runOptimized() method automatically analyzes your process and selects the best execution strategy:

// Recommended - auto-selects best strategy
process.runOptimized();

// With calculation ID for tracking
UUID calcId = UUID.randomUUID();
process.runOptimized(calcId);

How it works:

Performance gains (typical separation train with 40 units, 3 recycles):

Mode Time Speedup
Regular run() 464 ms baseline
Graph-based 336 ms 28%
runOptimized() 286 ms 38%

Steady-State Simulation

// Basic sequential execution
process.run();

// Run with calculation ID for tracking
UUID calcId = UUID.randomUUID();
process.run(calcId);

Parallel Execution

For feed-forward processes (no recycles), parallel execution runs independent units simultaneously:

// Run independent units in parallel
try {
    process.runParallel();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

Note: runParallel() does not handle recycles or adjusters. Use runOptimized() for processes with recycles.

Thread Safety: Shared Stream Handling

When multiple units share the same input stream (e.g., a splitter/manifold feeding parallel branches), NeqSim automatically groups them to prevent race conditions.

How it works:

  1. Units at the same execution level are analyzed for shared input streams
  2. Units sharing an input stream are grouped together using a Union-Find algorithm
  3. Groups with shared streams run sequentially within the group
  4. Independent groups (no shared streams) run in parallel

Example - Parallel Pipelines:

// Three pipelines fed by the same manifold
Stream feedStream = new Stream("feed", fluid);
Splitter manifold = new Splitter("manifold", feedStream, 3);

AdiabaticPipe pipe1 = new AdiabaticPipe("pipe1", manifold.getSplitStream(0));
AdiabaticPipe pipe2 = new AdiabaticPipe("pipe2", manifold.getSplitStream(1));
AdiabaticPipe pipe3 = new AdiabaticPipe("pipe3", manifold.getSplitStream(2));

process.add(feedStream);
process.add(manifold);
process.add(pipe1);
process.add(pipe2);
process.add(pipe3);

// Safe: Each pipe has its own split stream (different objects)
// Pipes run in parallel without race conditions
process.runParallel();

Example - Shared Input Stream (handled automatically):

// Two units explicitly sharing the same input stream object
Stream sharedInput = valve.getOutletStream();

Heater heater1 = new Heater("heater1", sharedInput);  // Same stream object
Heater heater2 = new Heater("heater2", sharedInput);  // Same stream object

// NeqSim detects shared input and runs heater1 and heater2 sequentially
// Other independent units at this level still run in parallel
process.runParallel();

Applies to:

Hybrid Execution

For processes with recycles, hybrid execution combines parallel and iterative strategies:

// Hybrid: parallel feed-forward + iterative recycle
try {
    process.runHybrid();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

How hybrid works:

  1. Phase 1 (Parallel): Run feed-forward units (before recycles) in parallel
  2. Phase 2 (Iterative): Run recycle section with graph-based iteration until convergence

Graph-Based Execution

Enable graph-based execution for optimized unit ordering:

// Enable graph-based execution order
process.setUseGraphBasedExecution(true);
process.run();

// Or use runOptimized() which handles this automatically
process.runOptimized();

Transient Simulation

Transient simulations use graph-based parallel execution for independent branches, applying the same shared-stream grouping as steady-state methods.

// Set time step
double dt = 1.0;  // seconds

// Run single transient step
process.runTransient(dt);

// Run for specified duration
double totalTime = 3600.0;  // 1 hour
for (double t = 0; t < totalTime; t += dt) {
    process.runTransient(dt);
    logResults(t);
}

Transient with Events

// Run transient with event handling
process.runTransient(dt, (time) -> {
    if (time > 600.0) {
        // Open blowdown valve after 10 minutes
        bdv.setOpen(true);
    }
});

Execution Strategy Analysis

Check Process Topology

// Check if process has recycles
boolean hasRecycles = process.hasRecycleLoops();

// Check if parallel execution would be beneficial
boolean useParallel = process.isParallelExecutionBeneficial();

// Get detailed partition analysis
String partitionInfo = process.getExecutionPartitionInfo();
System.out.println(partitionInfo);

Example output:

=== Execution Partition Analysis ===
Total units: 40
Has recycle loops: true
Parallel levels: 29
Max parallelism: 6
Units in recycle loops: 30

=== Hybrid Execution Strategy ===
Phase 1 (Parallel): 4 levels, 8 units
Phase 2 (Iterative): 25 levels, 32 units

Execution levels:
  Level 0 [PARALLEL]: feed TP setter, first stage oil reflux, LP stream temp controller
  Level 1 [PARALLEL]: 1st stage separator
  Level 2 [PARALLEL]: oil depres valve
  Level 3 [PARALLEL]: 
  --- Recycle Section Start (iterative) ---
  Level 4: oil heater second stage [RECYCLE]
  Level 5: 2nd stage separator [RECYCLE]
  ...

Get Parallel Partition Details

// Get parallel partition
ProcessGraph.ParallelPartition partition = process.getParallelPartition();

// Number of execution levels
int levels = partition.getLevelCount();

// Maximum units that can run simultaneously
int maxParallelism = partition.getMaxParallelism();

System.out.println("Execution levels: " + levels);
System.out.println("Max parallelism: " + maxParallelism);

Retrieving Equipment

By Name

// Get specific equipment
Compressor comp = (Compressor) process.getUnit("K-100");
Separator sep = (Separator) process.getUnit("HP Separator");
Stream stream = (Stream) process.getUnit("Feed");

By Type

// Get all compressors
List<CompressorInterface> compressors = process.getUnitsOfType(CompressorInterface.class);

// Get all separators
List<SeparatorInterface> separators = process.getUnitsOfType(SeparatorInterface.class);

All Equipment

// Get all equipment
List<ProcessEquipmentInterface> allUnits = process.getUnitOperations();

for (ProcessEquipmentInterface unit : allUnits) {
    System.out.println(unit.getName() + ": " + unit.getClass().getSimpleName());
}

Results and Reporting

Console Display

// Display summary to console
process.display();

JSON Report

// Get JSON report
String jsonReport = process.getReport_json();

// Save to file
Files.writeString(Path.of("process_report.json"), jsonReport);

Tabular Report

// Get as table
String[][] table = process.getUnitOperationsAsTable();

// Print table
for (String[] row : table) {
    System.out.println(String.join("\t", row));
}

Mass Balance

// Check overall mass balance
double totalIn = 0.0;
double totalOut = 0.0;

for (ProcessEquipmentInterface unit : process.getUnitOperations()) {
    if (unit instanceof StreamInterface) {
        StreamInterface stream = (StreamInterface) unit;
        if (isInletStream(stream)) {
            totalIn += stream.getFlowRate("kg/hr");
        } else if (isOutletStream(stream)) {
            totalOut += stream.getFlowRate("kg/hr");
        }
    }
}

double balance = (totalIn - totalOut) / totalIn * 100;
System.out.println("Mass balance closure: " + balance + "%");

Process Copying

Clone Process

// Create copy of process
ProcessSystem processCopy = process.copy();

// Modify copy without affecting original
Heater heater = (Heater) processCopy.getUnit("Heater");
heater.setOutTemperature(100.0, "C");
processCopy.run();

Deep Copy

All equipment and streams are deep-copied:

// Original
process.run();
double originalT = ((Stream) process.getUnit("Feed")).getTemperature("C");

// Copy and modify
ProcessSystem copy = process.copy();
((Stream) copy.getUnit("Feed")).setTemperature(50.0, "C");
copy.run();

// Original unchanged
assert originalT == ((Stream) process.getUnit("Feed")).getTemperature("C");

Advanced Features

Execution Strategy Selection

// Use optimized execution (recommended)
process.runOptimized();  // Auto-selects best strategy

// Or manually choose strategy:

// 1. Sequential (default)
process.run();

// 2. Graph-based ordering
process.setUseGraphBasedExecution(true);
process.run();

// 3. Parallel execution (no recycles)
process.runParallel();

// 4. Hybrid execution (recycle processes)
process.runHybrid();

Asynchronous Execution

// Run in background thread
Future<?> task = process.runAsTask();

// Do other work...

// Wait for completion
task.get();

// Or check if done
if (task.isDone()) {
    System.out.println("Simulation complete");
}

Convergence Settings

// Set global convergence tolerance
process.setGlobalTolerance(1e-6);

// Set maximum iterations for recycles
process.setMaxRecycleIterations(50);

Process Modules

// Add pre-built module
ProcessModule compressorTrain = new CompressorTrainModule("HP Compression");
process.addModule(compressorTrain);

// Connect to process
compressorTrain.setInletStream(feedGas);
Stream compressed = compressorTrain.getOutletStream();

Validation

ProcessSystem provides comprehensive validation to check that all equipment is properly configured before running a simulation. This helps catch configuration errors early and provides actionable error messages.

Quick Check: isReadyToRun()

The simplest way to validate a process before execution:

ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(compressor);

// Quick check - returns true if no CRITICAL errors
if (process.isReadyToRun()) {
    process.run();
} else {
    System.out.println("Process not ready to run");
    ValidationResult result = process.validateSetup();
    result.getErrors().forEach(System.out::println);
}

Detailed Validation: validateSetup()

Get a combined ValidationResult for the entire process system:

ValidationResult result = process.validateSetup();

if (!result.isValid()) {
    System.out.println("Validation issues found:");
    System.out.println(result.getReport());

    // Iterate through specific issues
    for (ValidationIssue issue : result.getIssues()) {
        System.out.println(issue.getSeverity() + ": " + issue.getMessage());
        System.out.println("  Fix: " + issue.getRemediation());
    }
}

Severity Levels:

Level Description
CRITICAL Blocks execution - must be fixed
MAJOR Likely to cause errors during simulation
MINOR May affect accuracy of results
INFO Informational warnings

Per-Equipment Validation: validateAll()

Get individual validation results for each piece of equipment:

Map<String, ValidationResult> allResults = process.validateAll();

for (Map.Entry<String, ValidationResult> entry : allResults.entrySet()) {
    String equipmentName = entry.getKey();
    ValidationResult equipResult = entry.getValue();

    if (!equipResult.isValid()) {
        System.out.println(equipmentName + " has issues:");
        equipResult.getErrors().forEach(e -> System.out.println("  - " + e));
    }
}

Equipment-Level Validation

Each equipment class implements validateSetup() to check equipment-specific requirements:

Equipment Validates
Stream Has fluid set, temperature > 0 K
Separator Inlet stream connected
Mixer At least one inlet stream
Splitter Inlet stream connected, split fractions sum to 1.0
Tank Has fluid or input stream
DistillationColumn Feed streams connected, condenser/reboiler configured
Recycle Inlet and outlet streams connected, tolerance > 0
Adjuster Target and adjustment variables set, tolerance > 0
TwoPortEquipment Inlet stream connected

Example - Individual Equipment Validation:

Separator separator = new Separator("V-100");
// Forgot to set inlet stream

ValidationResult result = separator.validateSetup();
if (!result.isValid()) {
    // Will report: "Separator 'V-100' has no inlet stream connected"
    System.out.println(result.getReport());
}

Validation in AI/ML Workflows

For AI agents and automated workflows, validation provides structured feedback:

AIIntegrationHelper helper = AIIntegrationHelper.forProcess(process);

if (helper.isReady()) {
    ExecutionResult result = helper.safeRun();
} else {
    // Get issues as structured text for AI to parse
    String[] issues = helper.getIssuesAsText();
    for (String issue : issues) {
        // AI can parse and fix these issues
        System.out.println(issue);
    }
}

See AI Validation Framework for more details on AI integration.


Examples

Simple Separation Process

ProcessSystem process = new ProcessSystem("Separator System");

// Create fluid
SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.04);
fluid.addComponent("n-butane", 0.03);
fluid.setMixingRule("classic");

// Feed stream
Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(100000.0, "kg/hr");
process.add(feed);

// Inlet valve
ThrottlingValve inletValve = new ThrottlingValve("Inlet Valve", feed);
inletValve.setOutletPressure(30.0, "bara");
process.add(inletValve);

// HP Separator
Separator hpSep = new Separator("HP Separator", inletValve.getOutletStream());
process.add(hpSep);

// LP Valve
ThrottlingValve lpValve = new ThrottlingValve("LP Valve", hpSep.getLiquidOutStream());
lpValve.setOutletPressure(5.0, "bara");
process.add(lpValve);

// LP Separator
Separator lpSep = new Separator("LP Separator", lpValve.getOutletStream());
process.add(lpSep);

// Run
process.run();

// Results
System.out.println("HP Gas: " + hpSep.getGasOutStream().getFlowRate("MSm3/day") + " MSm3/day");
System.out.println("LP Gas: " + lpSep.getGasOutStream().getFlowRate("MSm3/day") + " MSm3/day");
System.out.println("Liquid: " + lpSep.getLiquidOutStream().getFlowRate("m3/hr") + " m3/hr");

Compression System

ProcessSystem process = new ProcessSystem("Compression System");

// Gas feed
Stream gas = new Stream("Gas Feed", gasFluid);
gas.setFlowRate(50000.0, "Sm3/hr");
gas.setTemperature(40.0, "C");
gas.setPressure(5.0, "bara");
process.add(gas);

// First stage compressor
Compressor comp1 = new Compressor("K-101", gas);
comp1.setOutletPressure(15.0, "bara");
comp1.setPolytropicEfficiency(0.78);
process.add(comp1);

// Intercooler
Cooler cooler1 = new Cooler("E-101", comp1.getOutletStream());
cooler1.setOutTemperature(40.0, "C");
process.add(cooler1);

// Second stage compressor
Compressor comp2 = new Compressor("K-102", cooler1.getOutletStream());
comp2.setOutletPressure(45.0, "bara");
comp2.setPolytropicEfficiency(0.78);
process.add(comp2);

// Aftercooler
Cooler cooler2 = new Cooler("E-102", comp2.getOutletStream());
cooler2.setOutTemperature(40.0, "C");
process.add(cooler2);

// Run
process.run();

// Total power
double totalPower = comp1.getPower("kW") + comp2.getPower("kW");
System.out.println("Total compression power: " + totalPower + " kW");

Process with Recycle

ProcessSystem process = new ProcessSystem("Recycle Process");

// Fresh feed
Stream freshFeed = new Stream("Fresh Feed", freshFluid);
freshFeed.setFlowRate(1000.0, "kg/hr");
process.add(freshFeed);

// Mixer for fresh feed and recycle
Mixer feedMixer = new Mixer("Feed Mixer");
feedMixer.addStream(freshFeed);
process.add(feedMixer);

// Reactor
GibbsReactor reactor = new GibbsReactor("Reactor");
reactor.setInletStream(feedMixer.getOutletStream());
process.add(reactor);

// Product separator
Separator productSep = new Separator("Product Sep", reactor.getOutletStream());
process.add(productSep);

// Product stream
Stream product = productSep.getLiquidOutStream();

// Recycle unreacted gas
Recycle recycle = new Recycle("Gas Recycle");
recycle.addStream(productSep.getGasOutStream());
recycle.setOutletStream(feedMixer);
recycle.setTolerance(1e-5);
process.add(recycle);

// Complete the connection
feedMixer.addStream(recycle.getOutletStream());

// Run (will iterate until recycle converges)
process.run();

System.out.println("Recycle converged: " + recycle.isConverged());
System.out.println("Product rate: " + product.getFlowRate("kg/hr") + " kg/hr");

Saving and Loading

ProcessSystem supports saving and loading to/from compressed .neqsim files and JSON state files for version control.

// Save to compressed .neqsim file (recommended)
process.saveToNeqsim("my_process.neqsim");

// Load (auto-runs after loading)
ProcessSystem loaded = ProcessSystem.loadFromNeqsim("my_process.neqsim");

// Auto-detect format by extension
process.saveAuto("my_process.neqsim");  // Compressed XStream XML
process.saveAuto("my_process.json");    // JSON state export

// JSON state for version control
ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);
state.setVersion("1.0.0");
state.saveToFile("my_process_v1.0.0.json");

For full documentation on serialization options, see Process Serialization Guide.


ProcessModel

ProcessModel Class

Documentation for the ProcessModel class in NeqSim.

Table of Contents


Overview

Location: neqsim.process.processmodel.ProcessModel

The ProcessModel class manages a collection of ProcessSystem objects that can be run together. It provides:

Use ProcessModel when you need to simulate interconnected process systems or coordinate multiple flowsheets.


Creating a ProcessModel

Basic Constructor

import neqsim.process.processmodel.ProcessModel;

// Create empty process model
ProcessModel model = new ProcessModel();

Adding Processes

Adding ProcessSystems

// Create process systems
ProcessSystem gasProcessing = new ProcessSystem("Gas Processing");
gasProcessing.add(feedGas);
gasProcessing.add(separator);
gasProcessing.add(compressor);

ProcessSystem oilProcessing = new ProcessSystem("Oil Processing");
oilProcessing.add(oilFeed);
oilProcessing.add(heater);
oilProcessing.add(stabilizer);

// Add to model
model.add("Gas Processing", gasProcessing);
model.add("Oil Processing", oilProcessing);

Accessing Processes

// Get specific process
ProcessSystem gas = model.get("Gas Processing");

// Get all processes
Collection<ProcessSystem> allProcesses = model.getAllProcesses();

Running the Model

Continuous Mode (Default)

// Run until convergence or max iterations
model.run();

// Check if converged
if (model.isModelConverged()) {
    System.out.println("Model converged in " + model.getLastIterationCount() + " iterations");
}

Step Mode

// Enable step mode
model.setRunStep(true);

// Run one step at a time
model.run();  // Runs one step for each process

Optimized Execution

// Enable optimized execution (default is true)
model.setUseOptimizedExecution(true);
model.run();

// Each ProcessSystem uses runOptimized() internally

Asynchronous Execution

// Run in background thread
Future<?> task = model.runAsTask();

// Do other work...

// Wait for completion
task.get();

Convergence Tracking

Setting Tolerances

// Set individual tolerances
model.setFlowTolerance(1e-5);        // Relative flow error
model.setTemperatureTolerance(1e-5); // Relative temperature error
model.setPressureTolerance(1e-5);    // Relative pressure error

// Or set all at once
model.setTolerance(1e-5);

// Set maximum iterations
model.setMaxIterations(100);

Checking Convergence

model.run();

// Check overall convergence
boolean converged = model.isModelConverged();

// Get convergence errors
double flowErr = model.getLastMaxFlowError();
double tempErr = model.getLastMaxTemperatureError();
double pressErr = model.getLastMaxPressureError();
double maxErr = model.getError();

// Get detailed summary
System.out.println(model.getConvergenceSummary());

Validation

ProcessModel provides comprehensive validation to check that all contained ProcessSystems are properly configured before running.

Quick Check: isReadyToRun()

// Quick check - returns true if no CRITICAL errors
if (model.isReadyToRun()) {
    model.run();
} else {
    System.out.println("Model not ready to run");
    System.out.println(model.getValidationReport());
}

Detailed Validation: validateSetup()

ValidationResult result = model.validateSetup();

if (!result.isValid()) {
    System.out.println("Validation issues found:");
    System.out.println(result.getReport());
}

Per-Process Validation: validateAll()

Map<String, ValidationResult> allResults = model.validateAll();

for (Map.Entry<String, ValidationResult> entry : allResults.entrySet()) {
    String processName = entry.getKey();
    ValidationResult processResult = entry.getValue();

    if (!processResult.isValid()) {
        System.out.println(processName + " has issues:");
        processResult.getErrors().forEach(System.out::println);
    }
}

Formatted Validation Report

// Get a human-readable validation report
String report = model.getValidationReport();
System.out.println(report);

Example output:

=== ProcessModel Validation Report ===

--- EmptyProcess ---
  [CRITICAL] ProcessSystem is empty
    Fix: Add at least one process equipment using add()

Summary: 1 issue(s) found (1 critical, 0 major)
Ready to run: NO

Validation Methods Summary:

Method Returns Description
validateSetup() ValidationResult Combined result for all processes
validateAll() Map<String, ValidationResult> Per-process results
isReadyToRun() boolean True if no CRITICAL errors
getValidationReport() String Formatted human-readable report

Mass Balance

Checking Mass Balance

// Get mass balance for all processes
Map<String, Map<String, ProcessSystem.MassBalanceResult>> results = 
    model.checkMassBalance("kg/hr");

// Get failed mass balance checks
Map<String, Map<String, ProcessSystem.MassBalanceResult>> failed = 
    model.getFailedMassBalance(0.1);  // 0.1% threshold

// Get formatted reports
System.out.println(model.getMassBalanceReport());
System.out.println(model.getFailedMassBalanceReport());

Examples

Multi-Process Simulation

// Create gas processing system
ProcessSystem gasProcess = new ProcessSystem("Gas Train");
Stream gasIn = new Stream("Gas Feed", gasFluid);
Separator scrubber = new Separator("Inlet Scrubber", gasIn);
Compressor comp = new Compressor("Export Compressor", scrubber.getGasOutStream());
gasProcess.add(gasIn);
gasProcess.add(scrubber);
gasProcess.add(comp);

// Create oil processing system  
ProcessSystem oilProcess = new ProcessSystem("Oil Train");
Stream oilIn = new Stream("Oil Feed", oilFluid);
Heater heater = new Heater("Oil Heater", oilIn);
Separator stabilizer = new Separator("Stabilizer", heater.getOutletStream());
oilProcess.add(oilIn);
oilProcess.add(heater);
oilProcess.add(stabilizer);

// Create model and add processes
ProcessModel model = new ProcessModel();
model.add("Gas Train", gasProcess);
model.add("Oil Train", oilProcess);

// Validate before running
if (model.isReadyToRun()) {
    model.run();

    if (model.isModelConverged()) {
        System.out.println("Model converged!");
        System.out.println(model.getConvergenceSummary());
    }
} else {
    System.out.println(model.getValidationReport());
}

Saving and Loading

ProcessModel supports saving and loading to/from compressed .neqsim files and JSON state files for version control.

// Save to compressed .neqsim file
model.saveToNeqsim("field_model.neqsim");

// Load (auto-runs after loading)
ProcessModel loaded = ProcessModel.loadFromNeqsim("field_model.neqsim");

// Auto-detect format by extension
model.saveAuto("field_model.neqsim");  // Compressed
model.saveAuto("field_model.json");    // JSON state

// JSON state export for version control
ProcessModelState state = model.exportState();
state.setVersion("1.0.0");
state.saveToFile("field_model_v1.0.0.json");

For full documentation on serialization options, see Process Serialization Guide.


ProcessModule

ProcessModule Class

Documentation for modular process units in NeqSim.

Table of Contents


Overview

Location: neqsim.process.processmodel.ProcessModule

Process modules encapsulate complex process subsystems into reusable units. Benefits include:


Creating Modules

Basic Module

import neqsim.process.processmodel.ProcessModule;

// Create module
ProcessModule module = new ProcessModule("Compression Train");

// Add equipment to module
module.add(scrubber);
module.add(compressor);
module.add(cooler);

// Set inlet/outlet
module.setInletStream(gasIn);
module.setOutletStream(cooler.getOutletStream());

Adding to Process

ProcessSystem process = new ProcessSystem();

// Add module to process
process.addModule(module);

// Connect to other equipment
module.setInletStream(feedStream);
Stream compressed = module.getOutletStream();
process.add(downstream equipment);

Module Interface

ModuleInterface Methods

public interface ModuleInterface {
    // Identification
    String getName();
    void setName(String name);

    // Streams
    void setInletStream(StreamInterface stream);
    StreamInterface getInletStream();
    StreamInterface getOutletStream();

    // Execution
    void run();
    void runTransient(double dt);

    // Initialization
    void initializeModule();

    // Equipment access
    ProcessEquipmentInterface getUnit(String name);
    List<ProcessEquipmentInterface> getUnits();
}

Built-in Modules

Compressor Train Module

// Multi-stage compression with intercooling
CompressorTrainModule compTrain = new CompressorTrainModule("HP Compression");
compTrain.setNumberOfStages(3);
compTrain.setOutletPressure(150.0, "bara");
compTrain.setIntercoolerTemperature(40.0, "C");
compTrain.setIsentropicEfficiency(0.78);

compTrain.setInletStream(feedGas);
process.addModule(compTrain);

Separation Train Module

// Multi-stage separation
SeparationTrainModule sepTrain = new SeparationTrainModule("Separation");
sepTrain.addStage(50.0, "bara");  // HP stage
sepTrain.addStage(15.0, "bara");  // MP stage
sepTrain.addStage(3.0, "bara");   // LP stage

sepTrain.setInletStream(wellFluid);
process.addModule(sepTrain);

// Get products
Stream exportGas = sepTrain.getGasOutStream();
Stream exportOil = sepTrain.getOilOutStream();
Stream prodWater = sepTrain.getWaterOutStream();

Dehydration Module

// TEG dehydration unit
DehydrationModule dehy = new DehydrationModule("Gas Dehy");
dehy.setWaterDewPoint(-20.0, "C");
dehy.setSolventType("TEG");

dehy.setInletStream(wetGas);
process.addModule(dehy);

Stream dryGas = dehy.getOutletStream();

Custom Modules

Creating Custom Module

public class MyCustomModule extends ProcessModule {

    private Separator inlet Scrubber;
    private HeatExchanger heatEx;
    private Compressor compressor;

    public MyCustomModule(String name) {
        super(name);
        initializeEquipment();
    }

    private void initializeEquipment() {
        inletScrubber = new Separator("Inlet Scrubber");
        add(inletScrubber);

        heatEx = new HeatExchanger("Feed/Effluent HX");
        add(heatEx);

        compressor = new Compressor("Main Compressor");
        add(compressor);
    }

    @Override
    public void setInletStream(StreamInterface stream) {
        super.setInletStream(stream);
        inletScrubber.setInletStream(stream);
    }

    @Override
    public StreamInterface getOutletStream() {
        return compressor.getOutletStream();
    }

    public void setOutletPressure(double pressure, String unit) {
        compressor.setOutletPressure(pressure, unit);
    }
}

Using Custom Module

MyCustomModule customModule = new MyCustomModule("My Unit");
customModule.setInletStream(feed);
customModule.setOutletPressure(80.0, "bara");

process.addModule(customModule);
process.run();

Module Communication

Multiple Inlets

public class TwoInletModule extends ProcessModule {
    private Mixer inletMixer;

    public void setInletStream1(StreamInterface stream) {
        inletMixer.addStream(stream);
    }

    public void setInletStream2(StreamInterface stream) {
        inletMixer.addStream(stream);
    }
}

Multiple Outlets

public class TwoOutletModule extends ProcessModule {
    private Splitter outletSplitter;

    public StreamInterface getOutletStream1() {
        return outletSplitter.getOutletStream(0);
    }

    public StreamInterface getOutletStream2() {
        return outletSplitter.getOutletStream(1);
    }
}

Usage Examples

LNG Train Module

public class LNGTrainModule extends ProcessModule {

    private Cooler precooler;
    private HeatExchanger mainCryoExchanger;
    private Expander jt Expander;
    private Separator lngSeparator;

    public LNGTrainModule(String name) {
        super(name);

        precooler = new Cooler("Precooler");
        add(precooler);

        mainCryoExchanger = new HeatExchanger("MCHE");
        add(mainCryoExchanger);

        jtExpander = new Expander("JT Expander");
        add(jtExpander);

        lngSeparator = new Separator("LNG Separator");
        add(lngSeparator);
    }

    @Override
    public void setInletStream(StreamInterface stream) {
        super.setInletStream(stream);
        precooler.setInletStream(stream);
    }

    public void setLNGTemperature(double temp, String unit) {
        // Configure for target LNG temperature
    }

    public StreamInterface getLNGStream() {
        return lngSeparator.getLiquidOutStream();
    }

    public StreamInterface getBoilOffGas() {
        return lngSeparator.getGasOutStream();
    }
}

// Usage
LNGTrainModule lngTrain = new LNGTrainModule("LNG Production");
lngTrain.setInletStream(treatedGas);
lngTrain.setLNGTemperature(-162.0, "C");

process.addModule(lngTrain);
process.run();

Stream lng = lngTrain.getLNGStream();
System.out.println("LNG production: " + lng.getFlowRate("tonne/day") + " t/d");

FPSO Topsides Module

// Complete FPSO processing
ProcessSystem fpso = new ProcessSystem("FPSO Topsides");

// Separation module
SeparationTrainModule separation = new SeparationTrainModule("Separation");
separation.setInletStream(wellFluid);
fpso.addModule(separation);

// Gas compression
CompressorTrainModule gasComp = new CompressorTrainModule("Gas Compression");
gasComp.setInletStream(separation.getGasOutStream());
gasComp.setOutletPressure(200.0, "bara");
fpso.addModule(gasComp);

// Gas dehydration
DehydrationModule dehy = new DehydrationModule("Dehydration");
dehy.setInletStream(gasComp.getOutletStream());
fpso.addModule(dehy);

// Water treatment
WaterTreatmentModule waterTreat = new WaterTreatmentModule("Water Treatment");
waterTreat.setInletStream(separation.getWaterOutStream());
fpso.addModule(waterTreat);

fpso.run();

Best Practices

  1. Self-Contained: Modules should be self-contained with clear interfaces
  2. Documented Interfaces: Document inlet/outlet requirements
  3. Reasonable Defaults: Provide sensible default values
  4. Error Handling: Validate inputs and provide clear error messages
  5. Testing: Create unit tests for each module

Graph Simulation

Graph-Based Process Simulation

Documentation for graph-based execution in NeqSim.

Table of Contents


Overview

Location: neqsim.process.processmodel.graph

Classes:

Class Description
ProcessGraph Graph representation of process
ProcessGraphBuilder Builder for constructing graphs
ProcessNode Node representing equipment
ProcessEdge Edge representing stream connection

Graph-based simulation represents the process as a directed graph where:

Benefits:


Execution Strategies

Quick Start - Use runOptimized()

The recommended approach is to use runOptimized() which automatically analyzes your process and selects the best strategy:

// Auto-selects best execution strategy based on process topology
process.runOptimized();

// With calculation ID for tracking
UUID calcId = UUID.randomUUID();
process.runOptimized(calcId);

The method inspects the process for:

Execution Strategy Comparison

Strategy Method Best For When Used by runOptimized()
Sequential run() or runSequential() Recycles, multi-input equipment Has Recycle units or Mixer/HeatExchanger/etc.
Graph-based setUseGraphBasedExecution(true) Complex ordering Manual configuration only
Parallel runParallel() Feed-forward (no recycles) No recycles, no multi-input, no adjusters
Hybrid runHybrid() Processes with adjusters Has adjusters but no recycles/multi-input
Optimized runOptimized() All processes Auto-selects from above

Sequential Execution (Default)

Standard execution in insertion order:

process.run();

Graph-Based Execution

Uses topological ordering for optimal execution sequence:

// Enable graph-based ordering
process.setUseGraphBasedExecution(true);
process.run();

Parallel Execution

Executes independent units simultaneously using thread pool:

// For feed-forward processes (no recycles)
try {
    process.runParallel();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

How it works:

  1. Builds dependency graph
  2. Partitions into execution levels
  3. Runs units at each level in parallel
  4. Waits for level completion before next level

Hybrid Execution

Combines parallel and iterative execution for processes with recycles:

// For processes with recycles
try {
    process.runHybrid();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

How it works:

  1. Phase 1 (Parallel): Run feed-forward units in parallel
  2. Phase 2 (Iterative): Run recycle section with convergence iteration

Automatically selects the best strategy based on process topology:

// Auto-selects best execution strategy
process.runOptimized();

Decision logic (in order of priority):

Condition Strategy Reason
Has Recycle units runSequential() Recycles require full iterative convergence
Has multi-input equipment runSequential() Mixer, Manifold, etc. need correct stream ordering
Has Adjuster units runHybrid() Adjusters need iteration but feed-forward can parallelize
Feed-forward only runParallel() Maximum speed with no dependencies

Multi-input equipment includes:

Note: hasRecycles() checks for explicit Recycle unit operations, not graph-based cycle detection.


Analyzing Execution Strategy

Check Process Topology

// Check if process has Recycle units (requires iterative execution)
boolean hasRecycles = process.hasRecycles();

// Check if process has Adjuster units (requires iteration)
boolean hasAdjusters = process.hasAdjusters();

// Check if process has multi-input equipment (requires sequential)
// Includes: Mixer, Manifold, HeatExchanger, TurboExpanderCompressor, etc.
boolean hasMultiInput = process.hasMultiInputEquipment();

// Check if parallel execution would be beneficial (graph-based)
boolean beneficial = process.isParallelExecutionBeneficial();

// Get detailed partition analysis
System.out.println(process.getExecutionPartitionInfo());

Understanding runOptimized() Selection

// What will runOptimized() do for my process?
if (process.hasRecycles()) {
    System.out.println("Will use: runSequential() - Recycle units detected");
} else if (process.hasMultiInputEquipment()) {
    System.out.println("Will use: runSequential() - Multi-input equipment detected");
} else if (process.hasAdjusters()) {
    System.out.println("Will use: runHybrid() - Adjusters with parallel feed-forward");
} else {
    System.out.println("Will use: runParallel() - Full parallel execution");
}

Example Partition Analysis Output

=== Execution Partition Analysis ===
Total units: 40
Has recycle loops: true
Parallel levels: 29
Max parallelism: 6
Units in recycle loops: 30
  - 1st stage compressor
  - 2nd stage separator
  ...

=== Hybrid Execution Strategy ===
Phase 1 (Parallel): 4 levels, 8 units
Phase 2 (Iterative): 25 levels, 32 units

Execution levels:
  Level 0 [PARALLEL]: feed TP setter, first stage oil reflux, export oil
  Level 1 [PARALLEL]: 1st stage separator
  Level 2 [PARALLEL]: oil depres valve
  Level 3 [PARALLEL]: 
  --- Recycle Section Start (iterative) ---
  Level 4: oil heater second stage [RECYCLE]
  Level 5: 2nd stage separator [RECYCLE]
  ...

Get Parallel Partition

// Get detailed partition info
ProcessGraph.ParallelPartition partition = process.getParallelPartition();

System.out.println("Execution levels: " + partition.getLevelCount());
System.out.println("Max parallelism: " + partition.getMaxParallelism());

// Iterate through levels
for (List<ProcessNode> level : partition.getLevels()) {
    System.out.println("Level has " + level.size() + " units");
}

ProcessGraph Class

Basic Usage

import neqsim.process.processmodel.graph.ProcessGraph;
import neqsim.process.processmodel.graph.ProcessGraphBuilder;

// Build graph from process
ProcessGraphBuilder builder = new ProcessGraphBuilder();
ProcessGraph graph = builder.build(processSystem);

// Execute in topological order
graph.execute();

Graph Properties

// Get number of nodes (equipment)
int nodeCount = graph.getNodeCount();

// Get number of edges (connections)
int edgeCount = graph.getEdgeCount();

// Check for cycles (recycles)
boolean hasCycles = graph.hasCycles();

Graph Construction

Automatic Construction

// Build from existing process
ProcessGraphBuilder builder = new ProcessGraphBuilder();
ProcessGraph graph = builder.build(process);

Manual Construction

ProcessGraph graph = new ProcessGraph();

// Add nodes
graph.addNode(feed);
graph.addNode(heater);
graph.addNode(separator);

// Add edges (connections)
graph.addEdge(feed, heater);
graph.addEdge(heater, separator);

With Metadata

// Add node with properties
Map<String, Object> props = new HashMap<>();
props.put("criticality", "high");
props.put("maintainPriority", 1);
graph.addNode(compressor, props);

Graph Analysis

Topological Sort

// Get execution order
List<ProcessEquipmentInterface> order = graph.topologicalSort();

for (int i = 0; i < order.size(); i++) {
    System.out.println((i+1) + ". " + order.get(i).getName());
}

Find Cycles

// Identify recycle loops
List<List<ProcessEquipmentInterface>> cycles = graph.findCycles();

for (List<ProcessEquipmentInterface> cycle : cycles) {
    System.out.println("Cycle found:");
    for (ProcessEquipmentInterface node : cycle) {
        System.out.println("  - " + node.getName());
    }
}

Critical Path

// Find longest path (critical path)
List<ProcessEquipmentInterface> criticalPath = graph.findCriticalPath();

System.out.println("Critical path:");
for (ProcessEquipmentInterface node : criticalPath) {
    System.out.println("  " + node.getName());
}

Visualization

Export to DOT Format

// Export for Graphviz visualization
String dot = graph.toDOT();
Files.writeString(Path.of("process_graph.dot"), dot);

// Generate image with Graphviz:
// dot -Tpng process_graph.dot -o process_graph.png

Export to JSON

// Export graph structure to JSON
String json = graph.toJSON();
Files.writeString(Path.of("process_graph.json"), json);

Usage Examples

Parallel Compression Train

ProcessSystem process = new ProcessSystem();

// Feed splitter
Splitter splitter = new Splitter("Feed Splitter", feedStream);
splitter.setSplitRatios(new double[]{0.5, 0.5});
process.add(splitter);

// Parallel compressor trains (can execute simultaneously)
Compressor comp1 = new Compressor("K-101", splitter.getOutletStream(0));
comp1.setOutletPressure(80.0, "bara");
process.add(comp1);

Compressor comp2 = new Compressor("K-102", splitter.getOutletStream(1));
comp2.setOutletPressure(80.0, "bara");
process.add(comp2);

// Merger
Mixer mixer = new Mixer("Discharge Mixer");
mixer.addStream(comp1.getOutletStream());
mixer.addStream(comp2.getOutletStream());
process.add(mixer);

// Build graph
ProcessGraph graph = new ProcessGraphBuilder().build(process);

// Parallel execution - K-101 and K-102 run simultaneously
graph.setExecutionStrategy(ExecutionStrategy.PARALLEL);
graph.execute();

Complex Flowsheet Analysis

// Build graph from complex process
ProcessGraph graph = new ProcessGraphBuilder().build(process);

// Analyze structure
System.out.println("Process structure:");
System.out.println("  Equipment count: " + graph.getNodeCount());
System.out.println("  Connections: " + graph.getEdgeCount());
System.out.println("  Has recycles: " + graph.hasCycles());

// Identify independent sections
List<Set<ProcessEquipmentInterface>> sections = graph.findConnectedComponents();
System.out.println("  Independent sections: " + sections.size());

// Find potential bottlenecks (high in-degree)
for (ProcessEquipmentInterface node : graph.getNodes()) {
    int inDegree = graph.getInDegree(node);
    if (inDegree > 2) {
        System.out.println("  Potential bottleneck: " + node.getName() + 
            " (" + inDegree + " inputs)");
    }
}

Recycle Identification

ProcessGraph graph = new ProcessGraphBuilder().build(process);

// Find all recycle streams
List<List<ProcessEquipmentInterface>> cycles = graph.findCycles();

System.out.println("Recycle loops identified:");
for (int i = 0; i < cycles.size(); i++) {
    System.out.println("Recycle " + (i+1) + ":");
    List<ProcessEquipmentInterface> cycle = cycles.get(i);
    for (ProcessEquipmentInterface node : cycle) {
        System.out.println("  -> " + node.getName());
    }

    // Suggest tear stream (node with lowest "impact")
    ProcessEquipmentInterface tearStream = graph.suggestTearStream(cycle);
    System.out.println("  Suggested tear stream: " + tearStream.getName());
}

Performance Optimization

Identify Parallel Opportunities

// Get execution levels
List<List<ProcessEquipmentInterface>> levels = graph.getExecutionLevels();

// Count parallel opportunities
int parallelOps = 0;
for (List<ProcessEquipmentInterface> level : levels) {
    if (level.size() > 1) {
        parallelOps += level.size() - 1;
    }
}

System.out.println("Parallel execution opportunities: " + parallelOps);
System.out.println("Potential speedup: " + 
    (double)graph.getNodeCount() / levels.size() + "x");

Subgraph Extraction

// Extract subgraph for specific section
Set<ProcessEquipmentInterface> compressionUnits = process.getUnitsOfType(
    CompressorInterface.class).stream().collect(Collectors.toSet());

ProcessGraph compressionGraph = graph.extractSubgraph(compressionUnits);

// Analyze compression section separately
compressionGraph.execute();


ProcessModel Execution

When combining multiple ProcessSystem instances into a ProcessModel, execution follows a similar pattern:

Running ProcessModel

import neqsim.process.processmodel.ProcessModel;

// Create and populate model
ProcessModel model = new ProcessModel();
model.add("Upstream", upstreamProcess);
model.add("Compression", compressionProcess);
model.add("Export", exportProcess);

// Run until convergence (uses optimized execution internally)
model.run();

// Check convergence
if (model.isModelConverged()) {
    System.out.println("Converged in " + model.getLastIterationCount() + " iterations");
}

Execution Options

// Continuous mode (default) - iterates until convergence
model.setRunStep(false);
model.run();

// Step mode - run one iteration at a time
model.setRunStep(true);
model.run();  // One step for each ProcessSystem
model.run();  // Next step...

// Asynchronous execution
Future<?> task = model.runAsTask();
// ... do other work ...
task.get();  // Wait for completion

Optimized Execution in ProcessModel

Each ProcessSystem within a ProcessModel uses runOptimized() by default:

// Enable/disable optimized execution for contained ProcessSystems
model.setUseOptimizedExecution(true);  // Default
model.run();

Diagram Export

Process Flow Diagram (PFD) Export

NeqSim can generate professional oil & gas style process flow diagrams (PFDs) that follow industry conventions, comparable to UniSim, Aspen, and HYSYS.

Quick Start

// Create and run your process
ProcessSystem process = new ProcessSystem("Gas Processing Plant");
// ... add equipment ...
process.run();

// Export to DOT format (text)
String dot = process.toDOT();

// Or use the diagram exporter for more control
process.createDiagramExporter()
    .setTitle("Gas Processing Plant")
    .setDetailLevel(DiagramDetailLevel.STANDARD)
    .exportAsSVG(Path.of("diagram.svg"));

Features

Gravity-Based Layout

The diagram layout follows oil & gas conventions with left-to-right flow:

Phase-Aware Stream Styling

Streams are automatically colored based on phase composition:

Separator Outlet Semantics

For two-phase separators, outlets are positioned:

For three-phase separators (gas, oil, aqueous), outlets follow gravity:

Comprehensive Equipment Support

The diagram system supports all NeqSim equipment types with industry-standard shapes:

Separators & Vessels

Equipment Shape Color
Separator Cylinder Green
ThreePhaseSeparator Cylinder Green
Scrubber Cylinder Light Green
GasScrubber Cylinder Light Green
KnockOutDrum Cylinder Light Green
Flash Cylinder Light Green

Columns

Equipment Shape Color
DistillationColumn Tall Cylinder Green
Absorber Rectangle Light Green
Stripper Rectangle Light Green
WaterStripperColumn Rectangle Light Green

Compressors & Expanders

Equipment Shape Symbol
Compressor Trapezoid Standard P&ID trapezoid
CompressorModule Trapezoid Standard P&ID trapezoid
Expander Inverted Trapezium Inverted trapezoid
TurbineExpander Inverted Trapezium Inverted trapezoid

Pumps

Equipment Shape Symbol
Pump Circle with impeller Circle on triangle base
PumpModule Circle with impeller Circle on triangle base
ESP (Electrical Submersible Pump) Circle with impeller Circle on triangle base

Heat Exchangers

Equipment Shape Symbol
HeatExchanger Circle Simple circle
Cooler Circle Simple circle
Heater Circle Simple circle
Condenser Circle Simple circle
Reboiler Circle Simple circle
MultiStreamHeatExchanger Circle Simple circle
DirectContactHeater Circle Simple circle

Valves

Equipment Shape Symbol
ThrottlingValve Bowtie (▶◀) Two triangles tip-to-tip
ValveMoV Bowtie (▶◀) Two triangles tip-to-tip
ControlValve Bowtie (▶◀) Two triangles tip-to-tip
SafetyValve Bowtie (▶◀) Two triangles tip-to-tip
PressureReliefValve Bowtie (▶◀) Two triangles tip-to-tip
ESDValve Bowtie (▶◀) Two triangles tip-to-tip
HIPPSValve Bowtie (▶◀) Two triangles tip-to-tip

Reactors

Equipment Shape Color
Reactor Hexagon Orange
GibbsReactor Hexagon Orange
EquilibriumReactor Hexagon Orange
ElectrolyzerCell Hexagon Light Blue

Other Equipment

Equipment Shape Color
Mixer Triangle Light Gray
Splitter Inverted Triangle Light Gray
Stream Ellipse Light Green
Ejector Pentagon Light Gray
Flare Diamond Orange
Filter Rectangle Tan
Membrane Rectangle Light Blue
Tank Cylinder Gray
Pipeline Rectangle Gray
Well House Brown
GasGenerator/GasTurbine Octagon Steel Blue

Control Equipment

Equipment Shape Color
Recycle Rectangle (dashed) Gray
Adjuster Rectangle (dashed) Gray
Calculator Rectangle (dashed) Light Blue
Controller Rectangle (dashed) Light Yellow
SetPoint Rectangle (dashed) Light Gray

Diagram Styles

Four diagram styles are available to match different simulator conventions:

import neqsim.process.processmodel.diagram.DiagramStyle;

ProcessDiagramExporter exporter = new ProcessDiagramExporter(process);
exporter.setDiagramStyle(DiagramStyle.HYSYS);
Style Description Stream Color Background
NEQSIM Default NeqSim style with phase-based coloring Phase-dependent White
HYSYS HYSYS/UniSim style Blue (#0066CC) Light Cyan
PROII PRO/II style Dark Blue (#003366) White
ASPEN_PLUS Aspen Plus style Blue (#0066FF) Light Gray

Equipment Symbols (PFD Standard)

Equipment symbols follow oil & gas PFD conventions:

Equipment Symbol Description
Valve ▶◀ Bowtie - two triangles tip-to-tip
Heater/Cooler Simple circle
Pump ○ on ▽ Circle with impeller on triangle
Compressor Trapezoid shape
Mixer Right-pointing triangle
Splitter Left-pointing triangle
Separator Vertical cylinder

Recycle Stream Highlighting

Anti-surge loops and recycle streams are automatically detected and highlighted:

Stream Value Display

Two display modes for process values:

Simple Text Labels

exporter.setShowStreamValues(true)
        .setUseStreamTables(false);

Shows: Stream Name\n25.0°C, 50.0 bar\n1000 kg/hr

HTML Table Labels (Professional)

exporter.setShowStreamValues(true)
        .setUseStreamTables(true);

Generates HTML tables with:

Control Equipment Filtering

Control equipment (Recycle, Adjuster, Calculator, etc.) can be hidden for cleaner diagrams:

exporter.setShowControlEquipment(false);

Detail Levels

Three detail levels are available:

CONCEPTUAL

ENGINEERING

DEBUG

API Reference

ProcessSystem Methods

// Quick DOT export
String dot = process.toDOT();

// DOT export with specific detail level
String dot = process.toDOT(DiagramDetailLevel.CONCEPTUAL);

// Full-featured exporter
ProcessDiagramExporter exporter = process.createDiagramExporter();

// Direct SVG/PNG export (requires Graphviz)
process.exportDiagramSVG(Path.of("diagram.svg"));
process.exportDiagramPNG(Path.of("diagram.png"));

ProcessDiagramExporter Configuration

ProcessDiagramExporter exporter = new ProcessDiagramExporter(process)
    .setTitle("My Process")
    .setDiagramStyle(DiagramStyle.HYSYS)  // NEQSIM, HYSYS, PROII, ASPEN_PLUS
    .setDetailLevel(DiagramDetailLevel.ENGINEERING)
    .setVerticalLayout(false)   // LR layout (left-to-right flow) - default
    .setUseClusters(true)       // Group equipment by role
    .setShowLegend(true)        // Include legend
    .setShowStreamValues(true)  // Show T/P/F on streams
    .setUseStreamTables(false)  // Use HTML tables (true) or text (false)
    .setHighlightRecycles(true) // Highlight recycle streams
    .setShowControlEquipment(true)  // Show/hide control equipment
    .setShowDexpiMetadata(true);    // Show DEXPI line numbers/fluid codes

// Export options
String dot = exporter.toDOT();
exporter.exportDOT(Path.of("diagram.dot"));
exporter.exportSVG(Path.of("diagram.svg"));  // Requires Graphviz
exporter.exportPNG(Path.of("diagram.png"));  // Requires Graphviz
exporter.exportPDF(Path.of("diagram.pdf"));  // Requires Graphviz

Rendering DOT Files

The DOT format can be rendered using Graphviz:

# Install Graphviz (if needed)
# Windows: choco install graphviz
# macOS: brew install graphviz
# Linux: apt-get install graphviz

# Render to SVG
dot -Tsvg process.dot -o process.svg

# Render to PNG
dot -Tpng process.dot -o process.png

# Render to PDF
dot -Tpdf process.dot -o process.pdf

Example: Gas Separation Process

// Create fluid
SystemInterface fluid = new SystemSrkEos(298.0, 50.0);
fluid.addComponent("methane", 0.7);
fluid.addComponent("ethane", 0.15);
fluid.addComponent("propane", 0.1);
fluid.addComponent("n-butane", 0.05);
fluid.setMixingRule("classic");

// Build process
ProcessSystem process = new ProcessSystem("Gas Separation");

Stream feed = new Stream("Well Fluid", fluid);
feed.setFlowRate(5000.0, "kg/hr");
feed.setTemperature(60.0, "C");
feed.setPressure(80.0, "bara");
process.add(feed);

Separator hpSep = new Separator("HP Separator", feed);
process.add(hpSep);

Compressor comp = new Compressor("Export Compressor", hpSep.getGasOutStream());
comp.setOutletPressure(120.0, "bara");
process.add(comp);

Cooler cooler = new Cooler("Gas Cooler", comp.getOutletStream());
cooler.setOutTemperature(40.0, "C");
process.add(cooler);

Pump pump = new Pump("Oil Pump", hpSep.getLiquidOutStream());
pump.setOutletPressure(60.0, "bara");
process.add(pump);

process.run();

// Export diagram
process.createDiagramExporter()
    .setTitle("Gas Separation Process")
    .setDetailLevel(DiagramDetailLevel.ENGINEERING)
    .exportSVG(Path.of("gas_separation.svg"));

This generates a professional PFD with:

Example: Three-Phase Separation

// Create three-phase fluid (gas, oil, water)
SystemInterface fluid = new SystemSrkEos(298.0, 50.0);
fluid.addComponent("methane", 0.5);
fluid.addComponent("n-heptane", 0.3);
fluid.addComponent("water", 0.2);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

// Build process
ProcessSystem process = new ProcessSystem("Production Separation");

Stream feed = new Stream("Well Fluid", fluid);
feed.setFlowRate(5000.0, "kg/hr");
feed.setTemperature(60.0, "C");
feed.setPressure(80.0, "bara");
process.add(feed);

// Three-phase separator
ThreePhaseSeparator separator = new ThreePhaseSeparator("Production Separator", feed);
process.add(separator);

// Gas compression
Compressor gasCompressor = new Compressor("Gas Compressor", separator.getGasOutStream());
gasCompressor.setOutletPressure(120.0, "bara");
process.add(gasCompressor);

// Oil export pump
Pump oilPump = new Pump("Oil Pump", separator.getOilOutStream());
oilPump.setOutletPressure(60.0, "bara");
process.add(oilPump);

// Produced water handling
Pump waterPump = new Pump("Water Pump", separator.getWaterOutStream());
waterPump.setOutletPressure(10.0, "bara");
process.add(waterPump);

process.run();

// Export diagram
process.createDiagramExporter()
    .setTitle("Production Separation")
    .setDetailLevel(DiagramDetailLevel.ENGINEERING)
    .exportSVG(Path.of("production_separation.svg"));

This generates a PFD where the three-phase separator shows:

Each stream is color-coded by phase type for easy identification.

Example: Compressor Anti-Surge System

// Create gas fluid
SystemInterface gas = new SystemSrkEos(298.0, 50.0);
gas.addComponent("methane", 0.9);
gas.addComponent("ethane", 0.1);
gas.setMixingRule("classic");

// Build process with recycle
ProcessSystem process = new ProcessSystem("Compressor Station");

Stream feed = new Stream("Feed Gas", gas);
feed.setFlowRate(1000.0, "kg/hr");
feed.setTemperature(25.0, "C");
feed.setPressure(50.0, "bara");
process.add(feed);

// Mixer for recycle
Mixer suctionMixer = new Mixer("Suction Mixer");
suctionMixer.addStream(feed);
process.add(suctionMixer);

// Main compressor
Compressor compressor = new Compressor("Main Compressor", suctionMixer.getOutletStream());
compressor.setOutletPressure(100.0, "bara");
process.add(compressor);

// Anti-surge splitter
Splitter splitter = new Splitter("Discharge Splitter", compressor.getOutletStream(), 2);
splitter.setSplitFactors(new double[] {0.9, 0.1});
process.add(splitter);

// Anti-surge recycle
Recycle recycle = new Recycle("Anti-Surge Recycle");
recycle.addStream(splitter.getSplitStream(1));
recycle.setOutletStream(suctionMixer.getOutletStream());
process.add(recycle);

process.run();

// Export with recycle highlighting
process.createDiagramExporter()
    .setTitle("Compressor Anti-Surge System")
    .setDetailLevel(DiagramDetailLevel.ENGINEERING)
    .setHighlightRecycles(true)    // Purple dashed lines for recycles
    .setShowStreamValues(true)
    .exportSVG(Path.of("anti_surge.svg"));

This generates a PFD with:

DEXPI Integration

The diagram system integrates with DEXPI (Data Exchange in the Process Industry) for importing P&ID data and generating PFD diagrams from industry-standard data exchange files.

One-Step Import and Diagram

// Import DEXPI XML and create pre-configured diagram exporter
ProcessDiagramExporter exporter = DexpiDiagramBridge.importAndCreateExporter(
    Paths.get("plant.xml"));

// DEXPI metadata (line numbers, fluid codes) shown in labels by default
exporter.exportDOT(Paths.get("diagram.dot"));
exporter.exportSVG(Paths.get("diagram.svg"));  // Requires Graphviz

Full Round-Trip Workflow

// Import DEXPI → run simulation → generate diagram → export enriched DEXPI
ProcessSystem system = DexpiDiagramBridge.roundTrip(
    Paths.get("input.xml"),     // Input DEXPI P&ID file
    Paths.get("diagram.dot"),   // Output diagram
    Paths.get("output.xml"));   // Re-exported DEXPI with simulation results

DEXPI Metadata in Labels

Equipment imported from DEXPI files displays P&ID reference information:

// Enable/disable DEXPI metadata display
exporter.setShowDexpiMetadata(true);

Creating Exporters for DEXPI-Imported Processes

// Standard exporter with DEXPI features enabled
ProcessDiagramExporter exporter = DexpiDiagramBridge.createExporter(system);

// Detailed exporter with full operating conditions
ProcessDiagramExporter detailed = DexpiDiagramBridge.createDetailedExporter(system);

See DEXPI XML Reader for complete DEXPI import/export documentation.

Customization

Custom Layout Policy

PFDLayoutPolicy customPolicy = new PFDLayoutPolicy();
// Policy automatically classifies equipment by type

ProcessDiagramExporter exporter = new ProcessDiagramExporter(process, customPolicy);

Equipment Visual Styles

Visual styles are defined in EquipmentVisualStyle with defaults for all common equipment types. The style includes:

Architecture

The diagram export system consists of:

  1. ProcessDiagramExporter - Main entry point for diagram generation
  2. PFDLayoutPolicy - Layout intelligence layer with gravity logic
  3. EquipmentRole - Classification of equipment by function
  4. DiagramDetailLevel - Control over information density
  5. EquipmentVisualStyle - Visual styling for equipment types (unified with EquipmentEnum)
  6. DexpiDiagramBridge - Integration bridge for DEXPI P&ID data exchange

Design Philosophy

Professional PFDs are not drawn — they are computed using rules.

The layout intelligence layer applies engineering conventions:

  1. Gravity logic (gas up, liquid down)
  2. Functional zoning (separation center, gas upper, liquid lower)
  3. Equipment semantics (separator outlets positioned correctly)
  4. Stable layout (same model → same diagram)

This approach produces diagrams that are:

DEXPI Architecture

PFD Diagram System: Architecture Alignment and DEXPI Synergy

1. Architectural Fit Assessment

1.1 Current NeqSim Process Model Architecture

neqsim.process
├── equipment/           # Individual unit operations (150+ classes)
│   ├── EquipmentEnum    # Canonical equipment type enumeration
│   └── ProcessEquipmentInterface
├── processmodel/
│   ├── ProcessSystem    # Main flowsheet container
│   ├── graph/           # Graph representation layer
│   │   ├── ProcessGraph       # DAG with cycle detection
│   │   ├── ProcessNode        # Equipment nodes
│   │   ├── ProcessEdge        # Stream edges
│   │   └── ProcessGraphBuilder
│   ├── diagram/         # PFD visualization layer
│   │   ├── ProcessDiagramExporter
│   │   ├── PFDLayoutPolicy
│   │   ├── EquipmentRole
│   │   ├── DiagramDetailLevel
│   │   └── EquipmentVisualStyle
│   ├── dexpi/           # DEXPI integration layer
│   │   ├── DexpiXmlReader
│   │   ├── DexpiXmlWriter
│   │   ├── DexpiProcessUnit
│   │   ├── DexpiStream
│   │   ├── DexpiMetadata
│   │   └── DexpiRoundTripProfile
│   └── lifecycle/       # Phase/state management
└── thermo/              # Thermodynamic models

1.2 Diagram System Integration Points

The PFD diagram system integrates cleanly at three architectural levels:

Layer Integration Point Relationship
ProcessSystem toDOT(), createDiagramExporter() Facade methods for convenience
ProcessGraph ProcessGraphBuilder.build() Diagram uses graph for topology
Equipment ProcessEquipmentInterface Visual styles keyed by class name

Key Design Decisions:

  1. Graph-based rendering - Diagram exports use ProcessGraph, not raw equipment lists
  2. Separation of concerns - Layout policy separate from rendering
  3. Deterministic output - Same process → same diagram (no random placement)
  4. Serializable - All diagram classes implement Serializable

1.3 Alignment with NeqSim Principles

Principle Diagram System Compliance
Equipment extends ProcessEquipmentBaseClass ✓ Uses interfaces only, no tight coupling
Mixing rules before init ✓ Works on run() output, not during setup
Cloning with system.clone() ✓ No shared mutable state
Java 8 compatibility ✓ No var, no List.of(), streams used correctly
Serialization support ✓ All classes serializable
Package boundaries ✓ Located in processmodel.diagram

2. DEXPI Synergy Analysis

2.1 Current DEXPI Capabilities

NeqSim already has comprehensive DEXPI support:

Component Purpose
DexpiXmlReader Import DEXPI P&ID XML → ProcessSystem
DexpiXmlWriter Export ProcessSystem → DEXPI XML
DexpiProcessUnit Lightweight placeholder for imported equipment
DexpiStream Runnable stream with DEXPI metadata
DexpiMetadata Shared constants (tag names, line numbers, etc.)
DexpiRoundTripProfile Validation for round-trip fidelity
dexpi_equipment_mapping.properties DEXPI class → EquipmentEnum mapping

2.2 Synergy Opportunities

2.2.1 Shared Equipment Type Registry

Current State:

Opportunity: Unify equipment type handling through EquipmentEnum:

// EquipmentVisualStyle could use EquipmentEnum as key
public static EquipmentVisualStyle getStyle(EquipmentEnum type) {
    return STYLE_CACHE.get(type);
}

// DexpiProcessUnit already has getMappedEquipment() → EquipmentEnum
DexpiProcessUnit unit = ...;
EquipmentVisualStyle style = EquipmentVisualStyle.getStyle(unit.getMappedEquipment());

2.2.2 DEXPI-Aware Diagram Export

Current State:

Opportunity: Add DEXPI-aware export options:

public class ProcessDiagramExporter {
    // Export to DEXPI XML with embedded layout hints
    public void exportDexpiWithLayout(Path path) throws IOException {
        // 1. Generate ProcessGraph with layout
        // 2. Add layout coordinates as GenericAttributes
        // 3. Write via DexpiXmlWriter with coordinates
    }

    // Import DEXPI and preserve P&ID layout
    public static ProcessDiagramExporter fromDexpi(Path dexpiXml) {
        ProcessSystem system = DexpiXmlReader.read(dexpiXml, template);
        return new ProcessDiagramExporter(system)
            .preserveDexpiLayout(true);  // Use DEXPI positions if available
    }
}

2.2.3 Metadata Preservation

Current State:

Opportunity: Enrich diagram labels with DEXPI metadata:

private String buildNodeLabel(ProcessEquipmentInterface equipment) {
    StringBuilder label = new StringBuilder(equipment.getName());

    if (equipment instanceof DexpiProcessUnit) {
        DexpiProcessUnit dexpi = (DexpiProcessUnit) equipment;
        if (dexpi.getLineNumber() != null) {
            label.append("\\nLine: ").append(dexpi.getLineNumber());
        }
        if (dexpi.getFluidCode() != null) {
            label.append("\\nFluid: ").append(dexpi.getFluidCode());
        }
    }
    return label.toString();
}

2.2.4 Symbol Standardization (ISO 10628 / DEXPI)

Current State:

Opportunity: Map to ISO 10628 symbol classes:

public enum EquipmentSymbol {
    // ISO 10628-2 Section 5: Process equipment
    VESSEL(5.1, "cylinder", "#90EE90"),
    COLUMN(5.2, "cylinder", "#90EE90"),
    HEAT_EXCHANGER(5.3, "rectangle", "#FFD700"),

    // ISO 10628-2 Section 6: Piping components
    VALVE(6.1, "diamond", "#FFB6C1"),
    PUMP(6.2, "circle", "#4169E1"),
    COMPRESSOR(6.3, "parallelogram", "#87CEEB");

    private final String isoSection;
    private final String graphvizShape;
    private final String defaultColor;
}

2.3 Implementation Roadmap

Phase 1: Type Registry Unification (Low effort, high impact)

  1. Refactor EquipmentVisualStyle to accept EquipmentEnum as primary key
  2. Add fallback to class name for custom equipment
  3. Ensure DEXPI imports render with correct visual styles
// Before
EquipmentVisualStyle style = EquipmentVisualStyle.getStyle("Separator");

// After
EquipmentVisualStyle style = EquipmentVisualStyle.getStyle(EquipmentEnum.Separator);
// or
EquipmentVisualStyle style = EquipmentVisualStyle.getStyle(unit.getMappedEquipment());

Phase 2: DEXPI Metadata in Diagrams (Medium effort)

  1. Detect DexpiProcessUnit and DexpiStream instances
  2. Extract and display DEXPI metadata (tag names, line numbers)
  3. Add legend entry for DEXPI-originated equipment

Phase 3: Bidirectional DEXPI-Diagram Integration (Higher effort)

  1. Add layout coordinates to DEXPI export via GenericAttributes
  2. Parse layout hints from DEXPI imports
  3. Round-trip: DEXPI → NeqSim → PFD → DEXPI with layout preserved

3. Architectural Recommendations

3.1 Keep Current Structure

The diagram/ package is correctly positioned:

3.2 Create Shared Equipment Type Service

package neqsim.process.equipment;

/**
 * Unified equipment type resolution service.
 */
public final class EquipmentTypeResolver {

    /**
     * Resolves equipment to canonical EquipmentEnum.
     */
    public static EquipmentEnum resolve(ProcessEquipmentInterface equipment) {
        if (equipment instanceof DexpiProcessUnit) {
            return ((DexpiProcessUnit) equipment).getMappedEquipment();
        }
        // Fall back to class name mapping
        String className = equipment.getClass().getSimpleName();
        return EquipmentEnum.valueOf(className);
    }

    /**
     * Resolves DEXPI class name to EquipmentEnum using mapping file.
     */
    public static EquipmentEnum resolveFromDexpi(String dexpiClassName) {
        // Load from dexpi_equipment_mapping.properties
    }
}

3.3 Add DEXPI-Diagram Bridge Class

package neqsim.process.processmodel.diagram;

/**
 * Bridge between DEXPI metadata and PFD visualization.
 */
public class DexpiDiagramBridge {

    /**
     * Creates diagram exporter optimized for DEXPI-imported processes.
     */
    public static ProcessDiagramExporter createExporter(ProcessSystem system) {
        return new ProcessDiagramExporter(system)
            .setShowDexpiMetadata(true)
            .setPreserveDexpiLayout(true);
    }

    /**
     * Exports ProcessSystem to DEXPI XML with embedded layout coordinates.
     */
    public static void exportWithLayout(ProcessSystem system, Path output) {
        // Calculate layout via ProcessDiagramExporter
        // Inject coordinates as GenericAttributes
        // Write via DexpiXmlWriter
    }
}

4. Summary

Architectural Fit: ✅ Excellent

The PFD diagram system integrates cleanly:

DEXPI Synergy: ✅ Implemented

Synergy Area Status Implementation
EquipmentEnum unification ✅ Complete EquipmentVisualStyle.getStyle(EquipmentEnum)
DEXPI metadata in labels ✅ Complete appendDexpiMetadata(), setShowDexpiMetadata()
DexpiDiagramBridge ✅ Complete DexpiDiagramBridge class with round-trip support
ISO 10628 symbol mapping ⏳ Future Planned for P&ID compliance

Available Features

  1. EquipmentVisualStyle.getStyle(EquipmentEnum) - Unified styling via canonical enum
  2. EquipmentVisualStyle.getStyleForEquipment(equipment) - Auto-detects DEXPI units
  3. ProcessDiagramExporter.setShowDexpiMetadata(true) - Display line numbers/fluid codes
  4. DexpiDiagramBridge.createExporter(system) - Pre-configured DEXPI-aware exporter
  5. DexpiDiagramBridge.importAndCreateExporter(path) - One-step DEXPI → diagram
  6. DexpiDiagramBridge.roundTrip(input, dotOutput, dexpiOutput) - Full import/simulate/export

Chapter 13: Streams & Mixers

Streams

Streams

Comprehensive documentation for process streams in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.stream

Streams are the fundamental connections between process equipment in NeqSim, carrying material and energy through process flowsheets. They encapsulate thermodynamic fluid systems with flow conditions and provide methods for flash calculations, property retrieval, and gas quality analysis.

Class Hierarchy

ProcessEquipmentBaseClass
    └── Stream (implements StreamInterface)
            └── NeqStream

ProcessEquipmentBaseClass
    └── VirtualStream

java.io.Serializable
    └── EnergyStream

Available Classes

Class Description Use Case
Stream Standard process stream with full thermodynamic calculations General material flows
StreamInterface Interface defining stream contract Type declarations and polymorphism
NeqStream Stream without flash (uses existing phase split) When phase equilibrium is known
VirtualStream Reference stream with property overrides Branch flows, what-if scenarios
EnergyStream Heat/work duty carrier Heat exchanger duties, compressor work

Stream Architecture

Internal Structure

A Stream contains:

Object Ownership

// IMPORTANT: Stream uses the fluid object directly (not cloned)
SystemInterface fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 1.0);

// The stream references the same fluid object
Stream stream = new Stream("Feed", fluid);

// To create independent streams, clone explicitly
Stream independent = new Stream("Independent", fluid.clone());

Linked Streams

Streams can reference other streams:

// Source stream
Stream source = new Stream("Source", fluid);
source.run();

// Linked stream (shares fluid with source)
Stream linked = new Stream("Linked", source);

// When source changes, linked sees the changes after run()
source.setTemperature(350.0, "K");
source.run();
linked.run();  // Uses updated source properties

Stream Class

Constructors

import neqsim.thermo.system.SystemSrkEos;
import neqsim.process.equipment.stream.Stream;

// 1. Create with name only (fluid set later)
Stream emptyStream = new Stream("Empty");

// 2. Create from fluid system (uses fluid directly, not cloned)
SystemSrkEos fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.90);
fluid.addComponent("ethane", 0.07);
fluid.addComponent("propane", 0.03);
fluid.setMixingRule("classic");
Stream feedStream = new Stream("Feed", fluid);

// 3. Create from another stream (linked reference)
Stream linkedStream = new Stream("Linked", feedStream);

Setting Process Conditions

// Temperature (various units)
feed.setTemperature(300.0, "K");        // Kelvin
feed.setTemperature(25.0, "C");         // Celsius
feed.setTemperature(77.0, "F");         // Fahrenheit

// Pressure (various units)
feed.setPressure(50.0, "bara");         // Bar absolute
feed.setPressure(725.0, "psia");        // PSI absolute
feed.setPressure(5.0, "MPa");           // Megapascal

// Flow rate (various units)
feed.setFlowRate(10000.0, "kg/hr");     // Mass flow
feed.setFlowRate(500.0, "kmol/hr");     // Molar flow
feed.setFlowRate(1000000.0, "Sm3/day"); // Standard volume (gas)
feed.setFlowRate(100.0, "m3/hr");       // Actual volume

// IMPORTANT: Always run() after setting conditions
feed.run();

Setting Fluid/Composition

// Replace entire fluid
feed.setFluid(newFluidSystem);
feed.setThermoSystem(newFluidSystem);

// Set from specific phase of another system
feed.setThermoSystemFromPhase(otherSystem, "gas");      // Gas phase only
feed.setThermoSystemFromPhase(otherSystem, "oil");      // Oil phase only
feed.setThermoSystemFromPhase(otherSystem, "aqueous");  // Water phase only
feed.setThermoSystemFromPhase(otherSystem, "liquid");   // All liquid phases

// Create empty stream from template
feed.setEmptyThermoSystem(templateSystem);

Stream Specifications

The stream specification controls how flash calculations are performed.

Available Specifications

Specification Description When to Use
"TP" Temperature-Pressure flash (default) Standard conditions
"PH" Pressure-Enthalpy flash After isenthalpic processes
"dewP" Dew point temperature at given P Condensation studies
"dewT" Dew point pressure at given T Dew point analysis
"bubP" Bubble point temperature at given P Evaporation studies
"bubT" Bubble point pressure at given T Bubble point analysis
"gas quality" Constant phase fraction flash Fixed vapor fraction

Using Specifications

// Default TP flash
Stream stream = new Stream("Process", fluid);
stream.run();  // Performs TP flash

// Dew point calculation
stream.setSpecification("dewP");
stream.run();  // Calculates dew point temperature at current pressure

// Bubble point calculation
stream.setSpecification("bubP");
stream.run();  // Calculates bubble point temperature at current pressure

// Gas quality specification
stream.setSpecification("gas quality");
stream.setGasQuality(0.5);  // 50% vapor fraction
stream.run();  // Calculates temperature for specified vapor fraction

Stream Properties

Thermodynamic Properties

stream.run();  // Ensure stream is calculated

// Temperature
double tempK = stream.getTemperature();          // Kelvin (default)
double tempC = stream.getTemperature("C");       // Celsius
double tempF = stream.getTemperature("F");       // Fahrenheit

// Pressure
double pressPa = stream.getPressure();           // Pascal (default)
double pressBara = stream.getPressure("bara");   // Bar absolute
double pressPsia = stream.getPressure("psia");   // PSI absolute

// Flow rates
double massFlow = stream.getFlowRate("kg/hr");
double molarFlow = stream.getFlowRate("kmol/hr");
double molarRate = stream.getMolarRate();        // Total moles
double volFlow = stream.getFlowRate("m3/hr");
double stdVolFlow = stream.getFlowRate("Sm3/day");

Flow Rate Unit Reference

Unit Description Basis
"kg/sec" Kilograms per second Mass
"kg/min" Kilograms per minute Mass
"kg/hr" Kilograms per hour Mass
"kg/day" Kilograms per day Mass
"kmol/hr" Kilomoles per hour Molar
"mole/sec" Moles per second Molar
"mole/min" Moles per minute Molar
"mole/hr" Moles per hour Molar
"m3/sec" Actual m³/second Volume
"m3/min" Actual m³/minute Volume
"m3/hr" Actual m³/hour Volume
"Sm3/sec" Standard m³/second Std Volume
"Sm3/hr" Standard m³/hour Std Volume
"Sm3/day" Standard m³/day Std Volume
"MSm3/day" Million Sm³/day Std Volume
"barrel/day" Oil barrels/day Volume

Accessing the Fluid System

// Get fluid object for detailed properties
SystemInterface fluid = stream.getFluid();
// or equivalently:
SystemInterface fluid = stream.getThermoSystem();

// Molecular weight
double mw = fluid.getMolarMass("kg/kmol");

// Enthalpy
double enthalpy = fluid.getEnthalpy("kJ/kg");

// Entropy
double entropy = fluid.getEntropy("kJ/kgK");

// Density
double density = fluid.getDensity("kg/m3");

// Composition
double[] moleFractions = fluid.getMolarComposition();
double methaneFrac = fluid.getComponent("methane").getz();

Phase Handling

Checking Phase Presence

SystemInterface fluid = stream.getFluid();

// Check for specific phases
boolean hasGas = fluid.hasPhaseType("gas");
boolean hasOil = fluid.hasPhaseType("oil");
boolean hasAqueous = fluid.hasPhaseType("aqueous");

// Number of phases
int numPhases = fluid.getNumberOfPhases();

// Phase mole fractions (beta)
double gasFraction = fluid.getPhase("gas").getBeta();  // Mole basis

Accessing Phase Properties

if (fluid.hasPhaseType("gas")) {
    PhaseInterface gasPhase = fluid.getPhase("gas");

    // Phase properties
    double gasDensity = gasPhase.getDensity("kg/m3");
    double gasViscosity = gasPhase.getViscosity("cP");
    double gasMW = gasPhase.getMolarMass("kg/kmol");
    double gasZ = gasPhase.getZ();  // Compressibility factor

    // Component in phase
    double methaneInGas = gasPhase.getComponent("methane").getx();
}

if (fluid.hasPhaseType("oil")) {
    PhaseInterface oilPhase = fluid.getPhase("oil");
    double oilDensity = oilPhase.getDensity("kg/m3");
    double oilViscosity = oilPhase.getViscosity("cP");
}

Creating Streams from Phases

// From separator outlet
Separator separator = new Separator("Sep", feed);
separator.run();

// Gas outlet
Stream gasOut = new Stream("Gas Out");
gasOut.setThermoSystemFromPhase(separator.getFluid(), "gas");
gasOut.run();

// Oil outlet
Stream oilOut = new Stream("Oil Out");
oilOut.setThermoSystemFromPhase(separator.getFluid(), "oil");
oilOut.run();

// All liquids combined
Stream liquidOut = new Stream("Liquid Out");
liquidOut.setThermoSystemFromPhase(separator.getFluid(), "liquid");
liquidOut.run();

Gas Quality Properties

NeqSim provides comprehensive gas quality calculations per ISO 6976 and other standards.

Calorific Values

// Gross Calorific Value (Higher Heating Value)
double gcv = stream.GCV();  // kJ/Sm³ at 0°C, 15.55°C combustion

// GCV with specified reference conditions
double gcvCustom = stream.getGCV("volume", 15.0, 15.0);  // refT=15°C, combT=15°C

// Net Calorific Value (Lower Heating Value)
double lcv = stream.LCV();  // kJ/Sm³

Wobbe Index

// Wobbe Index (gas interchangeability measure)
double wi = stream.getWI("volume", 15.0, 15.0);  // kJ/Sm³

ISO 6976 Standard Calculations

// Get full ISO 6976 results
Standard_ISO6976 iso = stream.getISO6976("volume", 15.0, 15.0);
iso.calculate();

double gcv = iso.getValue("SuperiorCalorificValue");
double lcv = iso.getValue("InferiorCalorificValue");
double wobbe = iso.getValue("SuperiorWobbeIndex");
double relDensity = iso.getValue("RelativeDensity");
double compressibility = iso.getValue("CompressionFactor");

Dew Points

// Hydrocarbon dew point at specified pressure
double hcDewPoint = stream.getHydrocarbonDewPoint("C", 70.0, "bara");

// Hydrate equilibrium temperature
double hydrateTemp = stream.getHydrateEquilibriumTemperature();  // K

// Solid formation temperature
double freezeTemp = stream.getSolidFormationTemperature("wax");

Phase Envelope Points

// Cricondentherm (maximum temperature for two-phase)
double cctTemp = stream.CCT("C");      // Temperature
double cctPres = stream.CCT("bara");   // Pressure at CCT

// Cricondenbar (maximum pressure for two-phase)
double ccbTemp = stream.CCB("C");      // Temperature at CCB
double ccbPres = stream.CCB("bara");   // Pressure

// Phase envelope visualization
stream.phaseEnvelope();  // Opens plot window

Vapor Pressure

// True Vapor Pressure at reference temperature
double tvp = stream.TVP(37.8, "C");  // bara at 100°F
double tvpPsia = stream.getTVP(37.8, "C", "psia");

// Reid Vapor Pressure (ASTM D6377)
double rvp = stream.getRVP(37.8, "C", "psia");
double rvpMethod = stream.getRVP(37.8, "C", "psia", "VPCR4");

Virtual Streams

VirtualStream creates a modified copy of a reference stream with overridden properties.

Purpose

Usage

import neqsim.process.equipment.stream.VirtualStream;

// Reference stream
Stream mainFlow = new Stream("Main", fluid);
mainFlow.setFlowRate(10000.0, "kg/hr");
mainFlow.run();

// Virtual stream with modified flow
VirtualStream branch = new VirtualStream("Branch", mainFlow);
branch.setFlowRate(2000.0, "kg/hr");  // Override flow
branch.run();

// Virtual stream with modified conditions
VirtualStream heated = new VirtualStream("Heated", mainFlow);
heated.setTemperature(350.0, "K");    // Override temperature
heated.setFlowRate(3000.0, "kg/hr");  // Override flow
heated.run();

// Virtual stream with modified composition
VirtualStream altered = new VirtualStream("Altered", mainFlow);
double[] newComp = {0.95, 0.03, 0.02};  // New mole fractions
altered.setComposition(newComp, "mole");
altered.run();

// Get output stream from virtual
StreamInterface outputStream = altered.getOutletStream();

Key Methods

Method Description
setReferenceStream(stream) Set the source stream
setFlowRate(value, unit) Override flow rate
setTemperature(value, unit) Override temperature
setPressure(value, unit) Override pressure
setComposition(array, unit) Override composition
getOutletStream() Get the modified stream

NeqStream

NeqStream is a specialized stream that skips flash calculations, using the existing phase distribution.

When to Use

Behavior Difference

// Standard Stream: performs TP flash
Stream standard = new Stream("Standard", fluid);
standard.run();  // Calculates new phase equilibrium

// NeqStream: uses existing phases, just initializes properties
NeqStream neq = new NeqStream("NeqStream", fluid);
neq.run();  // Skips flash, uses existing x, y, beta

Usage Example

import neqsim.process.equipment.stream.NeqStream;

// After separator has calculated phases
Separator sep = new Separator("Sep", feed);
sep.run();

// Use NeqStream to preserve exact phase split
NeqStream gasStream = new NeqStream("Gas", sep.getGasOutStream());
gasStream.run();  // No reflash, preserves separator results

Energy Streams

EnergyStream carries heat or work duty between equipment.

Creating Energy Streams

import neqsim.process.equipment.stream.EnergyStream;

// Create energy stream
EnergyStream heatDuty = new EnergyStream("Heater Duty");
heatDuty.setDuty(1000000.0);  // Watts

// Get duty
double duty = heatDuty.getDuty();  // Watts

Connecting to Equipment

// Heater with energy stream
Heater heater = new Heater("Heater", feed);
heater.setOutletTemperature(350.0, "K");
heater.run();

// Energy stream gets duty from heater
EnergyStream heaterPower = new EnergyStream("Heater Power");
heaterPower.setDuty(heater.getDuty());

// Connect to heat source
HeatExchanger hx = new HeatExchanger("HX");
hx.setEnergyStream(heaterPower);

Cloning and State Management

Cloning Streams

// Clone with same name (returns copy)
Stream original = new Stream("Feed", fluid);
original.run();

Stream copy = original.clone();

// Clone with new name
Stream namedCopy = original.clone("Feed Copy");

// Clones are independent
copy.setFlowRate(500.0, "kg/hr");
copy.run();
// Original unchanged

State Caching

Streams cache their last calculated state for optimization:

// Check if recalculation is needed
if (stream.needRecalculation()) {
    stream.run();  // Conditions changed, recalculate
}

// Cached values used internally:
// - lastTemperature
// - lastPressure
// - lastFlowRate
// - lastComposition

Transient Operations

Streams support dynamic simulation with controller integration.

Running Transient

// Time step in seconds
double dt = 1.0;
UUID calcId = UUID.randomUUID();

// Run transient step
stream.runTransient(dt, calcId);

// Increase simulation time
stream.increaseTime(dt);

With Flow Controller

// Attach controller
ControllerDeviceInterface controller = new PIDController();
controller.setControllerSetPoint(1000.0);  // kg/hr target
stream.setController(controller);

// Transient run adjusts flow via controller
for (int i = 0; i < 100; i++) {
    stream.runTransient(1.0, UUID.randomUUID());
}

Minimum Flow Handling

// Streams below minimum flow are deactivated
if (stream.getFlowRate("kg/hr") < stream.getMinimumFlow()) {
    // Stream runs but marks as inactive
    stream.isActive();  // Returns false
}

Examples

Example 1: Complete Natural Gas Feed Setup

import neqsim.thermo.system.SystemSrkEos;
import neqsim.process.equipment.stream.Stream;

// Natural gas composition
SystemSrkEos gas = new SystemSrkEos(298.15, 70.0);
gas.addComponent("nitrogen", 0.02);
gas.addComponent("CO2", 0.01);
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.06);
gas.addComponent("propane", 0.03);
gas.addComponent("i-butane", 0.01);
gas.addComponent("n-butane", 0.015);
gas.addComponent("i-pentane", 0.005);
gas.setMixingRule("classic");

Stream feed = new Stream("Natural Gas Feed", gas);
feed.setFlowRate(10.0, "MSm3/day");  // 10 million Sm³/day
feed.run();

// Report properties
System.out.println("=== Feed Stream Properties ===");
System.out.println("Temperature: " + feed.getTemperature("C") + " °C");
System.out.println("Pressure: " + feed.getPressure("bara") + " bara");
System.out.println("Mass flow: " + feed.getFlowRate("kg/hr") + " kg/hr");
System.out.println("Molar flow: " + feed.getFlowRate("kmol/hr") + " kmol/hr");
System.out.println("Density: " + feed.getFluid().getDensity("kg/m3") + " kg/m³");
System.out.println("MW: " + feed.getFluid().getMolarMass("kg/kmol") + " kg/kmol");

// Gas quality
System.out.println("\n=== Gas Quality ===");
System.out.println("GCV: " + feed.GCV() / 1000.0 + " MJ/Sm³");
System.out.println("LCV: " + feed.LCV() / 1000.0 + " MJ/Sm³");
System.out.println("Wobbe Index: " + feed.getWI("volume", 15.0, 15.0) / 1000.0 + " MJ/Sm³");
System.out.println("HC Dew Point: " + feed.getHydrocarbonDewPoint("C", 70.0, "bara") + " °C");

Example 2: Two-Phase Stream Analysis

// Wellhead mixture
SystemSrkEos wellfluid = new SystemSrkEos(350.0, 150.0);
wellfluid.addComponent("methane", 0.60);
wellfluid.addComponent("ethane", 0.08);
wellfluid.addComponent("propane", 0.05);
wellfluid.addComponent("n-hexane", 0.12);
wellfluid.addComponent("n-decane", 0.10);
wellfluid.addComponent("water", 0.05);
wellfluid.setMixingRule("classic");

Stream wellStream = new Stream("Well Stream", wellfluid);
wellStream.setFlowRate(50000.0, "kg/hr");
wellStream.run();

// Phase analysis
SystemInterface fluid = wellStream.getFluid();
System.out.println("Number of phases: " + fluid.getNumberOfPhases());

if (fluid.hasPhaseType("gas")) {
    double gasRate = fluid.getPhase("gas").getBeta() 
                   * wellStream.getFlowRate("kg/hr");
    System.out.println("Gas rate: " + gasRate + " kg/hr");
    System.out.println("Gas density: " + fluid.getPhase("gas").getDensity("kg/m3") + " kg/m³");
}

if (fluid.hasPhaseType("oil")) {
    double oilRate = fluid.getPhase("oil").getBeta() 
                   * wellStream.getFlowRate("kg/hr");
    System.out.println("Oil rate: " + oilRate + " kg/hr");
    System.out.println("Oil API: " + fluid.getPhase("oil").getPhysicalProperties()
                       .getValue("API_gravity"));
}

if (fluid.hasPhaseType("aqueous")) {
    double waterRate = fluid.getPhase("aqueous").getBeta() 
                     * wellStream.getFlowRate("kg/hr");
    System.out.println("Water rate: " + waterRate + " kg/hr");
}

Example 3: Branch Flows with VirtualStream

// Main pipeline flow
Stream pipeline = new Stream("Pipeline", gas);
pipeline.setFlowRate(100000.0, "kg/hr");
pipeline.run();

// Customer branches (each takes portion of main flow)
VirtualStream customer1 = new VirtualStream("Customer 1", pipeline);
customer1.setFlowRate(30000.0, "kg/hr");
customer1.run();

VirtualStream customer2 = new VirtualStream("Customer 2", pipeline);
customer2.setFlowRate(25000.0, "kg/hr");
customer2.setTemperature(280.0, "K");  // Heated for customer 2
customer2.run();

VirtualStream customer3 = new VirtualStream("Customer 3", pipeline);
customer3.setFlowRate(45000.0, "kg/hr");
customer3.setPressure(40.0, "bara");  // Reduced pressure
customer3.run();

// Verify mass balance
double totalOut = customer1.getOutletStream().getFlowRate("kg/hr")
                + customer2.getOutletStream().getFlowRate("kg/hr")
                + customer3.getOutletStream().getFlowRate("kg/hr");
System.out.println("Pipeline in: " + pipeline.getFlowRate("kg/hr") + " kg/hr");
System.out.println("Total out: " + totalOut + " kg/hr");

Example 4: Dew Point Specification

// Gas stream
Stream gasStream = new Stream("Export Gas", gas);
gasStream.setPressure(70.0, "bara");
gasStream.setFlowRate(5000.0, "kmol/hr");

// Calculate dew point temperature
gasStream.setSpecification("dewP");
gasStream.run();
System.out.println("Dew point at 70 bara: " + gasStream.getTemperature("C") + " °C");

// Calculate bubble point
gasStream.setSpecification("bubP");
gasStream.run();
System.out.println("Bubble point at 70 bara: " + gasStream.getTemperature("C") + " °C");

// Return to normal operation
gasStream.setSpecification("TP");
gasStream.setTemperature(25.0, "C");
gasStream.run();

Example 5: Stream Cloning for Parallel Paths

// Feed stream
Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(10000.0, "kg/hr");
feed.run();

// Clone for train A (50%)
Stream trainA = feed.clone("Train A Feed");
trainA.setFlowRate(5000.0, "kg/hr");
trainA.run();

// Clone for train B (50%)
Stream trainB = feed.clone("Train B Feed");
trainB.setFlowRate(5000.0, "kg/hr");
trainB.run();

// Process independently
Heater heaterA = new Heater("Heater A", trainA);
heaterA.setOutletTemperature(400.0, "K");
heaterA.run();

Heater heaterB = new Heater("Heater B", trainB);
heaterB.setOutletTemperature(380.0, "K");  // Different setpoint
heaterB.run();

System.out.println("Train A outlet T: " + heaterA.getOutletStream().getTemperature("C") + " °C");
System.out.println("Train B outlet T: " + heaterB.getOutletStream().getTemperature("C") + " °C");

Example 6: Stream Reporting

// Get formatted report
ArrayList<String[]> report = stream.getReport();
for (String[] row : report) {
    System.out.println(String.join(" | ", row));
}

// JSON output
String json = stream.toJson();
System.out.println(json);

// Result table
String[][] results = stream.getResultTable();
for (String[] row : results) {
    System.out.println(String.join("\t", row));
}

// Display in NeqSim GUI
stream.displayResult();

Advanced Topics

Flash Type Auto-Selection

For single-component systems from other streams, the stream automatically switches to PH flash to handle phase changes correctly:

// Single component from separator
if (stream != null && thermoSystem.getNumberOfComponents() == 1 
    && getSpecification().equals("TP")) {
    setSpecification("PH");  // Auto-switch for stability
}

Recalculation Optimization

Streams track their last state to avoid unnecessary calculations:

// Implementation checks cached values
if (temperature == lastTemperature 
    && pressure == lastPressure
    && flowRate == lastFlowRate
    && composition == lastComposition) {
    return false;  // No recalculation needed
}

Serialization

Streams are fully serializable for persistence:

// Save process state
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("process.dat"));
out.writeObject(stream);
out.close();

// Restore process state
ObjectInputStream in = new ObjectInputStream(new FileInputStream("process.dat"));
Stream restored = (Stream) in.readObject();
in.close();

Integration with ProcessSystem

// Add streams to process system
ProcessSystem process = new ProcessSystem();

Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(1000.0, "kg/hr");

Heater heater = new Heater("Heater", feed);
heater.setOutletTemperature(350.0, "K");

Separator sep = new Separator("Separator", heater.getOutletStream());

process.add(feed);
process.add(heater);
process.add(sep);

// Run entire process
process.run();

// Access any stream
StreamInterface processedFeed = process.getMeasurementDevice("Feed");

Mixers/Splitters

Mixers and Splitters

Documentation for stream mixing and splitting equipment in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.mixer, neqsim.process.equipment.splitter

Classes:

Class Description
Mixer Combine multiple streams
MixerInterface Mixer interface
Splitter Split stream into fractions
SplitterInterface Splitter interface
StaticMixer Static mixing element

Mixer

Combine multiple streams into one outlet stream.

Basic Usage

import neqsim.process.equipment.mixer.Mixer;

Mixer mixer = new Mixer("M-100");
mixer.addStream(stream1);
mixer.addStream(stream2);
mixer.addStream(stream3);
mixer.run();

Stream mixed = mixer.getOutletStream();

Mixing Calculation

The mixer performs mass and energy balance:

$$\dot{m}_{out} = \sum_i \dot{m}_i$$

$$\dot{m}_{out} \cdot h_{out} = \sum_i \dot{m}_i \cdot h_i$$

$$x_{j,out} = \frac{\sum_i \dot{m}_i \cdot x_{j,i}}{\sum_i \dot{m}_i}$$

Pressure Handling

// Default: outlet pressure = minimum inlet pressure
mixer.run();

// Or specify outlet pressure
mixer.setOutletPressure(20.0, "bara");
mixer.run();

Splitter

Split a stream into multiple fractions.

Basic Usage

import neqsim.process.equipment.splitter.Splitter;

// Split into 2 streams
Splitter splitter = new Splitter("SP-100", inletStream, 2);
splitter.setSplitFactors(new double[]{0.7, 0.3});  // 70% and 30%
splitter.run();

Stream split1 = splitter.getSplitStream(0);  // 70%
Stream split2 = splitter.getSplitStream(1);  // 30%

Split Factor Specification

// By mass fractions (must sum to 1.0)
splitter.setSplitFactors(new double[]{0.5, 0.3, 0.2});

// By flow rates
splitter.setFlowRates(new double[]{100.0, 60.0, 40.0}, "kg/hr");

Properties

All split streams have identical:

Only flow rate differs.


Static Mixer

For inline mixing with pressure drop.

Usage

import neqsim.process.equipment.mixer.StaticMixer;

StaticMixer staticMixer = new StaticMixer("Static Mixer");
staticMixer.addStream(stream1);
staticMixer.addStream(stream2);
staticMixer.setPressureDrop(0.5, "bara");
staticMixer.run();

Examples

Example 1: Simple Stream Mixing

import neqsim.thermo.system.SystemSrkEos;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.mixer.Mixer;

// Stream 1: Rich gas
SystemSrkEos gas1 = new SystemSrkEos(300.0, 50.0);
gas1.addComponent("methane", 0.80);
gas1.addComponent("ethane", 0.15);
gas1.addComponent("propane", 0.05);
gas1.setMixingRule("classic");

Stream stream1 = new Stream("Rich Gas", gas1);
stream1.setFlowRate(5000.0, "kg/hr");
stream1.run();

// Stream 2: Lean gas
SystemSrkEos gas2 = new SystemSrkEos(310.0, 50.0);
gas2.addComponent("methane", 0.95);
gas2.addComponent("ethane", 0.04);
gas2.addComponent("propane", 0.01);
gas2.setMixingRule("classic");

Stream stream2 = new Stream("Lean Gas", gas2);
stream2.setFlowRate(3000.0, "kg/hr");
stream2.run();

// Mix streams
Mixer mixer = new Mixer("M-100");
mixer.addStream(stream1);
mixer.addStream(stream2);
mixer.run();

// Results
Stream mixed = mixer.getOutletStream();
System.out.println("Mixed flow: " + mixed.getFlowRate("kg/hr") + " kg/hr");
System.out.println("Mixed temp: " + mixed.getTemperature("C") + " C");
System.out.println("Methane: " + mixed.getFluid().getMoleFraction("methane"));

Example 2: Recycle Split

// Main process stream
Stream processStream = new Stream("Process", processFluid);
processStream.setFlowRate(10000.0, "kg/hr");
processStream.run();

// Split: 90% product, 10% recycle
Splitter splitter = new Splitter("Recycle Splitter", processStream, 2);
splitter.setSplitFactors(new double[]{0.90, 0.10});
splitter.run();

Stream product = splitter.getSplitStream(0);
Stream recycle = splitter.getSplitStream(1);

System.out.println("Product: " + product.getFlowRate("kg/hr") + " kg/hr");
System.out.println("Recycle: " + recycle.getFlowRate("kg/hr") + " kg/hr");

Example 3: Multiple Stream Manifold

// Create manifold mixer for 4 wells
Mixer manifold = new Mixer("Production Manifold");

for (int i = 1; i <= 4; i++) {
    SystemSrkEos wellFluid = new SystemSrkEos(350.0, 100.0 - i * 5);
    wellFluid.addComponent("methane", 0.85);
    wellFluid.addComponent("ethane", 0.08);
    wellFluid.addComponent("propane", 0.05);
    wellFluid.addComponent("water", 0.02);
    wellFluid.setMixingRule("classic");

    Stream wellStream = new Stream("Well " + i, wellFluid);
    wellStream.setFlowRate(1000.0 + i * 200, "Sm3/day");
    wellStream.run();

    manifold.addStream(wellStream);
}

manifold.run();

System.out.println("Total production: " + manifold.getOutletStream().getFlowRate("Sm3/day") + " Sm3/day");
System.out.println("Manifold pressure: " + manifold.getOutletStream().getPressure("bara") + " bara");

Example 4: Product Distribution

// Gas from separator
Stream gasProduct = separator.getGasOutStream();

// Distribute to 3 customers
Splitter distributor = new Splitter("Gas Distribution", gasProduct, 3);

// Set by flow rates
double[] rates = {5000.0, 3000.0, 2000.0};  // Sm3/hr
distributor.setFlowRates(rates, "Sm3/hr");
distributor.run();

for (int i = 0; i < 3; i++) {
    Stream customerStream = distributor.getSplitStream(i);
    System.out.println("Customer " + (i+1) + ": " + customerStream.getFlowRate("Sm3/hr") + " Sm3/hr");
}

Example 5: Bypass Configuration

// Main stream
Stream mainStream = new Stream("Main", fluid);
mainStream.setFlowRate(1000.0, "kg/hr");
mainStream.run();

// Split: 80% through heater, 20% bypass
Splitter bypass = new Splitter("Bypass", mainStream, 2);
bypass.setSplitFactors(new double[]{0.80, 0.20});
bypass.run();

// Heat 80%
Heater heater = new Heater("E-100", bypass.getSplitStream(0));
heater.setOutletTemperature(400.0, "K");
heater.run();

// Remix
Mixer remix = new Mixer("M-100");
remix.addStream(heater.getOutletStream());
remix.addStream(bypass.getSplitStream(1));
remix.run();

System.out.println("Bypass temp control: " + remix.getOutletStream().getTemperature("K") + " K");

Equipment Overview

Process Equipment Documentation

This folder contains detailed documentation for all process equipment in NeqSim.

Equipment Categories

Flow Equipment

Equipment File Description
Streams streams.md Material and energy streams
Mixers & Splitters mixers_splitters.md Stream mixing and splitting

Separation Equipment

Equipment File Description
Separators separators.md 2-phase and 3-phase separators, scrubbers
Distillation distillation.md Distillation columns
Absorbers absorbers.md Absorption/stripping columns
Membranes membranes.md Membrane separation units
Filters filters.md Particulate and charcoal filters

Heat Transfer Equipment

Equipment File Description
Heat Exchangers heat_exchangers.md Heaters, coolers, condensers, reboilers

Rotating Equipment

Equipment File Description
Compressors compressors.md Gas compression, mechanical losses, seal gas
Pumps pumps.md Liquid pumping
Expanders expanders.md Power recovery, turboexpanders

Flow Control

Equipment File Description
Valves valves.md Throttling valves, chokes, safety valves

Reactors

Equipment File Description
Reactors reactors.md CSTR, PFR, equilibrium reactors
Electrolyzers electrolyzers.md Water and CO₂ electrolysis

Ejectors

Equipment File Description
Ejectors ejectors.md Steam and gas ejectors

Safety Equipment

Equipment File Description
Flares flares.md Flare systems and combustion

Well/Reservoir

Equipment File Description
Wells wells.md Production wells, chokes
Reservoirs reservoirs.md Material balance reservoir modeling
Subsea Systems subsea_systems.md Subsea wells and flowlines

Pipeline/Network

Equipment File Description
Pipelines pipelines.md Pipe flow, pressure drop
Networks networks.md Pipeline network modeling
Manifolds manifolds.md Multi-stream routing

Flow Measurement

Equipment File Description
Differential Pressure differential_pressure.md Orifice plates, flow measurement

Storage

Equipment File Description
Tanks tanks.md Storage tanks, LNG boil-off

Gas Treatment

Equipment File Description
Adsorbers adsorbers.md CO₂ and gas adsorption

Power Generation

Equipment File Description
Power Equipment power_generation.md Gas turbines, fuel cells, renewables

Utility Equipment

Equipment File Description
Adjusters util/adjusters.md Variable adjustment to meet specs
Recycles util/recycles.md Recycle stream handling
Calculators util/calculators.md Custom calculations and setters

Quick Reference

Creating Equipment

// All equipment follows similar pattern
EquipmentType equipment = new EquipmentType("Name", inletStream);
equipment.setParameter(value);
equipment.run();
Stream outlet = equipment.getOutletStream();

Adding to ProcessSystem

ProcessSystem process = new ProcessSystem();
process.add(stream);
process.add(equipment1);
process.add(equipment2);
process.run();

Getting Equipment by Name

Compressor comp = (Compressor) process.getUnit("K-100");

Common Methods

All equipment inherits from ProcessEquipmentBaseClass:

Method Description
run() Execute calculation
runTransient() Execute transient step
getName() Get equipment name
getInletStream() Get inlet stream
getOutletStream() Get outlet stream
getPressure() Get operating pressure
getTemperature() Get operating temperature
getMechanicalDesign() Get mechanical design object
needRecalculation() Check if recalculation needed

Compressor-Specific Methods

Method Description
initMechanicalLosses(shaftDiameter) Initialize seal gas and bearing loss model
getSealGasConsumption() Get total seal gas consumption (Nm³/hr)
getBearingLoss() Get total bearing power loss (kW)
getMechanicalEfficiency() Get mechanical efficiency (0-1)

Equipment Inheritance

ProcessEquipmentInterface
    │
    └── ProcessEquipmentBaseClass
            │
            ├── TwoPortEquipment (inlet/outlet pattern)
            │       ├── Heater, Cooler
            │       ├── Compressor, Pump, Expander
            │       ├── ThrottlingValve
            │       └── ...
            │
            ├── Separator (multi-outlet)
            │       ├── ThreePhaseSeparator
            │       ├── GasScrubber
            │       └── ...
            │
            ├── Mixer (multi-inlet)
            ├── Splitter (multi-outlet)
            │
            └── DistillationColumn

Chapter 14: Separation Equipment

Separators

Separator Equipment

Documentation for separator equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.separator

Classes:


Separator Types

Two-Phase Separator

import neqsim.process.equipment.separator.Separator;

Separator separator = new Separator("V-100", inletStream);
separator.run();

// Get outlet streams
Stream gasOut = separator.getGasOutStream();
Stream liquidOut = separator.getLiquidOutStream();

// Properties
double gasRate = gasOut.getFlowRate("kg/hr");
double liquidRate = liquidOut.getFlowRate("kg/hr");
double liquidLevel = separator.getLiquidLevel();

Three-Phase Separator

import neqsim.process.equipment.separator.ThreePhaseSeparator;

ThreePhaseSeparator separator = new ThreePhaseSeparator("V-200", inletStream);
separator.run();

// Get outlet streams
Stream gasOut = separator.getGasOutStream();
Stream oilOut = separator.getOilOutStream();
Stream waterOut = separator.getWaterOutStream();

// Water cut
double waterCut = separator.getWaterCut();

Gas Scrubber

import neqsim.process.equipment.separator.GasScrubber;

GasScrubber scrubber = new GasScrubber("Inlet Scrubber", gasStream);
scrubber.run();

// Dry gas output
Stream dryGas = scrubber.getGasOutStream();

// Condensate removal
Stream condensate = scrubber.getLiquidOutStream();

Separator Sizing

Vertical Separator

// Set dimensions
separator.setInternalDiameter(2.0, "m");
separator.setLiquidVolume(10.0, "m3");

// Or specify residence time
separator.setLiquidResidenceTime(120.0, "sec");

Horizontal Separator

separator.setSeparatorType("horizontal");
separator.setLength(10.0, "m");
separator.setInternalDiameter(2.5, "m");

Dynamic Simulation

// Enable dynamic mode
separator.setCalculateSteadyState(false);

// Set initial conditions
separator.setLiquidLevel(0.5);  // 50% level

// Run transient
for (int i = 0; i < 100; i++) {
    separator.runTransient();

    double level = separator.getLiquidLevel();
    double pressure = separator.getPressure();
}

Separation Efficiency

// Set droplet removal efficiency
separator.setGasCarryUnderFraction(0.001);  // 0.1% liquid in gas
separator.setLiquidCarryOverFraction(0.0001); // 0.01% gas in liquid

Example: HP/LP Separation Train

// HP Separator at 50 bar
Separator hpSep = new Separator("HP Sep", feedStream);
process.add(hpSep);

// Letdown valve
ThrottlingValve lpValve = new ThrottlingValve("LP Valve", hpSep.getLiquidOutStream());
lpValve.setOutletPressure(5.0, "bara");
process.add(lpValve);

// LP Separator at 5 bar
Separator lpSep = new Separator("LP Sep", lpValve.getOutletStream());
process.add(lpSep);

// Run process
process.run();

// Total gas production
double hpGas = hpSep.getGasOutStream().getFlowRate("MSm3/day");
double lpGas = lpSep.getGasOutStream().getFlowRate("MSm3/day");
double totalGas = hpGas + lpGas;

Distillation

Distillation Equipment

Documentation for distillation column equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.distillation

Classes:


Basic Usage

import neqsim.process.equipment.distillation.DistillationColumn;

// Create column with 10 trays, condenser, and reboiler
DistillationColumn column = new DistillationColumn("Deethanizer", 10, true, true);
column.addFeedStream(feedStream, 5);  // Feed on tray 5
column.setCondenserTemperature(40.0, "C");
column.setReboilerTemperature(120.0, "C");
column.run();

// Get products
Stream overhead = column.getGasOutStream();
Stream bottoms = column.getLiquidOutStream();

Builder Pattern

For complex column configurations, use the fluent Builder API:

import neqsim.process.equipment.distillation.DistillationColumn;
import neqsim.process.equipment.distillation.DistillationColumn.SolverType;

// Build column with fluent API
DistillationColumn column = DistillationColumn.builder("Deethanizer")
    .numberOfTrays(15)
    .withCondenserAndReboiler()
    .topPressure(25.0, "bara")
    .bottomPressure(26.0, "bara")
    .temperatureTolerance(0.001)
    .massBalanceTolerance(0.01)
    .maxIterations(100)
    .solverType(SolverType.INSIDE_OUT)
    .internalDiameter(2.5)
    .addFeedStream(feedStream, 8)
    .build();

column.run();

Builder Methods

Method Description
numberOfTrays(int) Set number of simple trays (excluding condenser/reboiler)
withCondenser() Add condenser at top
withReboiler() Add reboiler at bottom
withCondenserAndReboiler() Add both
topPressure(double, String) Set top pressure with unit
bottomPressure(double, String) Set bottom pressure with unit
pressure(double, String) Set same pressure top and bottom
temperatureTolerance(double) Convergence tolerance for temperature
massBalanceTolerance(double) Convergence tolerance for mass balance
tolerance(double) Set all tolerances at once
maxIterations(int) Maximum solver iterations
solverType(SolverType) Set solver algorithm
directSubstitution() Use direct substitution solver
dampedSubstitution() Use damped substitution solver
insideOut() Use inside-out solver
relaxationFactor(double) Damping factor for solver
internalDiameter(double) Column internal diameter (meters)
multiPhaseCheck(boolean) Enable/disable multi-phase check
addFeedStream(Stream, int) Add feed stream to specified tray
build() Build the configured column

Column Configuration

Number of Trays

// Constructor: (name, numTrays, hasCondenser, hasReboiler)
DistillationColumn column = new DistillationColumn("T-100", 20, true, true);

Feed Location

// Single feed
column.addFeedStream(feed, 10);  // Tray 10 from bottom

// Multiple feeds
column.addFeedStream(feed1, 8);
column.addFeedStream(feed2, 12);

Condenser Type

// Total condenser
column.setCondenserType("total");

// Partial condenser (vapor overhead)
column.setCondenserType("partial");

Side Draws

// Liquid side draw
column.addSideDraw(7, "liquid", 100.0, "kg/hr");

// Vapor side draw
column.addSideDraw(15, "vapor", 50.0, "kg/hr");

Operating Specifications

Temperature Specifications

// Condenser temperature
column.setCondenserTemperature(40.0, "C");

// Reboiler temperature
column.setReboilerTemperature(120.0, "C");

Pressure Profile

// Top pressure
column.setTopPressure(15.0, "bara");

// Bottom pressure (or pressure drop)
column.setBottomPressure(16.0, "bara");

// Or specify pressure drop per tray
column.setPressureDropPerTray(0.05, "bar");

Reflux Specifications

// Reflux ratio
column.setRefluxRatio(3.0);

// Condenser duty
column.setCondenserDuty(-5000000.0);  // W (negative = cooling)

Reboiler Specifications

// Reboiler duty
column.setReboilerDuty(6000000.0);  // W

// Boilup ratio
column.setBoilupRatio(2.5);

Solver Options

Available Solvers

// Standard sequential solver
column.setSolverType(DistillationColumn.SolverType.STANDARD);

// Damped solver (more robust)
column.setSolverType(DistillationColumn.SolverType.DAMPED);

// Inside-out solver (fastest for converged cases)
column.setSolverType(DistillationColumn.SolverType.INSIDE_OUT);

Convergence Settings

// Maximum iterations
column.setMaxIterations(100);

// Tolerance
column.setTolerance(1e-6);

// Damping factor
column.setDampingFactor(0.5);

Initialization

// Linear temperature profile initialization
column.setInitialTemperatureProfile("linear");

// Custom initialization
double[] initTemps = {120, 115, 110, 105, 100, 95, 90, 85, 80, 75, 70};
column.setInitialTemperatures(initTemps);

Column Results

Tray-by-Tray Profiles

column.run();

// Temperature profile
for (int i = 0; i < column.getNumberOfTrays(); i++) {
    double T = column.getTray(i).getTemperature("C");
    System.out.println("Tray " + i + ": " + T + " °C");
}

// Composition profile
for (int i = 0; i < column.getNumberOfTrays(); i++) {
    double[] x = column.getTray(i).getLiquidComposition();
    double[] y = column.getTray(i).getVaporComposition();
}

Duties

double Qcond = column.getCondenserDuty();  // W
double Qreb = column.getReboilerDuty();    // W

System.out.println("Condenser duty: " + (-Qcond/1e6) + " MW");
System.out.println("Reboiler duty: " + (Qreb/1e6) + " MW");

Separation Performance

// Product purities
double overheadPurity = overhead.getFluid().getComponent("ethane").getx();
double bottomsRecovery = 1.0 - (overhead.getFluid().getComponent("propane").getNumberOfmable() /
    feedStream.getFluid().getComponent("propane").getNumberOfmable());

Example: NGL Fractionation

Deethanizer

// Feed: NGL from gas plant
SystemInterface ngl = new SystemSrkEos(273.15 + 30, 25.0);
ngl.addComponent("methane", 0.02);
ngl.addComponent("ethane", 0.25);
ngl.addComponent("propane", 0.35);
ngl.addComponent("i-butane", 0.10);
ngl.addComponent("n-butane", 0.18);
ngl.addComponent("n-pentane", 0.10);
ngl.setMixingRule("classic");

Stream feed = new Stream("NGL Feed", ngl);
feed.setFlowRate(5000.0, "kg/hr");

ProcessSystem process = new ProcessSystem();
process.add(feed);

// Deethanizer column
DistillationColumn deethanizer = new DistillationColumn("Deethanizer", 25, true, true);
deethanizer.addFeedStream(feed, 12);
deethanizer.setTopPressure(25.0, "bara");
deethanizer.setCondenserTemperature(-10.0, "C");
deethanizer.setReboilerTemperature(100.0, "C");
deethanizer.setSolverType(DistillationColumn.SolverType.INSIDE_OUT);
process.add(deethanizer);

process.run();

// Results
Stream ethaneProduct = deethanizer.getGasOutStream();
Stream c3plusProduct = deethanizer.getLiquidOutStream();

System.out.println("Ethane product:");
System.out.println("  Flow: " + ethaneProduct.getFlowRate("kg/hr") + " kg/hr");
System.out.println("  C2 purity: " + 
    ethaneProduct.getFluid().getComponent("ethane").getx() * 100 + " mol%");

System.out.println("C3+ product:");
System.out.println("  Flow: " + c3plusProduct.getFlowRate("kg/hr") + " kg/hr");
System.out.println("  C2 content: " + 
    c3plusProduct.getFluid().getComponent("ethane").getx() * 100 + " mol%");

Depropanizer

// Feed from deethanizer bottoms
DistillationColumn depropanizer = new DistillationColumn("Depropanizer", 30, true, true);
depropanizer.addFeedStream(c3plusProduct, 15);
depropanizer.setTopPressure(18.0, "bara");
depropanizer.setCondenserTemperature(45.0, "C");
depropanizer.setReboilerTemperature(110.0, "C");
process.add(depropanizer);

process.run();

Stream propaneProduct = depropanizer.getGasOutStream();
Stream c4plusProduct = depropanizer.getLiquidOutStream();

Absorber Column

For absorption without reboiler:

DistillationColumn absorber = new DistillationColumn("Absorber", 10, false, false);
absorber.addFeedStream(gasStream, 1);      // Gas at bottom
absorber.addFeedStream(leanSolvent, 10);   // Solvent at top
absorber.run();

Stream richSolvent = absorber.getLiquidOutStream();
Stream sweetGas = absorber.getGasOutStream();

Stripper Column

For stripping without condenser:

DistillationColumn stripper = new DistillationColumn("Stripper", 8, false, true);
stripper.addFeedStream(richSolvent, 1);
stripper.setReboilerTemperature(120.0, "C");
stripper.run();

Stream acidGas = stripper.getGasOutStream();
Stream leanSolvent = stripper.getLiquidOutStream();

Distillation Wiki

Distillation column algorithm

This document describes the mathematical model and solver implementations that power the DistillationColumn class in NeqSim. The class maps directly to the files src/main/java/neqsim/process/equipment/distillation/DistillationColumn.java and DistillationColumnMatrixSolver.java.

Governing equations

Each ideal-equilibrium tray satisfies the familiar MESH relationships:

  1. Total mass balance (tray j)

    [ V_{j-1} + L_{j+1} + F_j = V_j + L_j ]

  2. Component balances

    [ V_{j-1} y_{i,j-1} + L_{j+1} x_{i,j+1} + F_j z_{i,j} = V_j y_{i,j} + L_j x_{i,j} ]

  3. Phase equilibrium (K-values)

    [ y_{i,j} = K_{i,j} x_{i,j}, \qquad K_{i,j} = \frac{\hat f_{i,j}^{\text{vap}}}{\hat f_{i,j}^{\text{liq}}} ]

  4. Energy balance

    [ V_{j-1} h_{j-1}^{V} + L_{j+1} h_{j+1}^{L} + F_j h_j^{F} + Q_j = V_j h_j^{V} + L_j h_j^{L} ]

NeqSim evaluates fugacity-based K-values and molar enthalpies through the active SystemInterface. The matrix solver also uses linearized component balances in tridiagonal form:

[ A_j l_{i,j-1} + B_j l_{i,j} + C_j l_{i,j+1} = D_{i,j} ]

with stripping factors (S_j = K_{i,j} V_j / L_j) embedded in the diagonal terms.

Temperature updates rely on the log-Newton step derived from (\sum_i y_{i,j}=1):

[ \Delta T_j = -\frac{\ln(\sum_i K_{i,j} x_{i,j}) R T_j^2}{h_j^{V} - h_j^{L}} ]

The code limits (\Delta T_j) to ±5 K and enforces bounds of 50–1000 K for numerical stability.

Column preparation

  1. Feed assignment – feeds are attached with addFeedStream; unassigned feeds are auto-placed near matching tray temperatures.
  2. Temperature seedinginit() runs the lowest feed tray, extrapolates temperatures towards condenser and reboiler, and links neighbouring trays with vapour/liquid streams.
  3. Pressure profileprepareColumnForSolve() imposes a linear pressure drop between the configured bottom and top pressures (or inferred tray values when unspecified).

Solver implementations

Solver Class/Method Strategy Notes
DIRECT_SUBSTITUTION solveSequential() Classic two-sweep sequential substitution (liquids down, vapours up) with adaptive relaxation on temperatures and streams. Converges robustly for well-behaved systems; default choice.
DAMPED_SUBSTITUTION runDamped() Same equations as direct substitution but starts with a user-defined fixed relaxation factor before enabling adaptation. Useful for stiff columns where the default step overshoots.
INSIDE_OUT solveInsideOut() Quadrat-structure inside-out method: streams are relaxed against previous iterates while tray properties update using enthalpy-driven temperature corrections. Balances mass/energy less frequently to reduce cost and supports a polishing phase for tight tolerances.
BROYDEN (experimental) runBroyden() Applies a secant correction on tray temperatures, effectively mixing current and previous deltas. Handy for rapid feasibility studies but less stable than inside-out.
MATRIX_SOLVER DistillationColumnMatrixSolver.solve() Builds component flow equations into a TDMA system, blends constant molar overflow (CMO) estimates with sum-rate flows, then updates temperatures via the log-Newton scheme above. Eliminates explicit stream tearing by solving component balances directly; still refines temperatures iteratively.

Sequential substitution details

Inside-out specifics

Matrix solver specifics

Result handling

Once any solver converges, the top gas outlet (gasOutStream) and bottom liquid outlet (liquidOutStream) are cloned from the respective trays. Mass, energy, and iteration statistics are exposed through getters such as getLastIterationCount(), getLastMassResidual(), and getLastEnergyResidual().

Further improvements

Absorbers

Absorbers and Strippers

Documentation for mass transfer columns in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.absorber

Classes:

Class Description
Absorber General absorption column
SimpleAbsorber Simplified absorber model
WaterStripperColumn Water stripping column

Absorbers transfer components from gas to liquid phase, while strippers transfer from liquid to gas.


Absorber

Basic Usage

import neqsim.process.equipment.absorber.Absorber;

Absorber absorber = new Absorber("Amine Absorber");
absorber.addGasInStream(gasStream);
absorber.addSolventInStream(amineSolution);
absorber.setNumberOfTheoreticalStages(10);
absorber.run();

Stream sweetGas = absorber.getGasOutStream();
Stream richAmine = absorber.getLiquidOutStream();

Absorption Efficiency

// Component removal efficiency
absorber.setRemovalEfficiency("CO2", 0.95);  // 95% CO2 removal
absorber.setRemovalEfficiency("H2S", 0.99);  // 99% H2S removal

Stage Configuration

absorber.setNumberOfTheoreticalStages(20);
absorber.setStageEfficiency(0.7);  // Murphree efficiency

Stripper

Basic Usage

import neqsim.process.equipment.absorber.WaterStripperColumn;

WaterStripperColumn stripper = new WaterStripperColumn("Regenerator");
stripper.setLiquidInStream(richAmine);
stripper.setNumberOfStages(15);
stripper.setReboilerTemperature(120.0, "C");
stripper.run();

Stream leanAmine = stripper.getLiquidOutStream();
Stream acidGas = stripper.getGasOutStream();

Simple Absorber

Simplified mass transfer model.

Usage

import neqsim.process.equipment.absorber.SimpleAbsorber;

SimpleAbsorber absorber = new SimpleAbsorber("CO2 Absorber");
absorber.addGasInStream(feedGas);
absorber.addSolventInStream(solvent);
absorber.setAbsorptionEfficiency(0.90);
absorber.run();

Examples

Example 1: Amine Gas Treating

import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.absorber.Absorber;

// Sour gas
SystemSrkCPAstatoil sourGas = new SystemSrkCPAstatoil(313.15, 70.0);
sourGas.addComponent("methane", 0.85);
sourGas.addComponent("CO2", 0.10);
sourGas.addComponent("H2S", 0.01);
sourGas.addComponent("water", 0.04);
sourGas.setMixingRule("classic");

Stream gasIn = new Stream("Sour Gas", sourGas);
gasIn.setFlowRate(100000.0, "Sm3/hr");
gasIn.run();

// Lean amine (MDEA solution)
SystemSrkCPAstatoil amine = new SystemSrkCPAstatoil(313.15, 70.0);
amine.addComponent("water", 0.50);
amine.addComponent("MDEA", 0.50);
amine.setMixingRule("classic");

Stream leanAmine = new Stream("Lean Amine", amine);
leanAmine.setFlowRate(50000.0, "kg/hr");
leanAmine.run();

// Absorber
Absorber absorber = new Absorber("Amine Contactor");
absorber.addGasInStream(gasIn);
absorber.addSolventInStream(leanAmine);
absorber.setNumberOfTheoreticalStages(15);
absorber.run();

// Results
Stream sweetGas = absorber.getGasOutStream();
double co2Out = sweetGas.getFluid().getMoleFraction("CO2") * 1e6;  // ppm
System.out.println("Sweet gas CO2: " + co2Out + " ppm");

Example 2: TEG Dehydration

// Wet natural gas
SystemSrkEos wetGas = new SystemSrkEos(303.15, 70.0);
wetGas.addComponent("methane", 0.90);
wetGas.addComponent("ethane", 0.05);
wetGas.addComponent("propane", 0.03);
wetGas.addComponent("water", 0.02);
wetGas.setMixingRule("classic");

Stream gasIn = new Stream("Wet Gas", wetGas);
gasIn.setFlowRate(5000000.0, "Sm3/day");
gasIn.run();

// Lean TEG
SystemSrkEos teg = new SystemSrkEos(313.15, 70.0);
teg.addComponent("TEG", 0.99);
teg.addComponent("water", 0.01);
teg.setMixingRule("classic");

Stream leanTEG = new Stream("Lean TEG", teg);
leanTEG.setFlowRate(1000.0, "kg/hr");
leanTEG.run();

// Contactor
Absorber contactor = new Absorber("TEG Contactor");
contactor.addGasInStream(gasIn);
contactor.addSolventInStream(leanTEG);
contactor.setNumberOfTheoreticalStages(3);
contactor.run();

Stream dryGas = contactor.getGasOutStream();
double waterContent = dryGas.getFluid().getMoleFraction("water") * 1e6;
System.out.println("Dry gas water content: " + waterContent + " ppm");

Example 3: Water Wash Column

// Gas with methanol
SystemSrkEos gas = new SystemSrkEos(280.0, 50.0);
gas.addComponent("methane", 0.95);
gas.addComponent("methanol", 0.03);
gas.addComponent("water", 0.02);
gas.setMixingRule("classic");

Stream gasIn = new Stream("Gas", gas);
gasIn.setFlowRate(10000.0, "kg/hr");
gasIn.run();

// Wash water
SystemSrkEos water = new SystemSrkEos(290.0, 50.0);
water.addComponent("water", 1.0);
water.setMixingRule("classic");

Stream washWater = new Stream("Wash Water", water);
washWater.setFlowRate(500.0, "kg/hr");
washWater.run();

// Absorber
SimpleAbsorber waterWash = new SimpleAbsorber("Water Wash");
waterWash.addGasInStream(gasIn);
waterWash.addSolventInStream(washWater);
waterWash.setAbsorptionEfficiency(0.85);
waterWash.run();

Stream cleanGas = waterWash.getGasOutStream();
double meohRemaining = cleanGas.getFluid().getMoleFraction("methanol") * 100;
System.out.println("Methanol in clean gas: " + meohRemaining + " mol%");

Membrane

Membrane separation

This page outlines the basic model implemented in the MembraneSeparator unit. The unit is intended for simple simulations of gas separation membranes or pervaporation modules used in purification and CO2 capture.

Flux model

For each component $i$ a constant permeate fraction $f_i$ can be specified. The molar amount transferred to the permeate side is $$ N_i^{\text{perm}} = f_i N_i^{\text{feed}} $$ where $N_i^{\text{feed}}$ is the molar amount in the feed stream. Components without a specified fraction use a global default value.

A more rigorous model could employ Fick's law of diffusion through the membrane $$ J_i = P_i \left(p_{i,\text{feed}} - p_{i,\text{perm}}\right) $$ where $P_i$ is the permeability of component $i$ and $p_i$ are partial pressures. The separator can now perform this calculation mode when permeabilities and a membrane area are supplied.

Usage

MembraneSeparator mem = new MembraneSeparator("mem", feedStream);
mem.setDefaultPermeateFraction(0.1); // 10 % of each component permeates
mem.setPermeateFraction("CO2", 0.5); // override CO2 fraction

// Alternative using permeability coefficients mem.clearPermeateFractions(); mem.setMembraneArea(5.0); // m^2 mem.setPermeability("CO2", 5e-6); // mol/(m2sPa) mem.setPermeability("methane", 1e-6);

After running the process, the permeate and retentate streams can be obtained via `getPermeateStream()` and `getRetentateStream()`.



## Membrane Equipment


# Membrane Separation Equipment

Documentation for membrane separation equipment in NeqSim process simulation.

## Table of Contents
- [Overview](#overview)
- [MembraneSeparator Class](#membraneseparator-class)
- [Permeation Models](#permeation-models)
- [Configuration](#configuration)
- [Usage Examples](#usage-examples)

---

## Overview

**Location:** `neqsim.process.equipment.membrane`

**Classes:**
| Class | Description |
|-------|-------------|
| `MembraneSeparator` | Generic membrane separation unit |

Membrane separators provide selective separation based on component permeabilities through a membrane material. Applications include:
- CO₂ removal from natural gas
- Hydrogen recovery
- Nitrogen generation
- Dehydration
- Vapor/gas separation

---

## MembraneSeparator Class

### Basic Usage

```java
import neqsim.process.equipment.membrane.MembraneSeparator;

// Create membrane separator
MembraneSeparator membrane = new MembraneSeparator("CO2 Membrane", feedStream);

// Set permeate fractions for each component
membrane.setPermeateFraction("CO2", 0.95);     // 95% of CO2 permeates
membrane.setPermeateFraction("methane", 0.05); // 5% of methane permeates
membrane.setPermeateFraction("ethane", 0.03);  // 3% of ethane permeates

membrane.run();

// Get output streams
StreamInterface permeate = membrane.getPermeateStream();
StreamInterface retentate = membrane.getRetentateStream();

Constructor Options

// With name only
MembraneSeparator membrane = new MembraneSeparator("MEM-100");
membrane.setInletStream(feedStream);

// With name and inlet stream
MembraneSeparator membrane = new MembraneSeparator("MEM-100", feedStream);

Permeation Models

Permeate Fraction Method

The simplest approach specifies what fraction of each component permeates:

// Set permeate fraction (0.0 to 1.0)
membrane.setPermeateFraction("CO2", 0.90);
membrane.setPermeateFraction("H2S", 0.85);
membrane.setPermeateFraction("methane", 0.02);
membrane.setPermeateFraction("ethane", 0.01);
membrane.setPermeateFraction("propane", 0.005);

// Set default for unlisted components
membrane.setDefaultPermeateFraction(0.01);

Permeability Method

For more rigorous calculations using permeability coefficients:

// Set membrane area
membrane.setMembraneArea(100.0);  // m²

// Set permeability for each component (mol/(m²·s·Pa))
membrane.setPermeability("CO2", 1.0e-9);
membrane.setPermeability("methane", 2.0e-11);
membrane.setPermeability("nitrogen", 5.0e-12);

Selectivity

The selectivity of component A over B is:

$$\alpha_{A/B} = \frac{P_A}{P_B}$$

Where $P_A$ and $P_B$ are the permeabilities of components A and B.

Typical selectivities for polymeric membranes:

Separation Selectivity
CO₂/CH₄ 15-50
H₂/CH₄ 30-100
O₂/N₂ 4-8
H₂O/CH₄ >100

Configuration

Operating Conditions

// Inlet stream conditions affect separation
feedStream.setPressure(50.0, "bara");  // High feed pressure
feedStream.setTemperature(40.0, "C");

// Permeate side typically at lower pressure
// (Pressure difference drives permeation)

Stage Cut

The stage cut (θ) is the fraction of feed that permeates:

$$\theta = \frac{\dot{n}_{permeate}}{\dot{n}_{feed}}$$

membrane.run();

double feedFlow = feedStream.getFlowRate("kmol/hr");
double permeateFlow = membrane.getPermeateStream().getFlowRate("kmol/hr");
double stageCut = permeateFlow / feedFlow;

System.out.println("Stage cut: " + (stageCut * 100) + " %");

Output Streams

Permeate Stream

The permeate contains components that pass through the membrane:

StreamInterface permeate = membrane.getPermeateStream();

// Get permeate composition
double co2InPermeate = permeate.getFluid().getMoleFraction("CO2");
double permeateFlow = permeate.getFlowRate("kmol/hr");

System.out.println("Permeate CO2: " + (co2InPermeate * 100) + " mol%");

Retentate Stream

The retentate contains components that do not permeate:

StreamInterface retentate = membrane.getRetentateStream();

// Get retentate (product gas) composition
double co2InRetentate = retentate.getFluid().getMoleFraction("CO2");
double ch4InRetentate = retentate.getFluid().getMoleFraction("methane");

System.out.println("Retentate CO2: " + (co2InRetentate * 100) + " mol%");
System.out.println("Retentate CH4: " + (ch4InRetentate * 100) + " mol%");

Usage Examples

CO₂ Removal from Natural Gas

ProcessSystem process = new ProcessSystem();

// Feed gas with CO2
SystemInterface feedFluid = new SystemSrkEos(310.0, 60.0);
feedFluid.addComponent("methane", 0.85);
feedFluid.addComponent("ethane", 0.05);
feedFluid.addComponent("propane", 0.02);
feedFluid.addComponent("CO2", 0.08);
feedFluid.setMixingRule("classic");

Stream feedGas = new Stream("Feed Gas", feedFluid);
feedGas.setFlowRate(100000.0, "Sm3/day");
process.add(feedGas);

// Membrane unit
MembraneSeparator membrane = new MembraneSeparator("CO2 Membrane", feedGas);
membrane.setPermeateFraction("CO2", 0.90);
membrane.setPermeateFraction("methane", 0.03);
membrane.setPermeateFraction("ethane", 0.02);
membrane.setPermeateFraction("propane", 0.01);
process.add(membrane);

// Run
process.run();

// Check CO2 spec
double productCO2 = membrane.getRetentateStream().getFluid().getMoleFraction("CO2");
System.out.println("Product gas CO2: " + (productCO2 * 100) + " mol%");

// Methane recovery
double feedCH4 = feedGas.getFlowRate("Sm3/day") * 0.85;
double productCH4 = membrane.getRetentateStream().getFlowRate("Sm3/day") * 
    membrane.getRetentateStream().getFluid().getMoleFraction("methane");
double recovery = productCH4 / feedCH4 * 100;
System.out.println("Methane recovery: " + recovery + " %");

Multi-Stage Membrane System

For deep CO₂ removal, multiple stages may be required:

// First stage membrane
MembraneSeparator stage1 = new MembraneSeparator("Stage 1", feedGas);
stage1.setPermeateFraction("CO2", 0.80);
stage1.setPermeateFraction("methane", 0.05);
process.add(stage1);

// Second stage on retentate
MembraneSeparator stage2 = new MembraneSeparator("Stage 2", stage1.getRetentateStream());
stage2.setPermeateFraction("CO2", 0.80);
stage2.setPermeateFraction("methane", 0.05);
process.add(stage2);

// Recycle permeate from stage 2 to stage 1 feed
Mixer mixer = new Mixer("Feed Mixer");
mixer.addStream(feedGas);
mixer.addStream(stage2.getPermeateStream());
process.add(mixer);

// Connect mixer to stage 1
stage1.setInletStream(mixer.getOutletStream());

// Add recycle
Recycle recycle = new Recycle("Membrane Recycle");
recycle.addStream(stage2.getPermeateStream());
recycle.setOutletStream(mixer);
process.add(recycle);

process.run();

Hydrogen Recovery

// Refinery off-gas
SystemInterface offgas = new SystemSrkEos(320.0, 30.0);
offgas.addComponent("hydrogen", 0.40);
offgas.addComponent("methane", 0.35);
offgas.addComponent("ethane", 0.15);
offgas.addComponent("propane", 0.10);
offgas.setMixingRule("classic");

Stream feed = new Stream("Off-gas", offgas);
feed.setFlowRate(5000.0, "Sm3/hr");

// H2 selective membrane
MembraneSeparator h2Membrane = new MembraneSeparator("H2 Membrane", feed);
h2Membrane.setPermeateFraction("hydrogen", 0.95);
h2Membrane.setPermeateFraction("methane", 0.08);
h2Membrane.setPermeateFraction("ethane", 0.02);
h2Membrane.setPermeateFraction("propane", 0.01);
h2Membrane.run();

// H2 purity in permeate
double h2Purity = h2Membrane.getPermeateStream().getFluid().getMoleFraction("hydrogen");
System.out.println("H2 purity: " + (h2Purity * 100) + " mol%");

Process Integration

With Compression

// Compress permeate for recycle or further processing
Compressor permeateComp = new Compressor("Permeate Comp", membrane.getPermeateStream());
permeateComp.setOutletPressure(feedPressure, "bara");
permeateComp.setIsentropicEfficiency(0.75);

With Cooling

// Cool membrane feed to improve selectivity
Cooler feedCooler = new Cooler("Membrane Feed Cooler", feedGas);
feedCooler.setOutTemperature(30.0, "C");

membrane.setInletStream(feedCooler.getOutletStream());

Design Considerations

Operating Conditions

Membrane Selection

Type Applications Selectivity
Cellulose acetate CO₂/CH₄ 15-25
Polyimide CO₂/CH₄, H₂ 20-50
Polysulfone O₂/N₂ 5-6
PDMS VOC removal varies

Filters

Filter Equipment

Documentation for filter equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.filter

Classes:

Class Description
Filter Generic filter unit
CharCoalFilter Activated charcoal filter

Filters are used to remove specific components or contaminants from process streams. Applications include:


Filter Class

Basic Usage

import neqsim.process.equipment.filter.Filter;

// Create filter on gas stream
Filter filter = new Filter("Particulate Filter", gasStream);
filter.run();

// Get outlet stream
StreamInterface cleanGas = filter.getOutletStream();

CharCoalFilter Class

Activated charcoal filter for removing specific components.

Basic Usage

import neqsim.process.equipment.filter.CharCoalFilter;

// Create charcoal filter
CharCoalFilter charFilter = new CharCoalFilter("Mercury Filter", gasStream);
charFilter.setRemovalEfficiency("mercury", 0.99);  // 99% removal
charFilter.run();

// Get treated stream
StreamInterface treatedGas = charFilter.getOutletStream();

Removal Efficiency

// Set removal efficiency for specific components
charFilter.setRemovalEfficiency("mercury", 0.99);
charFilter.setRemovalEfficiency("H2S", 0.95);
charFilter.setRemovalEfficiency("benzene", 0.90);

Usage Examples

Inlet Gas Conditioning

ProcessSystem process = new ProcessSystem();

// Raw gas feed
Stream rawGas = new Stream("Raw Gas", gasFluid);
rawGas.setFlowRate(100000.0, "Sm3/day");
process.add(rawGas);

// Particulate filter
Filter particleFilter = new Filter("Inlet Filter", rawGas);
process.add(particleFilter);

// Mercury removal
CharCoalFilter hgFilter = new CharCoalFilter("Hg Guard Bed", 
    particleFilter.getOutletStream());
hgFilter.setRemovalEfficiency("mercury", 0.999);
process.add(hgFilter);

// Run
process.run();

LNG Mercury Removal

// Upstream of cryogenic section
CharCoalFilter mercuryRemoval = new CharCoalFilter("Mercury Removal", feed);
mercuryRemoval.setRemovalEfficiency("mercury", 0.9999);  // Critical for aluminum equipment
mercuryRemoval.run();

double outletMercury = mercuryRemoval.getOutletStream()
    .getFluid().getComponent("mercury").getFlowRate("g/hr");
System.out.println("Outlet mercury: " + outletMercury + " g/hr");

Chapter 15: Rotating Equipment

Compressors

Compressor Equipment

Documentation for compression equipment in NeqSim process simulation.

Table of Contents

📖 Detailed Curve Documentation: For comprehensive information on compressor curves, including multi-speed vs single-speed handling, surge curves, and stone wall curves, see Compressor Curves and Performance Maps.


Overview

Location: neqsim.process.equipment.compressor

Classes:


Basic Usage

import neqsim.process.equipment.compressor.Compressor;

Compressor compressor = new Compressor("K-100", inletStream);
compressor.setOutletPressure(80.0, "bara");
compressor.setIsentropicEfficiency(0.75);
compressor.run();

// Results
double power = compressor.getPower("kW");
double outletT = compressor.getOutletStream().getTemperature("C");
double polytropicHead = compressor.getPolytropicHead("kJ/kg");

System.out.println("Power: " + power + " kW");
System.out.println("Outlet temperature: " + outletT + " °C");

Calculation Methods

Isentropic Compression

compressor.setIsentropicEfficiency(0.75);  // 75%
compressor.setUsePolytropicCalc(false);
compressor.run();

double isentropicHead = compressor.getIsentropicHead("kJ/kg");
double isentropicPower = compressor.getPower("kW");

Polytropic Compression

More accurate for real gas behavior.

compressor.setPolytropicEfficiency(0.80);  // 80%
compressor.setUsePolytropicCalc(true);
compressor.run();

double polytropicHead = compressor.getPolytropicHead("kJ/kg");
double polytropicExponent = compressor.getPolytropicExponent();

Power Specified

compressor.setPower(5000.0, "kW");  // Specify power
compressor.setIsentropicEfficiency(0.75);
compressor.run();

double outletP = compressor.getOutletStream().getPressure("bara");

Efficiency Relationships

Isentropic Efficiency

$$\eta_{is} = \frac{H_{is}}{H_{actual}} = \frac{T_{2s} - T_1}{T_2 - T_1}$$

Polytropic Efficiency

$$\eta_p = \frac{n-1}{n} \cdot \frac{k}{k-1}$$

Where:


Performance Curves

NeqSim supports detailed compressor performance maps with multiple speed curves. For comprehensive documentation, see Compressor Curves and Performance Maps.

Setting Compressor Map

// Define speed curves
double[] speeds = {8000, 9000, 10000, 11000};  // RPM

// For each speed: arrays of flow, head, efficiency
double[][] flows = { {flow1_curve1, flow2_curve1}, {flow1_curve2, flow2_curve2}, ... };
double[][] heads = { {head1_curve1, head2_curve1}, {head1_curve2, head2_curve2}, ... };
double[][] efficiencies = { {eff1_curve1, eff2_curve1}, {eff1_curve2, eff2_curve2}, ... };

CompressorChartInterface chart = compressor.getCompressorChart();
chart.setCurves(chartConditions, speeds, flows, heads, flows, efficiencies);
chart.setHeadUnit("kJ/kg");
compressor.setSpeed(10000);  // Operating speed

Operating Point

compressor.setSpeed(10000);  // RPM
compressor.run();

double actualFlow = compressor.getActualFlow("m3/hr");
double head = compressor.getPolytropicHead("kJ/kg");
double efficiency = compressor.getPolytropicEfficiency();

Surge and Stone Wall

Multi-Speed vs Single-Speed Compressors

Compressor Type Surge/Stone Wall Setting Method
Multi-speed (≥2 speeds) Curves (interpolated) Multiple flow/head points
Single-speed (1 speed) Single points (constant) Single flow/head point

Checking Operating Limits

// Distance to surge (positive = above surge, safe)
double distanceToSurge = compressor.getDistanceToSurge();

// Distance to stone wall (positive = below choke, safe)
double distanceToStoneWall = compressor.getDistanceToStoneWall();

// Check if in surge
boolean isSurge = compressor.getCompressorChart().getSurgeCurve().isSurge(head, flow);

// Get surge flow rate
double surgeFlow = compressor.getSurgeFlowRate();

Single-Speed Compressor Example

// For single-speed compressors, surge is a single point
double[] surgeFlow = {5607.45};  // Single value
double[] surgeHead = {150.0};   // Single value
compressor.getCompressorChart().getSurgeCurve().setCurve(chartConditions, surgeFlow, surgeHead);

// Stone wall is also a single point
double[] stoneWallFlow = {9758.49};
double[] stoneWallHead = {112.65};
compressor.getCompressorChart().getStoneWallCurve().setCurve(chartConditions, stoneWallFlow, stoneWallHead);

📖 See Also: Compressor Curves and Performance Maps for detailed documentation on curve setup, interpolation methods, and Python examples.


Compressor Staging

Multi-Stage with Intercooling

ProcessSystem process = new ProcessSystem();

// Stage 1: 20 -> 50 bar
Compressor stage1 = new Compressor("K-100A", inletStream);
stage1.setOutletPressure(50.0, "bara");
stage1.setPolytropicEfficiency(0.78);
process.add(stage1);

// Intercooler
Cooler intercooler = new Cooler("E-100", stage1.getOutletStream());
intercooler.setOutTemperature(40.0, "C");
process.add(intercooler);

// Stage 2: 50 -> 120 bar
Compressor stage2 = new Compressor("K-100B", intercooler.getOutletStream());
stage2.setOutletPressure(120.0, "bara");
stage2.setPolytropicEfficiency(0.78);
process.add(stage2);

// Aftercooler
Cooler aftercooler = new Cooler("E-101", stage2.getOutletStream());
aftercooler.setOutTemperature(40.0, "C");
process.add(aftercooler);

process.run();

// Total power
double totalPower = stage1.getPower("kW") + stage2.getPower("kW");
System.out.println("Total compression power: " + totalPower + " kW");

Optimal Pressure Ratio

For minimum work with equal stage ratios:

$$r_{stage} = \left(\frac{P_{out}}{P_{in}}\right)^{1/n}$$

double overallRatio = 120.0 / 20.0;  // 6:1
int numStages = 2;
double stageRatio = Math.pow(overallRatio, 1.0 / numStages);  // √6 ≈ 2.45

Antisurge Control

// Recycle valve for antisurge
Splitter recycle = new Splitter("Antisurge Recycle", stage2.getOutletStream());
recycle.setSplitFactors(new double[]{0.9, 0.1});  // 10% recycle

// Mix with inlet
Mixer mixer = new Mixer("Inlet Mixer");
mixer.addStream(inletStream);
mixer.addStream(recycle.getSplitStream(1));

Power Calculation Details

Real Gas Effects

// Account for compressibility
double Z1 = compressor.getInletStream().getZ();
double Z2 = compressor.getOutletStream().getZ();
double Zavg = (Z1 + Z2) / 2;

Mechanical Efficiency

// Include mechanical losses
compressor.setMechanicalEfficiency(0.98);
double shaftPower = compressor.getShaftPower("kW");
double gasHorsepower = compressor.getGasHorsepower("hp");

Example: Export Gas Compression

// Export gas at 100 MSm³/day
SystemInterface gas = new SystemSrkEos(288.15, 30.0);
gas.addComponent("methane", 0.92);
gas.addComponent("ethane", 0.04);
gas.addComponent("propane", 0.02);
gas.addComponent("CO2", 0.01);
gas.addComponent("nitrogen", 0.01);
gas.setMixingRule("classic");

Stream inlet = new Stream("Inlet Gas", gas);
inlet.setFlowRate(100.0, "MSm3/day");
inlet.setTemperature(30.0, "C");
inlet.setPressure(30.0, "bara");

ProcessSystem process = new ProcessSystem();
process.add(inlet);

// 3-stage compression to 200 bar
double[] stagePressures = {55, 105, 200};

Stream currentStream = inlet;
for (int i = 0; i < 3; i++) {
    Compressor comp = new Compressor("K-10" + (i+1), currentStream);
    comp.setOutletPressure(stagePressures[i], "bara");
    comp.setPolytropicEfficiency(0.78);
    comp.setUsePolytropicCalc(true);
    process.add(comp);

    Cooler cooler = new Cooler("E-10" + (i+1), comp.getOutletStream());
    cooler.setOutTemperature(40.0, "C");
    process.add(cooler);

    currentStream = cooler.getOutletStream();
}

process.run();

// Report
System.out.println("\n=== Compression Summary ===");
double totalPower = 0;
for (int i = 1; i <= 3; i++) {
    Compressor c = (Compressor) process.getUnit("K-10" + i);
    totalPower += c.getPower("MW");
    System.out.printf("Stage %d: %.2f bar -> %.2f bar, %.2f MW%n",
        i, c.getInletStream().getPressure("bara"),
        c.getOutletStream().getPressure("bara"),
        c.getPower("MW"));
}
System.out.println("Total power: " + totalPower + " MW");

Automatic Curve Generation

When manufacturer performance data is not available, NeqSim can automatically generate realistic compressor curves using predefined templates.

Quick Start

// 1. Create and run compressor to establish design point
Compressor comp = new Compressor("K-100", inletStream);
comp.setOutletPressure(100.0, "bara");
comp.setPolytropicEfficiency(0.78);
comp.setSpeed(10000);
comp.run();

// 2. Generate curves from template
CompressorChartGenerator generator = new CompressorChartGenerator(comp);
CompressorChartInterface chart = generator.generateFromTemplate("PIPELINE", 5);

// 3. Apply and use
comp.setCompressorChart(chart);
comp.run();

Available Templates

Category Templates
Basic CENTRIFUGAL_STANDARD, CENTRIFUGAL_HIGH_FLOW, CENTRIFUGAL_HIGH_HEAD
Application PIPELINE, EXPORT, INJECTION, GAS_LIFT, REFRIGERATION, BOOSTER
Type SINGLE_STAGE, MULTISTAGE_INLINE, INTEGRALLY_GEARED, OVERHUNG

Template Selection

Use Case Recommended Template
Gas transmission PIPELINE
Offshore export EXPORT
Gas injection/EOR INJECTION
Artificial lift GAS_LIFT
LNG/refrigeration REFRIGERATION
General purpose CENTRIFUGAL_STANDARD

📖 Detailed Documentation: See Compressor Curves - Automatic Generation for complete API reference, advanced corrections, and examples.


Mechanical Losses and Seal Gas

NeqSim supports modeling of compressor mechanical losses and seal gas consumption per API 617 (bearings) and API 692 (dry gas seals).

Overview

The CompressorMechanicalLosses class models:

Component Standard Description
Dry Gas Seals API 692 Primary/secondary leakage, buffer gas, separation gas
Bearings API 617 Radial and thrust bearing friction losses
Lube Oil System API 614 Oil flow requirements and cooler duty

Quick Start

// Create and run compressor
Compressor compressor = new Compressor("K-100", inletStream);
compressor.setOutletPressure(100.0, "bara");
compressor.setSpeed(10000);
compressor.run();

// Initialize mechanical losses with shaft diameter (mm)
compressor.initMechanicalLosses(120.0);

// Get results
double sealGas = compressor.getSealGasConsumption();  // Nm³/hr total
double bearingLoss = compressor.getBearingLoss();     // kW
double mechEfficiency = compressor.getMechanicalEfficiency();  // 0-1

System.out.println("Seal gas consumption: " + sealGas + " Nm³/hr");
System.out.println("Bearing power loss: " + bearingLoss + " kW");
System.out.println("Mechanical efficiency: " + (mechEfficiency * 100) + "%");

Seal Types

CompressorMechanicalLosses losses = compressor.getMechanicalLosses();

// Available seal types
losses.setSealType(CompressorMechanicalLosses.SealType.DRY_GAS_TANDEM);  // Most common
losses.setSealType(CompressorMechanicalLosses.SealType.DRY_GAS_DOUBLE);  // Lower leakage
losses.setSealType(CompressorMechanicalLosses.SealType.DRY_GAS_SINGLE);  // Higher leakage
losses.setSealType(CompressorMechanicalLosses.SealType.LABYRINTH);       // Non-contacting
losses.setSealType(CompressorMechanicalLosses.SealType.OIL_FILM);        // Legacy

Seal Gas Flows (API 692)

Flow Type Description Typical Range
Primary Leakage Gas escaping past primary seal 0.5-5 Nm³/hr per seal
Secondary Leakage Gas past secondary seal (tandem) 10-30% of primary
Buffer Gas Clean gas between seals 2-10 Nm³/hr per seal
Separation Gas N₂ to prevent oil mist ingress 1-5 Nm³/hr per seal
// Individual seal gas flows
double primaryLeak = losses.calculatePrimarySealLeakage();    // Nm³/hr
double secondaryLeak = losses.calculateSecondarySealLeakage(); // Nm³/hr  
double bufferGas = losses.calculateBufferGasFlow();           // Nm³/hr
double separationGas = losses.calculateSeparationGasFlow();   // Nm³/hr

// Total consumption
double total = losses.getTotalSealGasConsumption();  // Nm³/hr

Bearing Types

// Available bearing types
losses.setBearingType(CompressorMechanicalLosses.BearingType.TILTING_PAD);    // Standard
losses.setBearingType(CompressorMechanicalLosses.BearingType.PLAIN_SLEEVE);   // Higher loss
losses.setBearingType(CompressorMechanicalLosses.BearingType.MAGNETIC_ACTIVE); // Very low loss
losses.setBearingType(CompressorMechanicalLosses.BearingType.GAS_FOIL);       // Oil-free

Bearing and Lube Oil Calculations (API 617)

// Bearing losses
double radialLoss = losses.calculateRadialBearingLoss();  // kW
double thrustLoss = losses.calculateThrustBearingLoss();  // kW
double totalBearingLoss = losses.getTotalBearingLoss();   // kW

// Lube oil system
losses.setLubeOilInletTemp(40.0);   // °C
losses.setLubeOilOutletTemp(55.0);  // °C

double oilFlow = losses.calculateLubeOilFlowRate();       // L/min
double coolerDuty = losses.calculateLubeOilCoolerDuty();  // kW

Configuration Options

CompressorMechanicalLosses losses = compressor.initMechanicalLosses(100.0);

// Seal configuration
losses.setSealType(CompressorMechanicalLosses.SealType.DRY_GAS_TANDEM);
losses.setNumberOfSeals(2);  // Typically 2 for single-shaft
losses.setSealGasSupplyPressure(105.0);  // bara
losses.setSealGasSupplyTemperature(40.0);  // °C

// Bearing configuration  
losses.setBearingType(CompressorMechanicalLosses.BearingType.TILTING_PAD);
losses.setNumberOfRadialBearings(2);

// Compressor updates operating conditions automatically
compressor.updateMechanicalLosses();

Complete Example

// Natural gas compressor with mechanical losses
SystemInterface gas = new SystemSrkEos(298.0, 50.0);
gas.addComponent("methane", 0.9);
gas.addComponent("ethane", 0.1);
gas.setMixingRule("classic");

Stream inlet = new Stream("inlet", gas);
inlet.setFlowRate(5000.0, "kg/hr");
inlet.run();

// Create and configure compressor
Compressor comp = new Compressor("HP Compressor", inlet);
comp.setOutletPressure(150.0, "bara");
comp.setPolytropicEfficiency(0.78);
comp.setSpeed(12000);
comp.run();

// Configure mechanical losses
CompressorMechanicalLosses losses = comp.initMechanicalLosses(120.0);
losses.setSealType(CompressorMechanicalLosses.SealType.DRY_GAS_TANDEM);
losses.setBearingType(CompressorMechanicalLosses.BearingType.TILTING_PAD);

// Print summary
System.out.println("=== Compressor Results ===");
System.out.println("Power: " + comp.getPower("kW") + " kW");
System.out.println("Polytropic head: " + comp.getPolytropicHead("kJ/kg") + " kJ/kg");
System.out.println();
System.out.println("=== Mechanical Losses ===");
System.out.println("Seal gas consumption: " + comp.getSealGasConsumption() + " Nm³/hr");
System.out.println("  - Primary leakage: " + losses.calculatePrimarySealLeakage() + " Nm³/hr");
System.out.println("  - Buffer gas: " + losses.calculateBufferGasFlow() + " Nm³/hr");
System.out.println("Bearing loss: " + comp.getBearingLoss() + " kW");
System.out.println("Mechanical efficiency: " + (comp.getMechanicalEfficiency() * 100) + "%");
System.out.println();
System.out.println("=== Lube Oil System ===");
System.out.println("Oil flow: " + losses.calculateLubeOilFlowRate() + " L/min");
System.out.println("Cooler duty: " + losses.calculateLubeOilCoolerDuty() + " kW");

Python Example

from jpype import JImplements, JOverride
from neqsim.thermo import fluid
from neqsim.process import compressor, stream

# Create gas and stream
gas = fluid('srk')
gas.addComponent('methane', 0.9)
gas.addComponent('ethane', 0.1)
gas.setMixingRule('classic')

inlet = stream(gas)
inlet.setFlowRate(5000.0, 'kg/hr')
inlet.setTemperature(25.0, 'C')
inlet.setPressure(50.0, 'bara')
inlet.run()

# Create compressor
comp = compressor(inlet)
comp.setOutletPressure(150.0)
comp.setPolytropicEfficiency(0.78)
comp.setSpeed(12000)
comp.run()

# Initialize mechanical losses (120mm shaft)
losses = comp.initMechanicalLosses(120.0)
losses.setSealType(losses.SealType.DRY_GAS_TANDEM)

# Get results
print(f"Seal gas consumption: {comp.getSealGasConsumption():.2f} Nm³/hr")
print(f"Bearing power loss: {comp.getBearingLoss():.2f} kW")
print(f"Mechanical efficiency: {comp.getMechanicalEfficiency()*100:.1f}%")

Compressor Curves

Compressor Curves and Performance Maps

Detailed documentation for compressor performance curves in NeqSim, including multi-speed and single-speed compressor handling, automatic curve generation, and predefined templates.

Table of Contents


Overview

NeqSim supports comprehensive compressor performance modeling through compressor charts (performance maps). The key classes are:

Class Description
CompressorChart Standard compressor chart with polynomial interpolation
CompressorChartKhader2015 Advanced chart with Khader 2015 method and fan law scaling
CompressorChartMWInterpolation Multi-map chart with MW interpolation between maps
CompressorChartGenerator Automatic curve generation from templates
CompressorCurveTemplate Predefined curve templates (12 available)
SafeSplineSurgeCurve Spline-based surge curve with safe extrapolation
SafeSplineStoneWallCurve Spline-based stone wall (choke) curve
CompressorCurve Individual speed curve (flow, head, efficiency)
CompressorMechanicalLosses Seal gas and bearing loss calculations (API 692/617)

Compressor Operating Envelope

                    Head
                     ↑
                     │        ╭──────────╮
                     │       ╱   Stone    ╲
                     │      ╱    Wall      ╲
              Surge │     ╱   (Choke)      ╲
              Curve │    ╱                  ╲
                    │   ╱                    ╲
                    │  ╱   Operating          ╲
                    │ ╱     Envelope           ╲
                    │╱                          ╲
                    └─────────────────────────────→ Flow
                         ↑                  ↑
                    Minimum Flow      Maximum Flow
                   (Surge Point)    (Stone Wall Point)

Varying Molecular Weight / Gas Composition

Compressor curves are typically measured at specific reference conditions (temperature, pressure, gas composition). When the actual operating fluid has a different molecular weight or composition, the curves must be corrected.

The Problem

Compressor performance maps are generated at specific reference conditions:

When the actual operating fluid differs from the reference:

Solution: CompressorChartKhader2015

NeqSim implements the Khader 2015 method in CompressorChartKhader2015 which automatically corrects curves for varying gas composition using dimensionless similarity parameters.

Mathematical Background

The method normalizes compressor map data using sound speed-based similarity:

Parameter Dimensionless Form Description
Corrected Head $H_{corr} = H / c_s^2$ Head normalized by sound speed squared
Corrected Flow $Q_{corr} = Q / (c_s \cdot D^2)$ Flow normalized by sound speed and diameter
Machine Mach Number $Ma = N \cdot D / c_s$ Speed normalized by sound speed

Where:

How It Works

  1. Reference curves are converted to dimensionless form using the reference fluid's sound speed
  2. At runtime, the actual fluid's sound speed is calculated
  3. Curves are scaled back to physical units using the actual sound speed
  4. Surge and stone wall curves are regenerated for the actual fluid

This approach ensures that when molecular weight changes, the operating envelope automatically adjusts.

Using CompressorChartKhader2015

import neqsim.process.equipment.compressor.CompressorChartKhader2015;

// Create fluid (this is the ACTUAL operating fluid)
SystemInterface operatingFluid = new SystemSrkEos(298.15, 50.0);
operatingFluid.addComponent("methane", 0.85);
operatingFluid.addComponent("ethane", 0.10);
operatingFluid.addComponent("propane", 0.05);
operatingFluid.setMixingRule("classic");

// Create stream
Stream stream = new Stream("inlet", operatingFluid);
stream.setFlowRate(5000.0, "Am3/hr");
stream.run();

// Create compressor with Khader 2015 chart
Compressor compressor = new Compressor("K-100", stream);
double impellerDiameter = 0.3;  // meters

// Create the Khader2015 chart - it takes the stream to get the actual fluid
CompressorChartKhader2015 chart = new CompressorChartKhader2015(stream, impellerDiameter);

// Chart conditions: [temperature (°C), pressure (bara), density (kg/m³), MW (g/mol)]
// The MW (4th element) is used to create a reference fluid for the curves
double[] chartConditions = new double[] {25.0, 50.0, 50.0, 20.0};

// Set curves at reference conditions
double[] speed = new double[] {10000, 11000, 12000};
double[][] flow = new double[][] {
    {3500, 4000, 4500, 5000, 5500},
    {3800, 4300, 4800, 5300, 5800},
    {4000, 4500, 5000, 5500, 6000}
};
double[][] head = new double[][] {
    {110, 105, 98, 88, 75},
    {128, 122, 114, 103, 90},
    {148, 141, 132, 120, 105}
};
double[][] polyEff = new double[][] {
    {77, 80, 81, 79, 74},
    {76, 79, 80, 78, 73},
    {75, 78, 79, 77, 72}
};

chart.setCurves(chartConditions, speed, flow, head, flow, polyEff);
chart.setHeadUnit("kJ/kg");

// Apply chart to compressor
compressor.setCompressorChart(chart);
compressor.setSpeed(11000);
compressor.run();

// The chart automatically corrects for the actual fluid's molecular weight!
System.out.println("Polytropic head: " + compressor.getPolytropicHead("kJ/kg"));
System.out.println("Polytropic efficiency: " + compressor.getPolytropicEfficiency());

Chart Conditions Array

The chartConditions array specifies the reference conditions:

Index Value Unit Description
0 Temperature °C Reference temperature
1 Pressure bara Reference pressure
2 Density kg/m³ Reference density (optional)
3 Molecular Weight g/mol Reference molecular weight

When the 4th element (molecular weight) is provided, the chart creates a reference fluid that matches this MW by blending methane, ethane, and propane in appropriate proportions.

Specifying a Custom Reference Fluid

For more control, you can provide your own reference fluid:

// Create reference fluid (the fluid the curves were measured with)
SystemInterface referenceFluid = new SystemSrkEos(298.15, 50.0);
referenceFluid.addComponent("methane", 0.90);
referenceFluid.addComponent("ethane", 0.07);
referenceFluid.addComponent("propane", 0.03);
referenceFluid.setMixingRule("classic");

// Create chart with both operating and reference fluids
CompressorChartKhader2015 chart = new CompressorChartKhader2015(
    operatingFluid,    // Actual fluid
    referenceFluid,    // Reference fluid (curves measured with this)
    impellerDiameter
);

chart.setCurves(chartConditions, speed, flow, head, flow, polyEff);

When the Fluid Changes at Runtime

If the fluid composition changes during simulation, regenerate the real curves:

// Fluid composition has changed...
operatingFluid.addComponent("CO2", 0.02);  // Added CO2
operatingFluid.init(0);

// Regenerate curves for new fluid
chart.generateRealCurvesForFluid();

// Run compressor with updated curves
compressor.run();

Comparison: Standard vs Khader2015 Chart

Feature CompressorChart CompressorChartKhader2015
MW Correction Manual Automatic
Method Polynomial interpolation Sound speed scaling
Impeller diameter Not required Required
Fluid dependency None Uses stream fluid
Best for Fixed composition Variable composition

Limitations


Multi-Map MW Interpolation

When you have compressor performance maps measured at multiple discrete molecular weights, use CompressorChartMWInterpolation to interpolate between maps based on the actual operating MW.

Quick Start

The simplest way to use multi-MW charts is to let the compressor automatically detect the gas MW:

// 1. Create chart and add maps at different MW values
CompressorChartMWInterpolation chart = new CompressorChartMWInterpolation();
chart.setHeadUnit("kJ/kg");
chart.setAutoGenerateSurgeCurves(true);
chart.setAutoGenerateStoneWallCurves(true);

chart.addMapAtMW(18.0, chartConditions, speed, flow18, head18, polyEff18);
chart.addMapAtMW(22.0, chartConditions, speed, flow22, head22, polyEff22);

// 2. Set chart on compressor
Compressor comp = new Compressor("K-100", inletStream);
comp.setCompressorChart(chart);
comp.setOutletPressure(60.0);
comp.setSpeed(11000);

// 3. Run - MW is automatically detected from inlet stream
comp.run();

// The chart now uses the actual gas MW from the inlet stream
System.out.println("Operating MW: " + chart.getOperatingMW() + " g/mol");

Key features:

When to Use Multi-Map Interpolation

Use this approach when:

How It Works

  1. Add multiple maps: Each map is defined at a specific molecular weight
  2. Compressor run(): Automatically sets inlet stream on the chart
  3. Chart uses actual MW: Gets MW from inletStream.getFluid().getMolarMass()
  4. Automatic interpolation: Head, efficiency, surge, and stone wall are linearly interpolated between the two nearest MW maps

Java Example

import neqsim.process.equipment.compressor.CompressorChartMWInterpolation;

// Create MW interpolation chart
CompressorChartMWInterpolation chart = new CompressorChartMWInterpolation();
chart.setHeadUnit("kJ/kg");

double[] chartConditions = new double[] {25.0, 50.0, 50.0, 20.0};

// Map at MW = 18 g/mol (lighter gas - higher head capacity)
double[] speed18 = {10000, 11000, 12000};
double[][] flow18 = {
    {3000, 3500, 4000, 4500, 5000},
    {3300, 3800, 4300, 4800, 5300},
    {3600, 4100, 4600, 5100, 5600}
};
double[][] head18 = {
    {120, 115, 108, 98, 85},
    {138, 132, 124, 113, 98},
    {158, 151, 142, 130, 113}
};
double[][] polyEff18 = {
    {75, 78, 80, 78, 73},
    {74, 77, 79, 77, 72},
    {73, 76, 78, 76, 71}
};
chart.addMapAtMW(18.0, chartConditions, speed18, flow18, head18, polyEff18);

// Map at MW = 22 g/mol (heavier gas - lower head capacity)
double[] speed22 = {10000, 11000, 12000};
double[][] flow22 = {
    {2800, 3300, 3800, 4300, 4800},
    {3100, 3600, 4100, 4600, 5100},
    {3400, 3900, 4400, 4900, 5400}
};
double[][] head22 = {
    {100, 96, 90, 82, 71},
    {115, 110, 103, 94, 82},
    {132, 126, 118, 108, 94}
};
double[][] polyEff22 = {
    {73, 76, 78, 76, 71},
    {72, 75, 77, 75, 70},
    {71, 74, 76, 74, 69}
};
chart.addMapAtMW(22.0, chartConditions, speed22, flow22, head22, polyEff22);

// Set current operating MW (e.g., from actual fluid composition)
double actualMW = 20.0;  // Midpoint - will interpolate 50/50 between maps
chart.setOperatingMW(actualMW);

// Get interpolated values
double flow = 3500.0;  // m³/hr
double speed = 10000;  // RPM
double polytropicHead = chart.getPolytropicHead(flow, speed);
double efficiency = chart.getPolytropicEfficiency(flow, speed);

System.out.println("Operating MW: " + actualMW + " g/mol");
System.out.println("Interpolated polytropic head: " + polytropicHead + " kJ/kg");
System.out.println("Interpolated efficiency: " + efficiency + " %");

// Apply to compressor
Compressor comp = new Compressor("K-100", stream);
comp.setCompressorChart(chart);
comp.setSpeed(10000);
comp.run();

Adding More Maps

You can add any number of MW maps. The chart will interpolate between the two nearest:

// Add a third map at MW = 20 g/mol for better accuracy
chart.addMapAtMW(20.0, chartConditions, speed20, flow20, head20, polyEff20);

// Maps are automatically sorted by MW
// Interpolation at MW=19 will use maps at MW=18 and MW=20
// Interpolation at MW=21 will use maps at MW=20 and MW=22

Automatic MW Detection from Inlet Stream

By default, the compressor automatically updates the chart with its inlet stream during each run() call. The chart then uses the actual molecular weight from the inlet stream's fluid. This means you don't need to manually call setOperatingMW() - it's automatically updated each time the compressor runs:

// Create MW interpolation chart
CompressorChartMWInterpolation chart = new CompressorChartMWInterpolation();
chart.setHeadUnit("kJ/kg");

// Add MW maps
chart.addMapAtMW(18.0, chartConditions, speed18, flow18, head18, polyEff18);
chart.addMapAtMW(22.0, chartConditions, speed22, flow22, head22, polyEff22);

// Set the chart on the compressor
Compressor comp = new Compressor("K-100", inletStream);
comp.setCompressorChart(chart);
comp.setOutletPressure(60.0);

// When the compressor runs, it automatically:
// 1. Sets the inlet stream on the chart
// 2. The chart uses the stream's MW for interpolation
comp.run();

// Check the operating MW that was used
System.out.println("Operating MW: " + chart.getOperatingMW() + " g/mol");

// If the gas composition changes, just run again
// The MW will be automatically updated
process.run();  // Chart uses new MW automatically

Manual Inlet Stream Setup (Optional)

You can also manually set the inlet stream on the chart if needed:

chart.setInletStream(compressorInletStream);

// MW is automatically updated when calling chart methods
double head = chart.getPolytropicHead(flow, speed);  // Uses stream's current MW

Disabling Auto MW Detection

If you want to manually control the operating MW:

// Disable auto MW detection
chart.setUseActualMW(false);

// Now you must set MW manually
chart.setOperatingMW(20.0);

Extrapolation Behavior

By default, when the operating MW is outside the range of available maps, the chart uses the boundary map:

Operating MW Default Behavior
Below lowest map MW Uses lowest MW map (no extrapolation)
Between maps Linear interpolation
Above highest map MW Uses highest MW map (no extrapolation)

Enabling Extrapolation

Enable extrapolation to linearly extend beyond the MW range:

// Enable extrapolation outside MW range
chart.setAllowExtrapolation(true);

// Now values are extrapolated when MW is outside the map range
chart.setOperatingMW(16.0);  // Below lowest map (18.0)
double head = chart.getPolytropicHead(flow, speed);  // Extrapolated value

chart.setOperatingMW(26.0);  // Above highest map (22.0)
double eff = chart.getPolytropicEfficiency(flow, speed);  // Extrapolated value

Caution: Extrapolation can produce unrealistic values if the MW is too far outside the measured range. Use with appropriate limits.

Surge and Stone Wall Curves

Generate surge and stone wall curves for all maps:

// Generate curves for all MW maps
chart.generateAllSurgeCurves();
chart.generateAllStoneWallCurves();

// Or set curves manually for each MW
chart.setSurgeCurveAtMW(18.0, chartConditions, surgeFlow18, surgeHead18);
chart.setSurgeCurveAtMW(22.0, chartConditions, surgeFlow22, surgeHead22);

chart.setStoneWallCurveAtMW(18.0, chartConditions, stoneWallFlow18, stoneWallHead18);
chart.setStoneWallCurveAtMW(22.0, chartConditions, stoneWallFlow22, stoneWallHead22);

Auto-Generate Curves When Adding Maps

Enable auto-generation before adding maps:

CompressorChartMWInterpolation chart = new CompressorChartMWInterpolation();
chart.setHeadUnit("kJ/kg");

// Enable auto-generation of surge and stone wall curves
chart.setAutoGenerateSurgeCurves(true);
chart.setAutoGenerateStoneWallCurves(true);

// Surge and stone wall are now automatically generated when maps are added
chart.addMapAtMW(18.0, chartConditions, speed18, flow18, head18, polyEff18);
chart.addMapAtMW(22.0, chartConditions, speed22, flow22, head22, polyEff22);

Interpolated Surge and Stone Wall Methods

Check operating limits using interpolated curves:

chart.setOperatingMW(20.0);
double head = 100.0;  // kJ/kg
double flow = 3500.0; // m³/hr

// Get interpolated surge flow at a given head
double surgeFlow = chart.getSurgeFlow(head);
double stoneWallFlow = chart.getStoneWallFlow(head);

// Check if operating point is in surge or choked
boolean inSurge = chart.isSurge(head, flow);
boolean isChoked = chart.isStoneWall(head, flow);

// Get distance to operating limits
double distanceToSurge = chart.getDistanceToSurge(head, flow);     // Positive = above surge
double distanceToStoneWall = chart.getDistanceToStoneWall(head, flow); // Positive = below stone wall

System.out.println("Surge flow: " + surgeFlow + " m³/hr");
System.out.println("Stone wall flow: " + stoneWallFlow + " m³/hr");
System.out.println("In surge: " + inSurge);
System.out.println("Is choked: " + isChoked);
System.out.println("Distance to surge: " + (distanceToSurge * 100) + "%");
System.out.println("Distance to stone wall: " + (distanceToStoneWall * 100) + "%");

Simplified Method Signatures

For convenience, you can omit chartConditions - default conditions will be generated automatically based on the MW:

Multi-Speed Compressor (without chartConditions)

// Simplest form: (MW, speed[], flow[][], head[][], efficiency[][])
chart.addMapAtMW(18.0, speeds, flow18, head18, polyEff18);
chart.addMapAtMW(22.0, speeds, flow22, head22, polyEff22);

// With separate flow arrays: (MW, speed[], flow[][], head[][], flowEff[][], efficiency[][])
chart.addMapAtMW(20.0, speeds, flowHead, heads, flowEff, effs);

Complete multi-speed example:

{% raw %}

CompressorChartMWInterpolation chart = new CompressorChartMWInterpolation();
chart.setHeadUnit("kJ/kg");
chart.setAutoGenerateSurgeCurves(true);
chart.setAutoGenerateStoneWallCurves(true);

double[] speeds = {10000, 11000, 12000};  // Multiple speeds (RPM)

// Map at MW = 18 g/mol
double[][] flow18 = {{3000, 3500, 4000, 4500, 5000}, 
                     {3300, 3800, 4300, 4800, 5300},
                     {3600, 4100, 4600, 5100, 5600}};
double[][] head18 = {{120, 115, 108, 98, 85}, 
                     {138, 132, 124, 113, 98},
                     {158, 151, 142, 130, 113}};
double[][] eff18 = {{75, 78, 80, 78, 73}, 
                    {74, 77, 79, 77, 72},
                    {73, 76, 78, 76, 71}};
chart.addMapAtMW(18.0, speeds, flow18, head18, eff18);  // No chartConditions needed!

// Map at MW = 22 g/mol
double[][] flow22 = {{2800, 3300, 3800, 4300, 4800}, 
                     {3100, 3600, 4100, 4600, 5100},
                     {3400, 3900, 4400, 4900, 5400}};
double[][] head22 = {{100, 96, 90, 82, 71}, 
                     {115, 110, 103, 94, 82},
                     {132, 126, 118, 108, 94}};
double[][] eff22 = {{73, 76, 78, 76, 71}, 
                    {72, 75, 77, 75, 70},
                    {71, 74, 76, 74, 69}};
chart.addMapAtMW(22.0, speeds, flow22, head22, eff22);

// Use with compressor
Compressor comp = new Compressor("K-100", inletStream);
comp.setCompressorChart(chart);
comp.setSpeed(11000);
comp.run();

{% endraw %}

Single-Speed Compressor (without chartConditions)

For single-speed compressors, you can use simplified method signatures with 1D arrays:

// Simplest form: (MW, speed, flow[], head[], efficiency[])
chart.addMapAtMW(18.0, 10000, flow, head, polyEff);
chart.addMapAtMW(22.0, 10000, flow22, head22, polyEff22);

// With separate flow arrays for efficiency: (MW, speed, flow[], head[], flowEff[], efficiency[])
chart.addMapAtMW(20.0, 10000, flowHead, head, flowEff, polyEff);

Complete single-speed example:

CompressorChartMWInterpolation chart = new CompressorChartMWInterpolation();
chart.setHeadUnit("kJ/kg");
chart.setAutoGenerateSurgeCurves(true);
chart.setAutoGenerateStoneWallCurves(true);

double speed = 10000;  // Single speed (RPM)

// Map at MW = 18 g/mol
double[] flow18 = {3000, 3500, 4000, 4500, 5000};
double[] head18 = {120, 115, 108, 98, 85};
double[] eff18 = {75, 78, 80, 78, 73};
chart.addMapAtMW(18.0, speed, flow18, head18, eff18);

// Map at MW = 22 g/mol
double[] flow22 = {2800, 3300, 3800, 4300, 4800};
double[] head22 = {100, 96, 90, 82, 71};
double[] eff22 = {73, 76, 78, 76, 71};
chart.addMapAtMW(22.0, speed, flow22, head22, eff22);

// Use with compressor
Compressor comp = new Compressor("K-100", inletStream);
comp.setCompressorChart(chart);
comp.setSpeed(speed);
comp.run();

Separate Flow Arrays for Head and Efficiency

When efficiency is measured at different flow points than head:

{% raw %}

double[] speeds = {10000, 11000};

// Flow and head arrays
double[][] flowHead = {{3000, 3500, 4000, 4500}, {3300, 3800, 4300, 4800}};
double[][] heads = {{120, 115, 108, 98}, {138, 132, 124, 113}};

// Efficiency measured at different flow points
double[][] flowEff = {{3100, 3600, 4100}, {3400, 3900, 4400}};
double[][] effs = {{76, 79, 77}, {75, 78, 76}};

// Use the 6-argument version of addMapAtMW
chart.addMapAtMW(20.0, chartConditions, speeds, flowHead, heads, flowEff, effs);

{% endraw %}

Python Example

from jpype import JClass

CompressorChartMWInterpolation = JClass(
    'neqsim.process.equipment.compressor.CompressorChartMWInterpolation'
)

# Create multi-map chart
chart = CompressorChartMWInterpolation()
chart.setHeadUnit("kJ/kg")

chart_conditions = [25.0, 50.0, 50.0, 20.0]

# Add map at MW = 18 g/mol
speed_18 = [10000, 11000, 12000]
flow_18 = [[3000, 3500, 4000], [3300, 3800, 4300], [3600, 4100, 4600]]
head_18 = [[120, 115, 108], [138, 132, 124], [158, 151, 142]]
eff_18 = [[75, 78, 80], [74, 77, 79], [73, 76, 78]]
chart.addMapAtMW(18.0, chart_conditions, speed_18, flow_18, head_18, eff_18)

# Add map at MW = 22 g/mol
speed_22 = [10000, 11000, 12000]
flow_22 = [[2800, 3300, 3800], [3100, 3600, 4100], [3400, 3900, 4400]]
head_22 = [[100, 96, 90], [115, 110, 103], [132, 126, 118]]
eff_22 = [[73, 76, 78], [72, 75, 77], [71, 74, 76]]
chart.addMapAtMW(22.0, chart_conditions, speed_22, flow_22, head_22, eff_22)

# Set operating MW and get interpolated values
chart.setOperatingMW(20.0)
head = chart.getPolytropicHead(3500.0, 10000)
eff = chart.getPolytropicEfficiency(3500.0, 10000)

print(f"Interpolated head at MW=20: {head:.2f} kJ/kg")
print(f"Interpolated efficiency at MW=20: {eff:.1f} %")

Complete Python Example with Automatic MW Detection

This example shows a full workflow where the compressor automatically uses the actual gas MW:

from jpype import JClass
from neqsim.thermo import fluid
from neqsim.process import stream, compressor, runProcess

# Import MW interpolation chart
CompressorChartMWInterpolation = JClass(
    'neqsim.process.equipment.compressor.CompressorChartMWInterpolation'
)

# Create gas with specific composition
gas = fluid("srk")
gas.addComponent("methane", 0.85)
gas.addComponent("ethane", 0.10)
gas.addComponent("propane", 0.05)
gas.setTemperature(25.0, "C")
gas.setPressure(30.0, "bara")
gas.setTotalFlowRate(5000.0, "Am3/hr")
gas.setMixingRule("classic")

# Create inlet stream
inlet_stream = stream(gas)
inlet_stream.run()

print(f"Inlet gas MW: {inlet_stream.getFluid().getMolarMass('kg/mol') * 1000:.2f} g/mol")

# Create MW interpolation chart with maps at MW = 18 and 22 g/mol
chart = CompressorChartMWInterpolation()
chart.setHeadUnit("kJ/kg")

chart_conditions = [25.0, 50.0, 50.0, 20.0]

# Map at MW = 18 g/mol (lighter gas)
speed = [10000, 11000, 12000]
flow_18 = [[3000, 3500, 4000, 4500, 5000],
           [3300, 3800, 4300, 4800, 5300],
           [3600, 4100, 4600, 5100, 5600]]
head_18 = [[120, 115, 108, 98, 85],
           [138, 132, 124, 113, 98],
           [158, 151, 142, 130, 113]]
eff_18 = [[75, 78, 80, 78, 73],
          [74, 77, 79, 77, 72],
          [73, 76, 78, 76, 71]]

# Map at MW = 22 g/mol (heavier gas)
flow_22 = [[2800, 3300, 3800, 4300, 4800],
           [3100, 3600, 4100, 4600, 5100],
           [3400, 3900, 4400, 4900, 5400]]
head_22 = [[100, 96, 90, 82, 71],
           [115, 110, 103, 94, 82],
           [132, 126, 118, 108, 94]]
eff_22 = [[73, 76, 78, 76, 71],
          [72, 75, 77, 75, 70],
          [71, 74, 76, 74, 69]]

# Add maps and enable auto-generation of surge/stone wall curves
chart.setAutoGenerateSurgeCurves(True)
chart.setAutoGenerateStoneWallCurves(True)
chart.addMapAtMW(18.0, chart_conditions, speed, flow_18, head_18, eff_18)
chart.addMapAtMW(22.0, chart_conditions, speed, flow_22, head_22, eff_22)

# Create compressor and set the chart
comp = compressor(inlet_stream)
comp.setOutletPressure(60.0, "bara")
comp.setSpeed(11000)
comp.setCompressorChart(chart)

# Run - MW is automatically detected from inlet stream
comp.run()

print(f"\nAfter compressor run:")
print(f"Chart operating MW: {chart.getOperatingMW():.2f} g/mol")
print(f"Polytropic head: {comp.getPolytropicHead('kJ/kg'):.2f} kJ/kg")
print(f"Polytropic efficiency: {comp.getPolytropicEfficiency() * 100:.1f}%")
print(f"Outlet pressure: {comp.getOutletStream().getPressure('bara'):.1f} bara")
print(f"Power: {comp.getPower('kW'):.1f} kW")

# Check surge/stone wall margins
print(f"\nOperating margins:")
print(f"Distance to surge: {comp.getDistanceToSurge() * 100:.1f}%")
print(f"Distance to stone wall: {comp.getDistanceToStoneWall() * 100:.1f}%")

# Change gas composition and run again
gas.addComponent("CO2", 0.03)
gas.init(0)
inlet_stream.run()
comp.run()

print(f"\nAfter adding CO2:")
print(f"New gas MW: {inlet_stream.getFluid().getMolarMass('kg/mol') * 1000:.2f} g/mol")
print(f"Chart operating MW: {chart.getOperatingMW():.2f} g/mol")
print(f"Polytropic head: {comp.getPolytropicHead('kJ/kg'):.2f} kJ/kg")

Enabling Extrapolation in Python

# Enable extrapolation outside the MW range
chart.setAllowExtrapolation(True)

# Now values can be extrapolated for MW outside 18-22 range
# For example, with a very light gas (MW = 16 g/mol)
# or a heavy gas (MW = 26 g/mol)

Disabling Auto MW Detection in Python

# Disable automatic MW detection from stream
chart.setUseActualMW(False)

# Now you must manually set the MW
chart.setOperatingMW(20.0)

Comparison: Multi-Map vs Khader2015

Feature CompressorChartMWInterpolation CompressorChartKhader2015
Input data Multiple measured maps Single reference map
Correction method Linear interpolation Sound speed scaling
Accuracy Higher (measured data) Theoretical approximation
Data requirements Maps at multiple MW One map + impeller diameter
Best for Validated multi-MW data When only one map available

Multi-Speed Compressors

Multi-speed (variable speed) compressors have performance curves at multiple rotational speeds. NeqSim interpolates between these curves to determine performance at any operating speed.

Setting Up Multi-Speed Curves

// Chart conditions: [temperature (°C), pressure (bara), density (kg/m³), MW (g/mol)]
double[] chartConditions = new double[] {25.0, 50.0, 50.0, 20.0};

// Multiple speeds (RPM)
double[] speed = new double[] {8000, 9000, 10000, 11000, 12000};

// Flow arrays for each speed (m³/hr at chart conditions)
double[][] flow = new double[][] {
    {3000, 3500, 4000, 4500, 5000},  // Speed 8000 RPM
    {3200, 3700, 4200, 4700, 5200},  // Speed 9000 RPM
    {3500, 4000, 4500, 5000, 5500},  // Speed 10000 RPM
    {3800, 4300, 4800, 5300, 5800},  // Speed 11000 RPM
    {4000, 4500, 5000, 5500, 6000}   // Speed 12000 RPM
};

// Head arrays for each speed (kJ/kg)
double[][] head = new double[][] {
    {80, 78, 74, 68, 60},  // Speed 8000 RPM
    {95, 92, 88, 82, 72},  // Speed 9000 RPM
    {110, 106, 101, 94, 85}, // Speed 10000 RPM
    {128, 123, 117, 109, 99}, // Speed 11000 RPM
    {145, 140, 133, 124, 112} // Speed 12000 RPM
};

// Polytropic efficiency arrays for each speed (%)
double[][] polyEff = new double[][] {
    {75, 78, 80, 79, 75},
    {76, 79, 81, 80, 76},
    {77, 80, 82, 81, 77},
    {76, 79, 81, 80, 76},
    {75, 78, 80, 79, 75}
};

// Set curves on compressor
compressor.getCompressorChart().setCurves(chartConditions, speed, flow, head, flow, polyEff);
compressor.getCompressorChart().setHeadUnit("kJ/kg");
compressor.setSpeed(10000);  // Operating speed

Automatic Surge and Stone Wall Generation

For multi-speed compressors with 2 or more speed curves, NeqSim automatically generates surge and stone wall curves:

// Automatic generation from chart data
compressor.getCompressorChart().generateSurgeCurve();
compressor.getCompressorChart().generateStoneWallCurve();

The surge curve is created by connecting the minimum flow points from each speed curve. The stone wall curve is created by connecting the maximum flow points from each speed curve.


Single-Speed Compressors

Single-speed (fixed speed) compressors operate at a constant rotational speed. For these compressors:

Setting Up Single-Speed Curves

// Chart conditions
double[] chartConditions = new double[] {25.0, 50.0, 50.0, 20.0};

// Single speed
double[] speed = new double[] {10250};

// Single speed curve
double[][] flow = new double[][] {
    {5607, 6008, 6480, 7112, 7800, 8180, 8509, 8750, 9007, 9758}
};
double[][] head = new double[][] {
    {150.0, 149.5, 148.8, 148.0, 146.1, 144.8, 143.0, 140.7, 137.3, 112.6}
};
double[][] polyEff = new double[][] {
    {78, 79, 80, 80.5, 80, 79.5, 79, 78, 77, 70}
};

compressor.setSpeed(10250);
compressor.getCompressorChart().setCurves(chartConditions, speed, flow, head, flow, polyEff);
compressor.getCompressorChart().setHeadUnit("kJ/kg");

Single-Point Surge and Stone Wall

For single-speed compressors, surge and stone wall are single points, not curves. NeqSim supports setting these directly:

// Single-point surge (minimum flow point on the curve)
double[] surgeFlow = new double[] {5607.45};  // Single value
double[] surgeHead = new double[] {150.0};    // Corresponding head
compressor.getCompressorChart().getSurgeCurve().setCurve(chartConditions, surgeFlow, surgeHead);

// Single-point stone wall (maximum flow point on the curve)
double[] stoneWallFlow = new double[] {9758.49};  // Single value
double[] stoneWallHead = new double[] {112.65};   // Corresponding head
compressor.getCompressorChart().getStoneWallCurve().setCurve(chartConditions, stoneWallFlow, stoneWallHead);

Key Differences from Multi-Speed:

Aspect Multi-Speed Single-Speed
Surge/Stone Wall Curves (interpolated) Single points (constant)
Speed Adjustment Can vary speed to avoid limits Fixed speed
Control Strategy Speed + recycle control Recycle control only
Curve Points Required ≥2 points per curve 1 point (single point)

Surge Curves

The surge curve defines the minimum stable flow at each head value. Operating below this flow causes unstable, potentially damaging oscillations.

Surge Curve Implementation

NeqSim uses SafeSplineSurgeCurve which provides:

Setting Surge Curve Manually

// Multi-speed: Multiple flow/head points
double[] surgeFlow = {4512.7, 4862.5, 5237.8, 5642.9, 6221.8, 6888.9, 7109.8, 7598.9};
double[] surgeHead = {61.9, 71.4, 81.6, 92.5, 103.5, 114.9, 118.6, 126.7};
compressor.getCompressorChart().getSurgeCurve().setCurve(chartConditions, surgeFlow, surgeHead);

// Single-speed: Single point
double[] singleSurgeFlow = {5607.45};
double[] singleSurgeHead = {150.0};
compressor.getCompressorChart().getSurgeCurve().setCurve(chartConditions, singleSurgeFlow, singleSurgeHead);

Checking Surge Status

// Check if operating point is in surge
boolean inSurge = compressor.getCompressorChart().getSurgeCurve().isSurge(head, flow);

// Get surge flow at current head
double surgeFlow = compressor.getSurgeFlowRate();

// Get margin above surge
double surgeMargin = compressor.getSurgeFlowRateMargin();  // m³/hr above surge

Stone Wall (Choke) Curves

The stone wall (choke) curve defines the maximum flow at each head value. At this limit, the gas velocity approaches sonic conditions and flow cannot increase further.

Stone Wall Curve Implementation

NeqSim uses SafeSplineStoneWallCurve which provides:

Setting Stone Wall Curve Manually

// Multi-speed: Multiple flow/head points
double[] stoneWallFlow = {6500, 7200, 8000, 8800, 9600};
double[] stoneWallHead = {55, 70, 88, 108, 130};
compressor.getCompressorChart().getStoneWallCurve().setCurve(chartConditions, stoneWallFlow, stoneWallHead);

// Single-speed: Single point
double[] singleStoneWallFlow = {9758.49};
double[] singleStoneWallHead = {112.65};
compressor.getCompressorChart().getStoneWallCurve().setCurve(chartConditions, singleStoneWallFlow, singleStoneWallHead);

Checking Stone Wall Status

// Check if operating point is at stone wall
boolean atStoneWall = compressor.isStoneWall();

// Check with explicit flow/head
boolean atStoneWall = compressor.getCompressorChart().getStoneWallCurve().isStoneWall(head, flow);

Distance to Operating Limits

NeqSim provides methods to calculate the margin from operating limits:

Distance to Surge

// Returns ratio: (current flow / surge flow) - 1
// Positive = above surge, Negative = in surge
double distanceToSurge = compressor.getDistanceToSurge();

// Example: 0.25 means operating 25% above surge flow
System.out.println("Operating " + (distanceToSurge * 100) + "% above surge");

Distance to Stone Wall

// Returns ratio: (stone wall flow / current flow) - 1
// Positive = below stone wall, Zero/Negative = at/beyond choke
double distanceToStoneWall = compressor.getDistanceToStoneWall();

// Example: 0.40 means stone wall is 40% above current flow
System.out.println("Stone wall is " + (distanceToStoneWall * 100) + "% above current flow");

How Distance Calculations Work

For multi-speed compressors (curve is active):

Distance to Surge = (Operating Flow / Surge Flow at Current Head) - 1
Distance to Stone Wall = (Stone Wall Flow at Current Head / Operating Flow) - 1

For single-speed compressors (single-point curve):

Distance to Surge = (Operating Flow / Single Surge Flow Point) - 1
Distance to Stone Wall = (Single Stone Wall Flow Point / Operating Flow) - 1

Speed Calculation from Operating Point

When you need to determine the compressor speed required to achieve a specific operating point (flow and head), NeqSim provides a robust algorithm that works both within the defined curve range and with extrapolation beyond it.

The getSpeed() and getSpeedValue() Methods

// Get speed as integer (legacy method)
int speed = chart.getSpeed(flow, head);

// Get speed as double for full precision
double preciseSpeed = chart.getSpeedValue(flow, head);

Algorithm Details

The speed calculation uses a hybrid approach for robustness:

  1. Fan-law Initial Guess: Uses the relationship $H \propto N^2$ to estimate: $$N_{guess} = N_{ref} \times \sqrt{\frac{H_{target}}{H_{ref}}}$$

  2. Damped Newton-Raphson Iteration: Fast convergence with safeguards:

    • 70% damping factor to prevent oscillation
    • Maximum 30% step change per iteration
    • Automatic gradient estimation using finite differences
    • Fallback to fan-law derivative when gradient is near zero
  3. Bounds Protection: Prevents divergence:

    • Lower bound: 50% of minimum curve speed
    • Upper bound: 150% of maximum curve speed
  4. Bisection Fallback: Guaranteed convergence if Newton-Raphson fails:

    • Automatic bounds extension if needed
    • Binary search to find the root

Speed Limit Checking

The compressor chart and compressor classes provide methods to check if the calculated speed is within the defined curve range:

// Using the compressor chart directly
CompressorChartInterface chart = compressor.getCompressorChart();
double speed = chart.getSpeedValue(flow, head);

// Check speed limits on chart
boolean tooHigh = chart.isHigherThanMaxSpeed(speed);
boolean tooLow = chart.isLowerThanMinSpeed(speed);
boolean inRange = chart.isSpeedWithinRange(speed);

// Get ratios (useful for warnings)
double ratioToMax = chart.getRatioToMaxSpeed(speed);  // >1.0 means above max
double ratioToMin = chart.getRatioToMinSpeed(speed);  // <1.0 means below min

// Using the compressor (uses current operating speed)
compressor.run();
boolean currentSpeedTooHigh = compressor.isHigherThanMaxSpeed();
boolean currentSpeedTooLow = compressor.isLowerThanMinSpeed();
double currentRatioToMax = compressor.getRatioToMaxSpeed();

// Or check a specific speed
boolean testSpeedTooHigh = compressor.isHigherThanMaxSpeed(12000);

When Speed is Outside Curve Range

When the calculated speed falls outside the defined performance curves, the algorithm uses fan-law extrapolation:

This allows reasonable estimates for speeds up to 50% beyond the curve boundaries.

Example: Speed Calculation with Limit Checking

// Set up compressor
Compressor comp = new Compressor("K-100", inlet);
comp.setOutletPressure(120.0, "bara");
comp.setCompressorChart(chart);

// Run and check speed limits
comp.run();

// Get the calculated speed
double speed = comp.getSpeed();

// Check if operating within design envelope
if (comp.isHigherThanMaxSpeed()) {
    double ratio = comp.getRatioToMaxSpeed();
    System.out.println("WARNING: Speed " + ratio * 100 + "% of max curve speed");
    System.out.println("Compressor may be undersized for this duty");
} else if (comp.isLowerThanMinSpeed()) {
    double ratio = comp.getRatioToMinSpeed();
    System.out.println("WARNING: Speed " + ratio * 100 + "% of min curve speed");
    System.out.println("Compressor may be oversized - turndown issues");
} else {
    System.out.println("Operating within design envelope");
}

Python Example

# Get calculated speed
speed = chart.getSpeedValue(flow, head)

# Check speed limits
if chart.isHigherThanMaxSpeed(speed):
    ratio = chart.getRatioToMaxSpeed(speed)
    print(f"Speed {speed:.0f} RPM is {ratio:.1%} of max curve speed")
elif chart.isLowerThanMinSpeed(speed):
    ratio = chart.getRatioToMinSpeed(speed)
    print(f"Speed {speed:.0f} RPM is {ratio:.1%} of min curve speed")
else:
    print(f"Speed {speed:.0f} RPM is within curve range")

# Using compressor methods with current operating speed
comp.run()
if comp.isHigherThanMaxSpeed():
    print(f"Current speed exceeds max by {(comp.getRatioToMaxSpeed() - 1) * 100:.1f}%")

Anti-Surge Control

Surge Control Factor

The anti-surge system adds a safety margin to the surge limit:

// Default surge control factor is 1.05 (5% safety margin)
compressor.getAntiSurge().setSurgeControlFactor(1.10);  // 10% margin

// Safe minimum flow = Surge Flow × Surge Control Factor
double safeMinFlow = compressor.getSurgeFlowRate() * compressor.getAntiSurge().getSurgeControlFactor();

Anti-Surge Configuration

AntiSurge antiSurge = compressor.getAntiSurge();
antiSurge.setActive(true);
antiSurge.setSurgeControlFactor(1.10);  // 10% margin above surge

// Check current status
boolean isSurging = antiSurge.isSurge();
double controlFactor = antiSurge.getSurgeControlFactor();

API Reference

CompressorChartInterface Methods

Speed Calculation and Limits

Method Description
getSpeed(flow, head) Calculate speed for given flow and head (returns int)
getSpeedValue(flow, head) Calculate speed for given flow and head (returns double for precision)
getMinSpeedCurve() Get minimum speed from defined curves (RPM)
getMaxSpeedCurve() Get maximum speed from defined curves (RPM)
isHigherThanMaxSpeed(speed) Check if speed exceeds maximum curve speed
isLowerThanMinSpeed(speed) Check if speed is below minimum curve speed
isSpeedWithinRange(speed) Check if speed is within [min, max] range
getRatioToMaxSpeed(speed) Get ratio speed/maxSpeed (>1.0 means above max)
getRatioToMinSpeed(speed) Get ratio speed/minSpeed (<1.0 means below min)

Performance Lookup

Method Description
getPolytropicHead(flow, speed) Get head at given flow and speed
getPolytropicEfficiency(flow, speed) Get efficiency at given flow and speed
getFlow(head, speed, guessFlow) Get flow for given head and speed

Surge and Stone Wall

Method Description
getSurgeCurve() Get the surge curve object
getStoneWallCurve() Get the stone wall curve object
generateSurgeCurve() Auto-generate surge curve from performance curves
generateStoneWallCurve() Auto-generate stone wall curve from performance curves
getSurgeFlowAtSpeed(speed) Get surge flow at given speed
getSurgeHeadAtSpeed(speed) Get surge head at given speed
getStoneWallFlowAtSpeed(speed) Get stone wall flow at given speed
getStoneWallHeadAtSpeed(speed) Get stone wall head at given speed

SafeSplineSurgeCurve

Method Description
setCurve(chartConditions, flow, head) Set surge curve points (1 or more points)
getSurgeFlow(head) Get surge flow at given head
getSurgeHead(flow) Get surge head at given flow
isSurge(head, flow) Check if point is in surge
isActive() Check if curve is active
isSinglePointSurge() Check if single-point surge (single-speed)
getSingleSurgeFlow() Get single-point surge flow value
getSingleSurgeHead() Get single-point surge head value

SafeSplineStoneWallCurve

Method Description
setCurve(chartConditions, flow, head) Set stone wall curve points (1 or more points)
getStoneWallFlow(head) Get stone wall flow at given head
getStoneWallHead(flow) Get stone wall head at given flow
isStoneWall(head, flow) Check if point is at stone wall
isActive() Check if curve is active
isSinglePointStoneWall() Check if single-point (single-speed)
getSingleStoneWallFlow() Get single-point stone wall flow value
getSingleStoneWallHead() Get single-point stone wall head value

CompressorChartMWInterpolation

Method Description
addMapAtMW(mw, chartConditions, speed[], flow[][], head[][], polyEff[][]) Add multi-speed map with chart conditions
addMapAtMW(mw, chartConditions, speed[], flow[][], head[][], flowPolyEff[][], polyEff[][]) Add multi-speed map with chart conditions and separate flow arrays
addMapAtMW(mw, speed[], flow[][], head[][], polyEff[][]) Add multi-speed map (default conditions)
addMapAtMW(mw, speed[], flow[][], head[][], flowPolyEff[][], polyEff[][]) Add multi-speed map with separate flow arrays (default conditions)
addMapAtMW(mw, speed, flow[], head[], polyEff[]) Add single-speed map (default conditions)
addMapAtMW(mw, speed, flow[], head[], flowPolyEff[], polyEff[]) Add single-speed map with separate flow arrays
setOperatingMW(mw) Set current operating molecular weight
getOperatingMW() Get current operating molecular weight
setInletStream(stream) Set inlet stream for auto MW detection
getInletStream() Get the inlet stream
setUseActualMW(enabled) Enable/disable auto MW from inlet stream (default: true)
isUseActualMW() Check if auto MW is enabled
setAllowExtrapolation(enabled) Enable/disable extrapolation outside MW range
isAllowExtrapolation() Check if extrapolation is enabled
getNumberOfMaps() Get number of MW maps defined
getMapMolecularWeights() Get list of MW values
getChartAtMW(mw) Get chart at specific MW
setAutoGenerateSurgeCurves(enabled) Enable auto-generation of surge curves
setAutoGenerateStoneWallCurves(enabled) Enable auto-generation of stone wall curves
generateAllSurgeCurves() Generate surge curves for all maps
generateAllStoneWallCurves() Generate stone wall curves for all maps
setSurgeCurveAtMW(mw, chartConditions, flow, head) Set surge curve for specific MW
setStoneWallCurveAtMW(mw, chartConditions, flow, head) Set stone wall curve for specific MW
getSurgeFlow(head) Get interpolated surge flow at head
getStoneWallFlow(head) Get interpolated stone wall flow at head
getSurgeFlowAtSpeed(speed) Get interpolated surge flow at speed
getStoneWallFlowAtSpeed(speed) Get interpolated stone wall flow at speed
getSurgeHeadAtSpeed(speed) Get interpolated surge head at speed
getStoneWallHeadAtSpeed(speed) Get interpolated stone wall head at speed
isSurge(head, flow) Check if point is in surge (interpolated)
isStoneWall(head, flow) Check if point is at stone wall (interpolated)
getDistanceToSurge(head, flow) Get distance to surge (interpolated)
getDistanceToStoneWall(head, flow) Get distance to stone wall (interpolated)
setInterpolationEnabled(enabled) Enable/disable MW interpolation

Compressor Methods

Method Description
getDistanceToSurge() Ratio margin above surge
getDistanceToStoneWall() Ratio margin below stone wall
getSurgeFlowRate() Surge flow at current head (m³/hr)
getSurgeFlowRateMargin() Flow margin above surge (m³/hr)
isSurge(flow, head) Check if in surge
isStoneWall() Check if at stone wall
isHigherThanMaxSpeed() Check if current speed exceeds max curve speed
isHigherThanMaxSpeed(speed) Check if given speed exceeds max curve speed
isLowerThanMinSpeed() Check if current speed is below min curve speed
isLowerThanMinSpeed(speed) Check if given speed is below min curve speed
isSpeedWithinRange() Check if current speed is within curve range
isSpeedWithinRange(speed) Check if given speed is within curve range
getRatioToMaxSpeed() Get ratio of current speed to max speed
getRatioToMaxSpeed(speed) Get ratio of given speed to max speed
getRatioToMinSpeed() Get ratio of current speed to min speed
getRatioToMinSpeed(speed) Get ratio of given speed to min speed

Python Examples

Multi-Speed Compressor with Curves

import jpype
import jpype.imports
jpype.startJVM(classpath=['path/to/neqsim.jar'])

from neqsim.thermo.system import SystemSrkEos
from neqsim.process.equipment.stream import Stream
from neqsim.process.equipment.compressor import Compressor

# Create fluid
fluid = SystemSrkEos(298.15, 50.0)
fluid.addComponent("methane", 0.9)
fluid.addComponent("ethane", 0.1)
fluid.setMixingRule("classic")
fluid.setTotalFlowRate(5000.0, "Am3/hr")

# Create stream and compressor
stream = Stream("inlet", fluid)
stream.run()

compressor = Compressor("K-100", stream)
compressor.setUsePolytropicCalc(True)
compressor.setOutletPressure(100.0)

# Set multi-speed curves
chartConditions = [25.0, 50.0, 50.0, 20.0]
speed = [8000, 10000, 12000]
flow = [[3000, 4000, 5000], [3500, 4500, 5500], [4000, 5000, 6000]]
head = [[80, 70, 55], [100, 88, 70], [120, 105, 85]]
polyEff = [[78, 80, 76], [79, 81, 77], [78, 80, 76]]

compressor.getCompressorChart().setCurves(chartConditions, speed, flow, head, flow, polyEff)
compressor.getCompressorChart().setHeadUnit("kJ/kg")
compressor.setSpeed(10000)

# Generate surge and stone wall curves automatically
compressor.getCompressorChart().generateSurgeCurve()
compressor.getCompressorChart().generateStoneWallCurve()

compressor.run()

# Check operating margins
print(f"Distance to surge: {compressor.getDistanceToSurge() * 100:.1f}%")
print(f"Distance to stone wall: {compressor.getDistanceToStoneWall() * 100:.1f}%")

Single-Speed Compressor with Single-Point Curves

# Create compressor with single speed
compressor = Compressor("K-100", stream)
compressor.setUsePolytropicCalc(True)
compressor.setOutletPressure(100.0)

# Set single-speed curve
chartConditions = [25.0, 50.0, 50.0, 20.0]
speed = [10250]
flow = [[5607, 6480, 7800, 8750, 9758]]
head = [[150.0, 148.8, 146.1, 140.7, 112.6]]
polyEff = [[78, 80, 80, 78, 70]]

compressor.getCompressorChart().setCurves(chartConditions, speed, flow, head, flow, polyEff)
compressor.getCompressorChart().setHeadUnit("kJ/kg")
compressor.setSpeed(10250)

# Set single-point surge and stone wall
compressor.getCompressorChart().getSurgeCurve().setCurve(
    chartConditions, 
    [5607.45],   # Single surge flow point
    [150.0]      # Single surge head point
)

compressor.getCompressorChart().getStoneWallCurve().setCurve(
    chartConditions,
    [9758.49],   # Single stone wall flow point  
    [112.65]     # Single stone wall head point
)

compressor.run()

# Verify single-point curves are active
surge_curve = compressor.getCompressorChart().getSurgeCurve()
print(f"Surge curve active: {surge_curve.isActive()}")
print(f"Is single-point surge: {surge_curve.isSinglePointSurge()}")
print(f"Surge flow: {surge_curve.getSingleSurgeFlow()}")

# Check operating margins
print(f"Distance to surge: {compressor.getDistanceToSurge() * 100:.1f}%")
print(f"Distance to stone wall: {compressor.getDistanceToStoneWall() * 100:.1f}%")

Molecular Weight Correction with CompressorChartKhader2015

from neqsim.thermo import SystemSrkEos
from neqsim.process import Stream, Compressor

# Import the Khader2015 chart class
from jpype import JClass
CompressorChartKhader2015 = JClass('neqsim.process.equipment.compressor.CompressorChartKhader2015')

# Create the actual operating fluid (composition different from reference)
operating_fluid = SystemSrkEos(298.15, 50.0)
operating_fluid.addComponent("methane", 0.75)  # Different composition
operating_fluid.addComponent("ethane", 0.15)
operating_fluid.addComponent("propane", 0.08)
operating_fluid.addComponent("n-butane", 0.02)
operating_fluid.setMixingRule("classic")
operating_fluid.setTotalFlowRate(5000.0, "Am3/hr")

# Create stream
stream = Stream("inlet", operating_fluid)
stream.run()

# Create compressor
compressor = Compressor("K-100", stream)
compressor.setUsePolytropicCalc(True)
compressor.setOutletPressure(100.0)

# Create Khader2015 chart with impeller diameter
impeller_diameter = 0.3  # meters
chart = CompressorChartKhader2015(stream, impeller_diameter)

# Chart conditions: [temp °C, pres bara, density kg/m³, MW g/mol]
# MW = 20.0 g/mol is the reference molecular weight
chart_conditions = [25.0, 50.0, 50.0, 20.0]

# Reference curves (measured at MW = 20 g/mol)
speed = [10000, 11000, 12000]
flow = [[3500, 4000, 4500, 5000, 5500],
        [3800, 4300, 4800, 5300, 5800],
        [4000, 4500, 5000, 5500, 6000]]
head = [[110, 105, 98, 88, 75],
        [128, 122, 114, 103, 90],
        [148, 141, 132, 120, 105]]
poly_eff = [[77, 80, 81, 79, 74],
            [76, 79, 80, 78, 73],
            [75, 78, 79, 77, 72]]

chart.setCurves(chart_conditions, speed, flow, head, flow, poly_eff)
chart.setHeadUnit("kJ/kg")

# Apply chart - curves are automatically corrected for the operating fluid's MW
compressor.setCompressorChart(chart)
compressor.setSpeed(11000)
compressor.run()

print(f"Operating fluid MW: {operating_fluid.getMolarMass('kg/mol') * 1000:.1f} g/mol")
print(f"Reference fluid MW: {chart_conditions[3]:.1f} g/mol")
print(f"Polytropic head: {compressor.getPolytropicHead('kJ/kg'):.2f} kJ/kg")
print(f"Polytropic efficiency: {compressor.getPolytropicEfficiency() * 100:.1f}%")

# If fluid composition changes, regenerate curves
operating_fluid.addComponent("CO2", 0.05)
operating_fluid.init(0)
chart.generateRealCurvesForFluid()
compressor.run()

print(f"\nAfter adding CO2:")
print(f"New fluid MW: {operating_fluid.getMolarMass('kg/mol') * 1000:.1f} g/mol")
print(f"Polytropic head: {compressor.getPolytropicHead('kJ/kg'):.2f} kJ/kg")

Automatic Curve Generation

When you don't have manufacturer performance data, NeqSim can automatically generate realistic compressor curves using the CompressorChartGenerator class and predefined curve templates.

Quick Start

// Create and run compressor to establish design point
Compressor compressor = new Compressor("K-100", inletStream);
compressor.setOutletPressure(100.0, "bara");
compressor.setPolytropicEfficiency(0.78);
compressor.setSpeed(10000);
compressor.run();

// Generate curves automatically
CompressorChartGenerator generator = new CompressorChartGenerator(compressor);
CompressorChartInterface chart = generator.generateFromTemplate("PIPELINE", 5);

// Apply and use
compressor.setCompressorChart(chart);
compressor.run();

Why Use Automatic Generation?

Use Case Benefit
Early design studies Estimate performance before vendor data available
Sensitivity analysis Quickly evaluate different compressor configurations
Education/training Realistic curves without proprietary data
Default behavior Reasonable performance when no map is provided

Compressor Curve Templates

NeqSim provides 12 predefined templates organized into three categories, each representing typical compressor characteristics for different applications.

Template Categories Overview

┌─────────────────────────────────────────────────────────────────────┐
│                    COMPRESSOR CURVE TEMPLATES                       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────────────┐  ┌─────────────────────┐  ┌─────────────┐ │
│  │   BASIC (3)         │  │  APPLICATION (6)    │  │  TYPE (4)   │ │
│  │                     │  │                     │  │             │ │
│  │  • STANDARD         │  │  • PIPELINE         │  │ • SINGLE    │ │
│  │  • HIGH_FLOW        │  │  • EXPORT           │  │   _STAGE    │ │
│  │  • HIGH_HEAD        │  │  • INJECTION        │  │ • MULTI     │ │
│  │                     │  │  • GAS_LIFT         │  │   STAGE     │ │
│  │                     │  │  • REFRIGERATION    │  │ • INTEGRAL  │ │
│  │                     │  │  • BOOSTER          │  │   _GEARED   │ │
│  │                     │  │                     │  │ • OVERHUNG  │ │
│  └─────────────────────┘  └─────────────────────┘  └─────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Basic Centrifugal Templates

Generic centrifugal compressor characteristics for general use.

Template Peak η Flow Range Head Best For
CENTRIFUGAL_STANDARD ~78% Medium Medium General purpose, default choice
CENTRIFUGAL_HIGH_FLOW ~78% Wide Lower High throughput, low pressure ratio
CENTRIFUGAL_HIGH_HEAD ~78% Narrow High High pressure ratio, multiple stages

Application-Based Templates

Optimized for specific oil & gas applications.

Template Peak η Typical Use Key Characteristics
PIPELINE 82-85% Gas transmission High capacity, flat curves, wide turndown (~40%)
EXPORT ~80% Offshore gas export High pressure, stable operation, 6-8 stages
INJECTION ~77% Gas injection/EOR Very high pressure ratio, lower capacity
GAS_LIFT ~75% Artificial lift Wide surge margin (~35%), liquid tolerant
REFRIGERATION ~78% LNG/process cooling Wide operating range, part-load efficiency
BOOSTER ~76% Process plant Moderate pressure ratio (2-4), balanced design

Compressor Type Templates

Based on mechanical design characteristics.

Template Peak η Pressure Ratio Design Features
SINGLE_STAGE ~75% 1.5-2.5 Simple, wide flow range, cost-effective
MULTISTAGE_INLINE ~78% 5-15 Barrel type, 4-8 stages, O&G standard
INTEGRALLY_GEARED 82% Flexible Multiple pinions, air separation, optimized
OVERHUNG ~74% Low-medium Cantilever, simple maintenance

Template Selection Guide

Decision Flowchart

                          ┌─────────────────────────┐
                          │ What is the application?│
                          └───────────┬─────────────┘
                                      │
           ┌──────────────┬───────────┼───────────┬──────────────┐
           ▼              ▼           ▼           ▼              ▼
    ┌────────────┐ ┌────────────┐ ┌─────────┐ ┌─────────┐ ┌────────────┐
    │Gas Pipeline│ │ Offshore   │ │  EOR /  │ │Gas Lift │ │Refriger.   │
    │Transmission│ │   Export   │ │Injection│ │         │ │ / LNG      │
    └─────┬──────┘ └─────┬──────┘ └────┬────┘ └────┬────┘ └─────┬──────┘
          │              │             │           │             │
          ▼              ▼             ▼           ▼             ▼
      PIPELINE        EXPORT      INJECTION    GAS_LIFT    REFRIGERATION


                          ┌─────────────────────────┐
                          │  What type of machine?  │
                          └───────────┬─────────────┘
                                      │
           ┌──────────────┬───────────┴───────────┬──────────────┐
           ▼              ▼                       ▼              ▼
    ┌────────────┐ ┌────────────┐         ┌────────────┐ ┌────────────┐
    │   Simple   │ │   Barrel   │         │ Integrally │ │  Overhung  │
    │Single Stage│ │ Multistage │         │   Geared   │ │ Cantilever │
    └─────┬──────┘ └─────┬──────┘         └─────┬──────┘ └─────┬──────┘
          │              │                      │              │
          ▼              ▼                      ▼              ▼
    SINGLE_STAGE   MULTISTAGE_INLINE    INTEGRALLY_GEARED   OVERHUNG

Quick Selection Matrix

Your Requirement Recommended Template
Default / don't know CENTRIFUGAL_STANDARD
Large capacity, moderate PR PIPELINE
High discharge pressure EXPORT or INJECTION
Variable inlet conditions GAS_LIFT
Process cooling/LNG REFRIGERATION
Simple, low PR SINGLE_STAGE
High PR, compact MULTISTAGE_INLINE
Highest efficiency INTEGRALLY_GEARED
Small duty, easy maintenance OVERHUNG

Using the CompressorChartGenerator

Basic Usage

// Create generator from compressor
CompressorChartGenerator generator = new CompressorChartGenerator(compressor);

// Option 1: Generate from template (recommended)
CompressorChartInterface chart = generator.generateFromTemplate("PIPELINE", 5);

// Option 2: Generate "normal curves" from operating point
CompressorChartInterface chart = generator.generateCompressorChart("normal curves", 5);

// Option 3: Single speed curve
CompressorChartInterface chart = generator.generateCompressorChart("normal curves");

Setting Chart Type

The generator supports three output chart types:

generator.setChartType("interpolate and extrapolate");  // Default, most flexible
generator.setChartType("interpolate");                  // No extrapolation
generator.setChartType("simple");                       // Basic fan law scaling
Chart Type Class Use Case
interpolate and extrapolate CompressorChartAlternativeMapLookupExtrapolate Production, wide operating range
interpolate CompressorChartAlternativeMapLookup Stay within measured envelope
simple CompressorChart Basic calculations, teaching

Specifying Custom Speeds

// Using number of speeds (auto-distributed)
CompressorChartInterface chart = generator.generateFromTemplate("EXPORT", 5);
// → Generates 5 curves from 70% to 100% of design speed

// Using specific speed values
double[] speeds = {7000, 8000, 9000, 10000, 10500};
CompressorChartInterface chart = generator.generateCompressorChart("normal curves", speeds);

Advanced Corrections

Enable industry-standard corrections for more accurate off-design performance:

CompressorChartGenerator generator = new CompressorChartGenerator(compressor);

// Individual corrections
generator.setUseReynoldsCorrection(true);      // Efficiency correction for Re
generator.setUseMachCorrection(true);          // Choke flow limitation
generator.setUseMultistageSurgeCorrection(true); // Surge line shift at low speeds
generator.setNumberOfStages(6);
generator.setImpellerDiameter(0.35);

// Or enable all at once
generator.enableAdvancedCorrections(6);  // 6 stages

CompressorChartInterface chart = generator.generateFromTemplate("MULTISTAGE_INLINE", 5);

Correction Effects

Correction Effect When Important
Reynolds Adjusts η at low Re (high viscosity, low speed) Heavy gases, low-speed operation
Mach Limits stonewall flow based on sonic velocity Light gases (H₂), high speed
Multistage Surge Shifts surge to higher flow at reduced speed Variable speed, >3 stages

Template API Reference

Listing Available Templates

// Get all templates
String[] all = CompressorCurveTemplate.getAvailableTemplates();
// → ["CENTRIFUGAL_STANDARD", "CENTRIFUGAL_HIGH_FLOW", ..., "OVERHUNG"]

// Get by category
String[] basic = CompressorCurveTemplate.getTemplatesByCategory("basic");
// → ["CENTRIFUGAL_STANDARD", "CENTRIFUGAL_HIGH_FLOW", "CENTRIFUGAL_HIGH_HEAD"]

String[] application = CompressorCurveTemplate.getTemplatesByCategory("application");
// → ["PIPELINE", "EXPORT", "INJECTION", "GAS_LIFT", "REFRIGERATION", "BOOSTER"]

String[] type = CompressorCurveTemplate.getTemplatesByCategory("type");
// → ["SINGLE_STAGE", "MULTISTAGE_INLINE", "INTEGRALLY_GEARED", "OVERHUNG"]

Template Name Matching

The getTemplate() method is flexible with naming:

// All of these return the same template:
CompressorCurveTemplate.getTemplate("GAS_LIFT");
CompressorCurveTemplate.getTemplate("gas-lift");
CompressorCurveTemplate.getTemplate("gas lift");
CompressorCurveTemplate.getTemplate("gaslift");

// Abbreviations work too:
CompressorCurveTemplate.getTemplate("igc");     // → INTEGRALLY_GEARED
CompressorCurveTemplate.getTemplate("barrel");  // → MULTISTAGE_INLINE
CompressorCurveTemplate.getTemplate("LNG");     // → REFRIGERATION

Working Directly with Templates

// Get template object
CompressorCurveTemplate template = CompressorCurveTemplate.PIPELINE;

// Get unscaled original chart
CompressorChartInterface originalChart = template.getOriginalChart();

// Scale to specific speed
CompressorChartInterface scaledChart = template.scaleToSpeed(8000);

// Scale to design point
CompressorChartInterface chart = template.scaleToDesignPoint(
    10000,   // designSpeed (RPM)
    5000,    // designFlow (m³/hr)
    85.0,    // designHead (kJ/kg)
    5        // numberOfSpeeds
);

// Get template metadata
String name = template.getName();
double refSpeed = template.getReferenceSpeed();
double[] speedRatios = template.getSpeedRatios();

Complete Java Example

import neqsim.process.equipment.compressor.*;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

public class CompressorCurveGenerationExample {
    public static void main(String[] args) {
        // 1. Create fluid and inlet stream
        SystemSrkEos gas = new SystemSrkEos(298.15, 50.0);
        gas.addComponent("methane", 0.85);
        gas.addComponent("ethane", 0.10);
        gas.addComponent("propane", 0.05);
        gas.setMixingRule("classic");

        Stream inlet = new Stream("inlet", gas);
        inlet.setFlowRate(15000.0, "kg/hr");
        inlet.setTemperature(25.0, "C");
        inlet.setPressure(40.0, "bara");
        inlet.run();

        // 2. Create compressor at design point
        Compressor comp = new Compressor("K-100", inlet);
        comp.setOutletPressure(120.0, "bara");
        comp.setUsePolytropicCalc(true);
        comp.setPolytropicEfficiency(0.78);
        comp.setSpeed(9500);
        comp.run();

        System.out.println("=== Design Point ===");
        System.out.println("Flow: " + String.format("%.1f", inlet.getFlowRate("m3/hr")) + " m³/hr");
        System.out.println("Head: " + String.format("%.1f", comp.getPolytropicFluidHead()) + " kJ/kg");
        System.out.println("Power: " + String.format("%.1f", comp.getPower("kW")) + " kW");

        // 3. Generate curves using EXPORT template (offshore gas export)
        CompressorChartGenerator generator = new CompressorChartGenerator(comp);
        generator.setChartType("interpolate and extrapolate");
        generator.enableAdvancedCorrections(6);  // 6-stage compressor

        CompressorChartInterface chart = generator.generateFromTemplate("EXPORT", 5);

        // 4. Apply chart and verify
        comp.setCompressorChart(chart);
        comp.run();

        System.out.println("\n=== With Generated Chart ===");
        System.out.println("Speeds available: " + chart.getSpeeds().length);
        System.out.println("Efficiency from chart: " + 
            String.format("%.1f", comp.getPolytropicEfficiency() * 100) + "%");
        System.out.println("Distance to surge: " + 
            String.format("%.1f", comp.getDistanceToSurge() * 100) + "%");

        // 5. Test at different operating point
        inlet.setFlowRate(12000.0, "kg/hr");
        inlet.run();
        comp.run();

        System.out.println("\n=== Turndown Operation ===");
        System.out.println("Flow: " + String.format("%.1f", inlet.getFlowRate("m3/hr")) + " m³/hr");
        System.out.println("Efficiency: " + 
            String.format("%.1f", comp.getPolytropicEfficiency() * 100) + "%");
        System.out.println("Distance to surge: " + 
            String.format("%.1f", comp.getDistanceToSurge() * 100) + "%");
    }
}

Python Example

from neqsim import jNeqSim
from neqsim.process import stream, compressor

# Import Java classes
SystemSrkEos = jNeqSim.thermo.system.SystemSrkEos
Stream = jNeqSim.process.equipment.stream.Stream
Compressor = jNeqSim.process.equipment.compressor.Compressor
CompressorChartGenerator = jNeqSim.process.equipment.compressor.CompressorChartGenerator
CompressorCurveTemplate = jNeqSim.process.equipment.compressor.CompressorCurveTemplate

# Create fluid
gas = SystemSrkEos(298.15, 50.0)
gas.addComponent("methane", 0.90)
gas.addComponent("ethane", 0.07)
gas.addComponent("propane", 0.03)
gas.setMixingRule("classic")

# Create stream
inlet = Stream("inlet", gas)
inlet.setFlowRate(20000.0, "kg/hr")
inlet.setTemperature(30.0, "C")
inlet.setPressure(45.0, "bara")
inlet.run()

# Create compressor
comp = Compressor("K-100", inlet)
comp.setOutletPressure(150.0, "bara")
comp.setUsePolytropicCalc(True)
comp.setPolytropicEfficiency(0.77)
comp.setSpeed(10000)
comp.run()

# List available templates
print("Available templates:")
for cat in ["basic", "application", "type"]:
    templates = CompressorCurveTemplate.getTemplatesByCategory(cat)
    print(f"  {cat}: {list(templates)}")

# Generate chart using INJECTION template (high pressure application)
generator = CompressorChartGenerator(comp)
generator.setChartType("interpolate and extrapolate")
generator.enableAdvancedCorrections(8)  # 8-stage injection compressor

chart = generator.generateFromTemplate("INJECTION", 5)
comp.setCompressorChart(chart)
comp.run()

print(f"\nDesign efficiency: {comp.getPolytropicEfficiency() * 100:.1f}%")
print(f"Design head: {comp.getPolytropicFluidHead():.1f} kJ/kg")
print(f"Power: {comp.getPower('MW'):.2f} MW")
print(f"Surge margin: {comp.getDistanceToSurge() * 100:.1f}%")

Template Technical Specifications

PIPELINE Template

Application: Natural gas transmission (30-50 MW class)
Reference Speed: 5500 RPM (large direct-drive or gear-driven)
Peak Efficiency: 85%
Turndown: ~40%

Curve Characteristics:
        Head
         ↑
    79 ──┼────╮
         │     ╲   Flat curve
    65 ──┼──────╲──  for pipeline
         │       ╲   stability
    53 ──┼────────╲─
         │         ╲
         └──────────┴────→ Flow
           30k    70k m³/hr

INJECTION Template

Application: Gas injection/EOR (very high pressure)
Reference Speed: 11000 RPM
Peak Efficiency: 77%
Pressure Ratio: 50-200 (overall, with intercooling)

Curve Characteristics:
        Head
         ↑
   245 ──┼──╮
         │   ╲   Steep curve
   165 ──┼────╲──  (high head
         │     ╲    per stage)
   130 ──┼──────╲
         │       ╲
         └────────┴────→ Flow
           2.5k   6k m³/hr
           (lower capacity)

INTEGRALLY_GEARED Template

Application: Air separation, process air
Reference Speed: 20000 RPM (pinion speed)
Peak Efficiency: 82% (highest of all templates)

Design Features:
  - Bull gear drives multiple pinions
  - Each pinion has optimized impeller
  - Intercooling between stages

┌─────────────────────────┐
│     BULL GEAR           │
│    ╭───────╮            │
│   ╱  ●   ●  ╲           │  ● = Pinion with impeller
│  │  ●     ●  │          │
│   ╲  ●   ●  ╱           │
│    ╰───────╯            │
└─────────────────────────┘

Dynamic Simulation Features ⭐ NEW

NeqSim now provides comprehensive dynamic simulation capabilities for compressors, including state machines, event-driven control, driver modeling, and startup/shutdown sequences.

Overview

The dynamic simulation features enable realistic transient simulations including:

Compressor States

The CompressorState enum defines the possible operating states:

State Description Can Start? Is Operational?
STOPPED Compressor is not running Yes No
STARTING Startup sequence in progress No No
RUNNING Normal operation No Yes
SURGE_PROTECTION Near or in surge, recycle active No Yes
SPEED_LIMITED At max/min speed limit No Yes
SHUTDOWN Shutdown sequence in progress No No
DEPRESSURIZING Pressure settling after shutdown No No
TRIPPED Emergency shutdown, requires acknowledgment No No
STANDBY Ready to start after trip acknowledgment Yes No

Basic Dynamic Simulation Setup

// Create compressor with dynamic features
Compressor comp = new Compressor("K-100", inletStream);
comp.setOutletPressure(100.0, "bara");
comp.setSpeed(10000);
comp.run();

// Enable dynamic simulation features
comp.enableOperatingHistory();
comp.setRotationalInertia(15.0);  // kg⋅m² combined rotor inertia
comp.setMaxAccelerationRate(100.0);  // RPM/s
comp.setMaxDecelerationRate(200.0);  // RPM/s

// Set surge margin thresholds
comp.setSurgeWarningThreshold(0.15);   // 15% margin triggers warning
comp.setSurgeCriticalThreshold(0.05);  // 5% margin triggers critical alarm

// Configure anti-surge controller
AntiSurge antiSurge = comp.getAntiSurge();
antiSurge.setControlStrategy(AntiSurge.ControlStrategy.PID);
antiSurge.setPIDParameters(2.0, 0.5, 0.1);
antiSurge.setValveResponseTime(2.0);  // seconds

// Start compressor with startup profile
comp.startCompressor(10000);  // Target speed

Event Listeners

Listen for compressor events during dynamic simulation:

// Create event listener
CompressorEventListener listener = new CompressorEventListener() {
    @Override
    public void onSurgeApproach(Compressor compressor, double surgeMargin, boolean isCritical) {
        if (isCritical) {
            System.out.println("CRITICAL: Surge margin only " + surgeMargin * 100 + "%");
        } else {
            System.out.println("WARNING: Approaching surge, margin = " + surgeMargin * 100 + "%");
        }
    }

    @Override
    public void onSurgeOccurred(Compressor compressor, double surgeMargin) {
        System.out.println("ALARM: Compressor in surge!");
    }

    @Override
    public void onSpeedLimitExceeded(Compressor compressor, double currentSpeed, double ratio) {
        System.out.println("Speed limit exceeded: " + currentSpeed + " RPM (" + ratio * 100 + "% of max)");
    }

    @Override
    public void onSpeedBelowMinimum(Compressor compressor, double currentSpeed, double ratio) {
        System.out.println("Speed below minimum: " + currentSpeed + " RPM");
    }

    @Override
    public void onPowerLimitExceeded(Compressor compressor, double currentPower, double maxPower) {
        System.out.println("Power limit exceeded: " + currentPower + " kW > " + maxPower + " kW");
    }

    @Override
    public void onStateChange(Compressor compressor, CompressorState oldState, CompressorState newState) {
        System.out.println("State change: " + oldState + " -> " + newState);
    }

    @Override
    public void onStoneWallApproach(Compressor compressor, double stoneWallMargin) {
        System.out.println("Approaching stone wall, margin = " + stoneWallMargin * 100 + "%");
    }

    @Override
    public void onStartupComplete(Compressor compressor) {
        System.out.println("Startup complete!");
    }

    @Override
    public void onShutdownComplete(Compressor compressor) {
        System.out.println("Shutdown complete!");
    }
};

// Register listener
comp.addEventListener(listener);

Driver Modeling

Model driver (motor, turbine) characteristics:

// Create driver model
CompressorDriver driver = new CompressorDriver(DriverType.GAS_TURBINE, 5000);  // 5000 kW gas turbine
driver.setMaxPower(5500);  // 110% overload capacity
driver.setInertia(25.0);   // kg⋅m² combined inertia
driver.setMaxAcceleration(80.0);   // RPM/s
driver.setMaxDeceleration(150.0);  // RPM/s

// For gas turbines: ambient temperature derating
driver.setAmbientTemperature(308.15);  // 35°C
driver.setTemperatureDerateFactor(0.005);  // 0.5% power reduction per °C above ISO

// For VFD motors: efficiency vs speed curve
CompressorDriver vfdDriver = new CompressorDriver(DriverType.VFD_MOTOR, 3000);
vfdDriver.setVfdEfficiencyCoefficients(0.90, 0.05, -0.02);  // η = a + b*(N/Nrated) + c*(N/Nrated)²

// Apply driver to compressor
comp.setDriver(driver);

Driver Types

Driver Type Typical Efficiency Response Time Characteristics
ELECTRIC_MOTOR 95% 1 s Fixed speed, constant torque
VFD_MOTOR 93% 5 s Variable speed, high efficiency
GAS_TURBINE 35% 30 s Variable speed, ambient temp dependent
STEAM_TURBINE 40% 20 s Variable speed, steam supply dependent
RECIPROCATING_ENGINE 42% 15 s High efficiency, limited speed range
EXPANDER_DRIVE 85% 5 s Direct drive from process expander

Startup/Shutdown Profiles

Define sequenced startup and shutdown procedures:

// Create startup profile
StartupProfile startup = new StartupProfile();
startup.setMinimumIdleSpeed(3000);      // RPM
startup.setIdleHoldTime(60.0);           // seconds at idle
startup.setWarmupRampRate(50.0);         // RPM/s during warmup
startup.setNormalRampRate(100.0);        // RPM/s during normal ramp
startup.setRequireAntisurgeOpen(true);
startup.setAntisurgeOpeningDuration(10.0);  // seconds before start

// Or use predefined profiles
StartupProfile fastStartup = StartupProfile.createFastProfile(10000);  // Emergency restart
StartupProfile slowStartup = StartupProfile.createSlowProfile(10000, 3000);  // Cold start

comp.setStartupProfile(startup);

// Create shutdown profile
ShutdownProfile shutdown = new ShutdownProfile(ShutdownProfile.ShutdownType.NORMAL, 10000);
shutdown.setNormalRampRate(100.0);       // RPM/s
shutdown.setIdleRundownTime(30.0);       // seconds at idle before stop
shutdown.setOpenAntisurgeOnShutdown(true);

comp.setShutdownProfile(shutdown);

// Start/stop compressor
comp.startCompressor(10000);  // Start with target speed
comp.stopCompressor();        // Normal shutdown
comp.emergencyShutdown();     // Emergency shutdown (trips compressor)

Anti-Surge Control Strategies

The enhanced AntiSurge class supports multiple control strategies:

Strategy Description Best For
ON_OFF Simple on/off based on surge line Simple systems, teaching
PROPORTIONAL Linear valve opening based on margin Most applications
PID Full PID control with anti-windup Precise control, variable load
PREDICTIVE Uses rate-of-change prediction Rapid load changes
DUAL_LOOP Separate surge and capacity loops Complex systems
AntiSurge antiSurge = comp.getAntiSurge();

// Select control strategy
antiSurge.setControlStrategy(AntiSurge.ControlStrategy.PID);

// Configure PID
antiSurge.setPIDParameters(2.0, 0.5, 0.1);  // Kp, Ki, Kd
antiSurge.setPIDSetpoint(0.10);              // 10% surge margin setpoint

// Configure predictive control
antiSurge.setControlStrategy(AntiSurge.ControlStrategy.PREDICTIVE);
antiSurge.setPredictiveHorizon(5.0);         // 5 second look-ahead

// Valve dynamics
antiSurge.setValveResponseTime(2.0);         // First-order time constant
antiSurge.setValveRateLimit(0.5);            // Max 50% per second
antiSurge.setMinimumRecycleFlow(500.0);      // Minimum flow m³/hr
antiSurge.setMaximumRecycleFlow(3000.0);     // Maximum flow m³/hr

// Surge cycle trip protection
antiSurge.setMaxSurgeCyclesBeforeTrip(3);    // Trip after 3 surge cycles

Dynamic Simulation Loop

Update compressor state during transient simulation:

double dt = 0.1;  // 100 ms time step
double simTime = 0.0;

// Startup compressor
comp.startCompressor(10000);

while (simTime < 600.0) {  // 10 minute simulation
    // Update inlet conditions (from upstream process)
    inletStream.run();

    // Run compressor
    comp.run();

    // Update dynamic state (handles startup/shutdown, checks limits)
    comp.updateDynamicState(dt);

    // Update anti-surge controller
    double surgeMargin = comp.getDistanceToSurge();
    comp.getAntiSurge().updateController(surgeMargin, dt);

    // Record to history
    comp.recordOperatingPoint(simTime);

    simTime += dt;
}

// Export operating history
comp.getOperatingHistory().exportToCSV("compressor_history.csv");
System.out.println(comp.getOperatingHistory().generateSummary());

Operating History Analysis

Track and analyze operating history:

// Enable history tracking
comp.enableOperatingHistory();

// ... run simulation ...

// Get history summary
CompressorOperatingHistory history = comp.getOperatingHistory();
System.out.println("Total points recorded: " + history.getPointCount());
System.out.println("Surge events: " + history.getSurgeEventCount());
System.out.println("Time in surge: " + history.getTimeInSurge() + " s");
System.out.println("Minimum surge margin: " + history.getMinimumSurgeMargin() * 100 + "%");
System.out.println("Average efficiency: " + history.getAverageEfficiency() * 100 + "%");

// Get peak values
CompressorOperatingHistory.OperatingPoint peakPower = history.getPeakPower();
System.out.println("Peak power: " + peakPower.getPower() + " kW at t=" + peakPower.getTime() + " s");

// Export to CSV for plotting
history.exportToCSV("compressor_history.csv");

// Full summary report
System.out.println(history.generateSummary());

Performance Degradation

Model compressor performance degradation over time:

// Set degradation factor (1.0 = new, <1.0 = degraded)
comp.setDegradationFactor(0.95);  // 5% degradation

// Set fouling factor (head reduction)
comp.setFoulingFactor(0.03);  // 3% head reduction due to fouling

// Track operating hours
comp.setOperatingHours(25000);  // Initial operating hours
comp.addOperatingHours(100);    // Add 100 hours

// Get effective performance
double effectiveHead = comp.getEffectivePolytropicHead();  // Accounts for degradation
double effectiveEff = comp.getEffectivePolytropicEfficiency();  // Accounts for degradation

Auto-Speed Mode

Automatically calculate speed from operating point:

// Enable auto-speed mode
comp.setAutoSpeedMode(true);

// During simulation, speed is calculated from flow and head
comp.run();  // Speed automatically adjusted based on chart

Python Example - Dynamic Simulation

from neqsim import jNeqSim
from jpype import JClass

# Import classes
Compressor = jNeqSim.process.equipment.compressor.Compressor
CompressorState = JClass('neqsim.process.equipment.compressor.CompressorState')
CompressorDriver = JClass('neqsim.process.equipment.compressor.CompressorDriver')
DriverType = JClass('neqsim.process.equipment.compressor.DriverType')
AntiSurge = jNeqSim.process.equipment.compressor.AntiSurge
StartupProfile = JClass('neqsim.process.equipment.compressor.StartupProfile')
ShutdownProfile = JClass('neqsim.process.equipment.compressor.ShutdownProfile')

# Create compressor (assuming inlet stream exists)
comp = Compressor("K-100", inlet_stream)
comp.setOutletPressure(100.0, "bara")
comp.setSpeed(10000)
comp.run()

# Configure dynamic features
comp.enableOperatingHistory()
comp.setRotationalInertia(15.0)
comp.setSurgeWarningThreshold(0.15)
comp.setSurgeCriticalThreshold(0.05)

# Set up gas turbine driver
driver = CompressorDriver(DriverType.GAS_TURBINE, 5000)
driver.setAmbientTemperature(308.15)  # 35°C
comp.setDriver(driver)

# Configure anti-surge
anti_surge = comp.getAntiSurge()
anti_surge.setControlStrategy(AntiSurge.ControlStrategy.PID)
anti_surge.setPIDParameters(2.0, 0.5, 0.1)

# Run dynamic simulation
dt = 0.1  # 100 ms
sim_time = 0.0

comp.startCompressor(10000)

while sim_time < 300.0:
    inlet_stream.run()
    comp.run()
    comp.updateDynamicState(dt)

    # Print state periodically
    if int(sim_time) % 10 == 0 and sim_time == int(sim_time):
        print(f"t={sim_time:.0f}s: State={comp.getOperatingState()}, "
              f"Speed={comp.getSpeed():.0f} RPM, "
              f"Surge margin={comp.getDistanceToSurge()*100:.1f}%")

    sim_time += dt

# Export results
comp.getOperatingHistory().exportToCSV("compressor_dynamic.csv")
print(str(comp.getOperatingHistory().generateSummary()))

Compressor Design

Compressor Mechanical Design

This document describes the mechanical design calculations for centrifugal compressors in NeqSim, implemented in the CompressorMechanicalDesign class.

Overview

The mechanical design module provides sizing and design calculations for centrifugal compressors based on API 617 (Axial and Centrifugal Compressors) and industry practice. The calculations enable:

Design Standards Reference

Standard Description
API 617 Axial and Centrifugal Compressors and Expander-compressors
API 672 Packaged, Integrally Geared Centrifugal Air Compressors
API 692 Dry Gas Sealing Systems
API 614 Lubrication, Shaft-Sealing and Oil-Control Systems

Design Calculations

1. Number of Stages

The number of compression stages is determined by the total polytropic head and the maximum allowable head per stage:

numberOfStages = ceil(totalPolytropicHead / maxHeadPerStage)

Design Limit: Maximum head per stage = 30 kJ/kg (typical for process gas centrifugal compressors)

The actual head per stage is then:

headPerStage = totalPolytropicHead / numberOfStages

2. Impeller Sizing

Tip Speed Calculation

The impeller tip speed is derived from the head requirement using the work coefficient:

tipSpeed = sqrt(headPerStage [J/kg] / workCoefficient)

Where:

Design Limit: Maximum tip speed = 350 m/s (material limit for steel impellers)

Impeller Diameter

From the tip speed and rotational speed:

impellerDiameter [mm] = (tipSpeed × 60) / (π × speedRPM) × 1000

The design verifies the flow coefficient is within acceptable range (0.01-0.15):

flowCoefficient = volumeFlow [m³/s] / (D² × U)

3. Shaft Diameter

Shaft diameter is calculated from torque requirements and allowable shear stress:

torque [Nm] = power [kW] × 1000 × 60 / (2π × speedRPM)
shaftDiameter [mm] = ((16 × torque) / (π × allowableShear))^(1/3) × 1000 × safetyFactor

Where:

4. Driver Sizing

Driver power includes margins per API 617:

Shaft Power Driver Margin
< 150 kW 25%
150-750 kW 15%
> 750 kW 10%
driverPower = (shaftPower + mechanicalLosses) × driverMargin

5. Casing Design

Design Pressure and Temperature

designPressure = dischargePressure × 1.10  (10% margin)
designTemperature = dischargeTemperature + 30°C

Casing Type Selection

Design Pressure Casing Type
> 100 bara Barrel
40-100 bara Horizontally Split
< 40 bara Vertically Split

6. Rotor Dynamics

Critical Speeds

maxContinuousSpeed = operatingSpeed × 1.05
tripSpeed = maxContinuousSpeed × 1.05

The first lateral critical speed is estimated using simplified Rayleigh-Ritz formulation based on shaft geometry.

API 617 Requirement: Separation margin from critical speed ≥ 15%

Bearing Span

bearingSpan = numberOfStages × (impellerDiameter × 0.8) + impellerDiameter

7. Weight Estimation

Rotor Weight

impellerWeight = numberOfStages × 0.5 × (impellerDiameter/100)^2.5
shaftWeight = bearingSpan/1000 × 7850 × π × (shaftDiameter/2000)²
rotorWeight = impellerWeight + shaftWeight

Casing Weight

casingThickness = max(10mm, designPressure × impellerDiameter / (2 × 150))
casingWeight = π × casingOD × casingLength × casingThickness × 7850 × 1.2

For barrel-type casing, add 30% additional weight.

Total Skid Weight

Component Estimation Method
Casing As calculated above
Bundle (rotor + internals) rotorWeight + stage internals
Seal system 100 × (shaftDiameter/100) kg
Lube oil system 200 + driverPower × 0.1 kg
Baseplate casingWeight × 0.3
Piping emptyVesselWeight × 0.2
Electrical driverPower × 0.5 kg
Structural steel emptyVesselWeight × 0.15

8. Module Dimensions

moduleLength = compressorLength + driverLength + couplingSpace + auxiliarySpace
moduleWidth = casingOD + 3.0m (access each side)
moduleHeight = casingOD + 2.0m (piping and lifting)

Minimum dimensions: 4m × 3m × 3m

Integration with CompressorMechanicalLosses

The mechanical design integrates with CompressorMechanicalLosses for:

When setDesign() is called, the mechanical losses model is automatically initialized with the calculated shaft diameter.

Usage Example

// Create and run compressor
SystemInterface gas = new SystemSrkEos(300.0, 10.0);
gas.addComponent("methane", 1.0);
gas.setMixingRule(2);

Stream inlet = new Stream("inlet", gas);
inlet.setFlowRate(10000.0, "kg/hr");

Compressor comp = new Compressor("export compressor", inlet);
comp.setOutletPressure(40.0);
comp.setPolytropicEfficiency(0.76);
comp.setSpeed(8000);

ProcessSystem ps = new ProcessSystem();
ps.add(inlet);
ps.add(comp);
ps.run();

// Calculate mechanical design
comp.getMechanicalDesign().calcDesign();

// Access design results
int stages = comp.getMechanicalDesign().getNumberOfStages();
double impellerD = comp.getMechanicalDesign().getImpellerDiameter(); // mm
double driverPower = comp.getMechanicalDesign().getDriverPower(); // kW
double totalWeight = comp.getMechanicalDesign().getWeightTotal(); // kg

// Apply design (initializes mechanical losses)
comp.getMechanicalDesign().setDesign();

// Get seal gas consumption
double sealGas = comp.getSealGasConsumption(); // Nm³/hr

Design Output Parameters

Parameter Method Unit
Number of stages getNumberOfStages() -
Head per stage getHeadPerStage() kJ/kg
Impeller diameter getImpellerDiameter() mm
Tip speed getTipSpeed() m/s
Shaft diameter getShaftDiameter() mm
Bearing span getBearingSpan() mm
Design pressure getDesignPressure() bara
Design temperature getDesignTemperature() °C
Casing type getCasingType() enum
Driver power getDriverPower() kW
Max continuous speed getMaxContinuousSpeed() rpm
Trip speed getTripSpeed() rpm
First critical speed getFirstCriticalSpeed() rpm
Casing weight getCasingWeight() kg
Bundle weight getBundleWeight() kg
Total skid weight getWeightTotal() kg
Module dimensions getModuleLength/Width/Height() m

Limitations and Assumptions

  1. Single-shaft configuration - Does not handle integrally geared or multi-body compressors
  2. Empirical correlations - Weight and dimension estimates are approximate; vendor data should be used for detailed design
  3. Steel impellers - Tip speed limit assumes conventional steel; titanium or composites allow higher speeds
  4. Backward-curved impellers - Work coefficient assumes standard backward-curved blade geometry
  5. No intercooling - Multi-stage calculations assume adiabatic compression; intercooled designs require separate handling

References

  1. API 617, 8th Edition - Axial and Centrifugal Compressors
  2. Bloch, H.P. - "A Practical Guide to Compressor Technology"
  3. Japikse, D. - "Centrifugal Compressor Design and Performance"
  4. Lüdtke, K.H. - "Process Centrifugal Compressors"

Pumps

Pumps

Documentation for liquid pumping equipment in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.pump

Classes:

Class Description
Pump Centrifugal or positive displacement pump
PumpInterface Pump interface

Pump Class

Basic Usage

import neqsim.process.equipment.pump.Pump;

// Create pump on liquid stream
Pump pump = new Pump("P-100", liquidStream);
pump.setOutletPressure(50.0, "bara");
pump.run();

// Results
double power = pump.getPower("kW");
double head = pump.getHead("m");
double efficiency = pump.getIsentropicEfficiency();

Outlet Specification

// By outlet pressure
pump.setOutletPressure(50.0, "bara");

// By pressure rise
pump.setPressureRise(30.0, "bara");

// By head
pump.setHead(300.0, "m");

Pump Performance

Isentropic Efficiency

// Set pump efficiency
pump.setIsentropicEfficiency(0.75);  // 75%

// Calculate power
pump.run();
double power = pump.getPower("kW");
double isentropicPower = pump.getIsentropicPower("kW");
double actualPower = pump.getActualPower("kW");

// Efficiency = Isentropic Power / Actual Power

Power Calculation

The pump power is calculated as:

$$P = \frac{\dot{m} \cdot \Delta h_{isentropic}}{\eta_{isentropic}}$$

Where:

Head Calculation

$$H = \frac{\Delta P}{\rho \cdot g}$$

Where:


Head and Efficiency Curves

Define Pump Curves

// Define head vs flow curve points
double[] flowRates = {0, 50, 100, 150, 200};      // m³/hr
double[] heads = {350, 340, 310, 260, 180};       // m
double[] efficiencies = {0, 0.65, 0.80, 0.75, 0.60};

pump.setHeadCurve(flowRates, heads, "m3/hr", "m");
pump.setEfficiencyCurve(flowRates, efficiencies, "m3/hr");

Operating Point

pump.run();

// Get operating point
double flowRate = pump.getInletStream().getFlowRate("m3/hr");
double actualHead = pump.getHead("m");
double actualEff = pump.getIsentropicEfficiency();

NPSH Calculations

Net Positive Suction Head

// NPSH available from process conditions
double npshAvailable = pump.getNPSHAvailable("m");

// NPSH required (from pump curve)
double[] flows = {50, 100, 150, 200};
double[] npshReq = {1.5, 2.0, 3.0, 5.0};
pump.setNPSHRequiredCurve(flows, npshReq, "m3/hr", "m");

double npshRequired = pump.getNPSHRequired("m");

// Check cavitation margin
double margin = npshAvailable - npshRequired;
if (margin < 1.0) {
    System.out.println("Warning: Low NPSH margin");
}

NPSH Available Calculation

$$NPSH_A = \frac{P_{suction}}{\rho g} + \frac{v^2}{2g} - \frac{P_{vapor}}{\rho g}$$


Examples

Example 1: Simple Pump

import neqsim.thermo.system.SystemSrkEos;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.pump.Pump;

// Create liquid stream
SystemSrkEos fluid = new SystemSrkEos(298.15, 5.0);
fluid.addComponent("n-heptane", 1.0);
fluid.setMixingRule("classic");

Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(100.0, "m3/hr");
feed.run();

// Pump
Pump pump = new Pump("P-100", feed);
pump.setOutletPressure(30.0, "bara");
pump.setIsentropicEfficiency(0.75);
pump.run();

// Results
System.out.println("Flow rate: " + pump.getInletStream().getFlowRate("m3/hr") + " m³/hr");
System.out.println("Head: " + pump.getHead("m") + " m");
System.out.println("Power: " + pump.getPower("kW") + " kW");
System.out.println("Efficiency: " + pump.getIsentropicEfficiency() * 100 + " %");

Example 2: Pump with Curves

// Define pump curves
double[] flows = {0, 25, 50, 75, 100, 125, 150};
double[] heads = {400, 395, 380, 355, 320, 270, 200};
double[] effs = {0, 0.55, 0.70, 0.78, 0.80, 0.75, 0.65};

Pump pump = new Pump("P-100", liquidStream);
pump.setHeadCurve(flows, heads, "m3/hr", "m");
pump.setEfficiencyCurve(flows, effs, "m3/hr");
pump.run();

// Operating point found on curves
System.out.println("Operating flow: " + pump.getInletStream().getFlowRate("m3/hr"));
System.out.println("Operating head: " + pump.getHead("m"));
System.out.println("Operating efficiency: " + pump.getIsentropicEfficiency());

Example 3: Booster Pump System

// Inlet conditions
SystemSrkEos crude = new SystemSrkEos(340.0, 3.0);
crude.addComponent("methane", 0.01);
crude.addComponent("n-pentane", 0.20);
crude.addComponent("n-heptane", 0.50);
crude.addComponent("n-decane", 0.29);
crude.setMixingRule("classic");

Stream feed = new Stream("Crude Feed", crude);
feed.setFlowRate(500.0, "m3/hr");
feed.run();

// First stage pump
Pump pump1 = new Pump("P-100A", feed);
pump1.setOutletPressure(20.0, "bara");
pump1.setIsentropicEfficiency(0.78);
pump1.run();

// Second stage pump
Pump pump2 = new Pump("P-100B", pump1.getOutletStream());
pump2.setOutletPressure(50.0, "bara");
pump2.setIsentropicEfficiency(0.75);
pump2.run();

// Total power
double totalPower = pump1.getPower("kW") + pump2.getPower("kW");
System.out.println("Total pump power: " + totalPower + " kW");

Pump Guide

Pump Usage Guide - Quick Reference

Basic Pump Setup

Simple Pump (Specified Pressure)

SystemInterface fluid = new SystemSrkEos(298.15, 1.0);
fluid.addComponent("water", 1.0);
fluid.setTotalFlowRate(100.0, "m3/hr");

Stream feed = new Stream("Feed", fluid);
feed.setTemperature(20.0, "C");
feed.setPressure(1.0, "bara");

Pump pump = new Pump("Pump1", feed);
pump.setOutletPressure(10.0, "bara");
pump.setIsentropicEfficiency(0.75); // 75% efficiency
pump.run();

double power = pump.getPower("kW");
double outletTemp = pump.getOutletStream().getTemperature("C");

Using Pump Curves

Setting Up Pump Curves

// Define pump performance at different speeds
double[] speed = new double[] {1000.0, 1500.0, 2000.0};

// Flow rates in m³/hr for each speed
double[][] flow = new double[][] {
    {10.0, 20.0, 30.0, 40.0, 50.0, 60.0},
    {15.0, 30.0, 45.0, 60.0, 75.0, 90.0},
    {20.0, 40.0, 60.0, 80.0, 100.0, 120.0}
};

// Head in meters for each speed and flow
double[][] head = new double[][] {
    {120.0, 118.0, 115.0, 110.0, 103.0, 94.0},
    {270.0, 265.5, 258.8, 247.5, 231.8, 211.5},
    {480.0, 472.0, 460.0, 440.0, 412.0, 376.0}
};

// Efficiency in % for each speed and flow
double[][] efficiency = new double[][] {
    {65.0, 72.0, 78.0, 82.0, 80.0, 74.0},
    {66.0, 73.0, 79.0, 83.0, 81.0, 75.0},
    {67.0, 74.0, 80.0, 84.0, 82.0, 76.0}
};

pump.getPumpChart().setCurves(new double[]{}, speed, flow, head, efficiency);
pump.getPumpChart().setHeadUnit("meter"); // or "kJ/kg"
pump.setSpeed(1500.0); // Set operating speed in rpm

Head Units

Meters (most common):

pump.getPumpChart().setHeadUnit("meter");
// Head represents height of fluid column
// ΔP = ρ × g × H

Specific Energy (kJ/kg):

pump.getPumpChart().setHeadUnit("kJ/kg");
// Head represents specific energy
// ΔP = E × ρ

NPSH Monitoring

Enable Cavitation Detection

pump.setCheckNPSH(true);
pump.setNPSHMargin(1.3); // Recommended: 1.1-1.5

pump.run();

// Check for cavitation risk
if (pump.isCavitating()) {
    double npsha = pump.getNPSHAvailable();
    double npshr = pump.getNPSHRequired();
    System.out.println("Warning: NPSHa = " + npsha + " m, NPSHr = " + npshr + " m");
    // Take corrective action: increase suction pressure or decrease temperature
}

Manual NPSH Check

double npsha = pump.getNPSHAvailable();
double npshr = pump.getNPSHRequired();

if (npsha < 1.3 * npshr) {
    // Insufficient NPSH - risk of cavitation
    // Solutions:
    // 1. Increase suction pressure
    // 2. Decrease fluid temperature
    // 3. Reduce pump speed
    // 4. Select different pump
}

Operating Status Monitoring

Check Pump Operating Region

double flow = feed.getFlowRate("m3/hr");
double speed = pump.getSpeed();

String status = pump.getPumpChart().getOperatingStatus(flow, speed);

switch (status) {
    case "OPTIMAL":
        // Operating near best efficiency point
        break;
    case "NORMAL":
        // Operating within acceptable range
        break;
    case "LOW_EFFICIENCY":
        // Operating far from BEP - inefficient
        // Consider adjusting speed or selecting different pump
        break;
    case "SURGE":
        // Flow too low - risk of instability and damage
        // Increase flow or reduce speed immediately
        break;
    case "STONEWALL":
        // Flow too high - maximum capacity reached
        // Reduce flow or increase speed
        break;
}

Find Best Efficiency Point

double bepFlow = pump.getPumpChart().getBestEfficiencyFlowRate();
double bepHead = pump.getPumpChart().getHead(bepFlow, speed);
double bepEfficiency = pump.getPumpChart().getEfficiency(bepFlow, speed);

System.out.println("Best efficiency: " + bepEfficiency + "% at " + bepFlow + " m³/hr");

Pump Selection and Sizing

Calculate Specific Speed

double ns = pump.getPumpChart().getSpecificSpeed();

if (ns < 1000) {
    System.out.println("Radial flow (centrifugal) pump");
} else if (ns < 4000) {
    System.out.println("Mixed flow pump");
} else {
    System.out.println("Axial flow pump");
}

Variable Speed Operation

// Affinity laws: Q ∝ N, H ∝ N², P ∝ N³

double baseSpeed = 1500.0;
double baseFlow = 50.0; // m³/hr
double baseHead = pump.getPumpChart().getHead(baseFlow, baseSpeed);

// To increase head by 44% (factor of 1.44 = 1.2²):
double newSpeed = baseSpeed * 1.2;
double newFlow = baseFlow * 1.2;
double newHead = baseHead * 1.44;

pump.setSpeed(newSpeed);
// Efficiency stays approximately constant at same reduced flow

Common Patterns

Pump with Minimum Flow Protection

pump.setMinimumFlow(0.05); // kg/sec

// When flow drops below minimum, pump idles with no pressure rise
// In practice, add minimum flow recirculation loop

Multi-stage Pump System

Stream stage1Out = new Stream("Stage 1 Out");
Pump stage1 = new Pump("Stage 1", feed);
stage1.setOutletPressure(5.0, "bara");
stage1.setOutStream(stage1Out);

Pump stage2 = new Pump("Stage 2", stage1Out);
stage2.setOutletPressure(10.0, "bara");

// Total head = stage1 head + stage2 head

Pump with Different Chart Type

// Default: Simple fan law interpolation
pump.setPumpChartType("fan law");

// Alternative: Map lookup with extrapolation
pump.setPumpChartType("interpolate and extrapolate");

Troubleshooting

Low Outlet Pressure

  1. Check pump curve covers operating flow rate
  2. Verify speed setting matches curve
  3. Check for cavitation (low NPSH)
  4. Verify head unit setting ("meter" vs "kJ/kg")

High Power Consumption

  1. Operating far from BEP - reduce or increase flow
  2. Check efficiency curve - may need different pump
  3. Verify outlet pressure requirement is reasonable

Cavitation Warnings

  1. Increase suction pressure
  2. Reduce fluid temperature
  3. Reduce pump speed
  4. Check for air entrainment
  5. Verify NPSH_r curve is accurate

Surge/Instability

  1. Increase minimum flow setpoint
  2. Add recirculation line from discharge to suction
  3. Reduce speed if possible
  4. Check for blockage downstream

Performance Calculations

Hydraulic Power

double rho = feed.getThermoSystem().getDensity("kg/m3");
double Q = feed.getFlowRate("m3/s");
double H = pump.getPumpChart().getHead(feed.getFlowRate("m3/hr"), pump.getSpeed());
double g = 9.81; // m/s²

double hydraulicPower = rho * g * Q * H; // Watts

Shaft Power (with losses)

double efficiency = pump.getIsentropicEfficiency() / 100.0; // Convert % to decimal
double shaftPower = hydraulicPower / efficiency;

Energy Cost Estimate

double powerKW = pump.getPower("kW");
double hoursPerYear = 8760;
double costPerKWh = 0.10; // $/kWh

double annualEnergyCost = powerKW * hoursPerYear * costPerKWh;
System.out.println("Annual energy cost: $" + annualEnergyCost);

Best Practices

  1. Always set pump curves when available - more accurate than fixed efficiency
  2. Enable NPSH checking for all liquid pumps
  3. Monitor operating status to avoid damage and inefficiency
  4. Operate near BEP (±20% flow) when possible
  5. Use correct head units - "meter" for liquid pumps
  6. Set realistic efficiency - typical centrifugal pumps: 70-85%
  7. Consider minimum flow - typically 10-20% of BEP flow
  8. Document curve source - manufacturer data sheets
  9. Validate with measurements - adjust curves if needed
  10. Check affinity laws - verify speed changes follow theory

Example: Complete Pump System

// Create fluid system
SystemInterface water = new SystemSrkEos(298.15, 1.5);
water.addComponent("water", 1.0);
water.setTemperature(25.0, "C");
water.setPressure(1.5, "bara");
water.setTotalFlowRate(75.0, "m3/hr");

Stream feed = new Stream("Pump Feed", water);
feed.run();

// Create pump with curve
Pump pump = new Pump("Booster Pump", feed);

double[] speed = new double[] {1450.0};
double[] flowPoints = {30, 50, 70, 90, 110, 130};
double[] headPoints = {45, 44, 42, 38, 32, 24};
double[] effPoints = {68, 76, 82, 84, 80, 70};
double[][] flow = new double[][] {flowPoints};
double[][] head = new double[][] {headPoints};
double[][] eff = new double[][] {effPoints};

pump.getPumpChart().setCurves(new double[]{}, speed, flow, head, eff);
pump.getPumpChart().setHeadUnit("meter");
pump.setSpeed(1450.0);
pump.setCheckNPSH(true);
pump.setNPSHMargin(1.3);

// Run simulation
pump.run();

// Check results
System.out.println("Outlet pressure: " + pump.getOutletPressure() + " bara");
System.out.println("Power: " + pump.getPower("kW") + " kW");
System.out.println("Outlet temp: " + pump.getOutletStream().getTemperature("C") + " °C");
System.out.println("NPSHa: " + pump.getNPSHAvailable() + " m");
System.out.println("Status: " + pump.getPumpChart().getOperatingStatus(75.0, 1450.0));

if (pump.isCavitating()) {
    System.out.println("WARNING: Cavitation risk!");
}

Example: Pump with Suction Line (Python)

This example demonstrates a realistic pump configuration where a suction line connects an upstream separator to the pump. The suction piping introduces pressure losses and static head changes that directly affect the NPSH available at the pump inlet. Properly modeling the suction line is critical for accurate cavitation assessment.

Why Model the Suction Line?

In real installations, the pump does not receive fluid directly at separator conditions. The suction system introduces:

  1. Valve pressure drop - Control or isolation valves at the separator outlet cause pressure loss depending on Cv and flow rate
  2. Frictional pressure losses - Depends on pipe length, diameter, roughness, flow rate, and fluid properties
  3. Static head changes - Elevation difference between liquid source and pump centerline
  4. Minor losses - Elbows, filters, and other fittings

These effects reduce the pressure at the pump suction flange relative to the source, directly impacting NPSHa. Ignoring suction system effects can lead to:

Example Code

import neqsim

# Get the oil outlet stream from an upstream separator
# (This would typically come from a configured process system)
pump_feed = oseberg_process.get('main process').getUnit('3RD stage separator').getOilOutStream()

# --- Separator Outlet Valve ---
# Model the isolation/control valve at the separator oil outlet
# Cv sizing: For a 6" valve (DN150) with full port, typical Cv ≈ 400-500
# For a 4" valve (DN100), typical Cv ≈ 150-200

separatorValve = neqsim.process.equipment.valve.ThrottlingValve("SeparatorOutletValve", pump_feed)
separatorValve.setCv(350)              # Valve Cv (flow coefficient in US gpm/psi^0.5)
separatorValve.setIsCalcOutPressure(True) 
separatorValve.setPercentValveOpening(80)  # 80% open - allows for control margin

separatorValve.run()

# --- Suction Line Configuration ---
# Model the piping between separator valve and pump using Beggs & Brill correlation
# This accounts for friction losses and elevation effects

suctionLine = neqsim.process.equipment.pipeline.PipeBeggsAndBrills("SuctionLine", separatorValve.getOutletStream())
suctionLine.setLength(20.0)           # Pipe length in meters
suctionLine.setDiameter(0.2)          # Internal diameter in meters (200 mm)
suctionLine.setPipeWallRoughness(1.0e-5)  # Internal roughness in meters (~smooth pipe)
suctionLine.setElevation(-20)         # Pump is 20 m below separator (positive static head)

suctionLine.run()

# --- Pump Configuration ---
# Create the pump taking suction from the pipe outlet

pump1 = neqsim.process.equipment.pump.Pump('oil pump', suctionLine.getOutStream())
pump1.setOutletPressure(60.0, 'bara')  # Required discharge pressure
pump1.setCheckNPSH(True)               # Enable cavitation monitoring
pump1.setNPSHMargin(1.3)               # Require NPSHa >= 1.3 × NPSHr

# --- Pump Performance Curves ---
# Define pump characteristic curves at the operating speed
# These are typically from manufacturer datasheets

speed = [3259]                            # Pump speed in RPM
flow = [[1, 50, 70, 130]]                 # Flow points in m³/hr
head = [[250, 240, 230, 180]]             # Head in meters at each flow
eff = [[5, 40, 50, 52]]                   # Efficiency in % at each flow
npsh = [[2.0, 4.3, 6.0, 8.0]]             # NPSHr curve in meters

pump1.getPumpChart().setCurves([], speed, flow, head, eff)
pump1.getPumpChart().setNPSHCurve(npsh)
pump1.getPumpChart().setHeadUnit("meter")
pump1.setSpeed(3259)

pump1.run()

# --- Results Analysis ---
print("=== Pump & Suction System Results ===")
print(f"Flow rate (m3/hr): {pump_feed.getFlowRate('idSm3/hr')}")
print(f"Separator outlet pressure (bara): {pump_feed.getPressure('bara')}")
print(f"Valve outlet pressure (bara): {separatorValve.getOutletPressure()}")
print(f"Valve pressure drop (bar): {separatorValve.getDeltaP()}")
print(f"Pump inlet pressure (bara): {pump1.getInletPressure()}")
print(f"Pump outlet pressure (bara): {pump1.getOutletPressure()}")
print(f"Pump NPSHa (meter): {pump1.getNPSHAvailable()}")
print(f"Pump NPSHr (meter): {pump1.getNPSHRequired()}")
print(f"Pump power (kW): {pump1.getPower('kW')}")
print(f"Cavitation risk: {'YES' if pump1.isCavitating() else 'NO'}")

Key Points

Parameter Purpose
setCv(350) Valve flow coefficient - determines pressure drop for given flow
setPercentValveOpening(80) Valve position (0-100%); partially open for control margin
setLength(20.0) Total equivalent length of suction piping including fittings
setDiameter(0.2) Internal pipe diameter - larger diameter reduces friction loss
setPipeWallRoughness(1.0e-5) Surface roughness; affects friction factor
setElevation(-20) Negative elevation means pump is below source (increases NPSHa)
setCheckNPSH(True) Enables automatic cavitation detection
setNPSHMargin(1.3) Safety factor; typical values 1.1–1.5
setNPSHCurve(npsh) Required NPSH as function of flow from pump datasheet

Understanding the Results

Design Considerations

  1. Suction pipe sizing: Velocity in suction lines should typically be 1–2 m/s for liquids to minimize friction losses while avoiding sedimentation.

  2. Elevation effects: Locating the pump below the liquid source is the most reliable way to ensure adequate NPSHa.

  3. Temperature sensitivity: Hot liquids have higher vapor pressure, reducing NPSHa. Consider subcooling or elevated suction pressure for near-boiling liquids.

  4. Transient conditions: During startup or upset conditions, flow rates may exceed design, increasing NPSHr while simultaneously increasing suction line losses—always check NPSHa at maximum expected flow.

Pump Theory

Pump Theory and Implementation in NeqSim

Overview

NeqSim provides comprehensive centrifugal pump simulation through the Pump and PumpChart classes. The implementation supports:

Also see pump usage guide.


Theoretical Background

Affinity Laws (Similarity Laws)

The affinity laws relate pump performance at different speeds:

Parameter Relationship
Flow Q₂/Q₁ = N₂/N₁
Head H₂/H₁ = (N₂/N₁)²
Power P₂/P₁ = (N₂/N₁)³
NPSH NPSH₂/NPSH₁ = (N₂/N₁)²

Hydraulic Power and Efficiency

P_hydraulic = ρ·g·Q·H = Q·ΔP
P_shaft = P_hydraulic / η

Net Positive Suction Head (NPSH)

NPSHₐ = (P_suction - P_vapor) / (ρ·g) + v²/(2g)

Cavitation occurs when NPSHₐ < NPSHᵣ. A safety margin of 1.3× is typically required.

Density Correction

Pump curves measured with water require correction for other fluids:

H_actual = H_chart × (ρ_chart / ρ_actual)

Implementation

Class Structure

Pump (PumpInterface)
├── PumpChart (PumpChartInterface)
│   ├── PumpCurve (individual speed curves)
│   └── PumpChartAlternativeMapLookupExtrapolate (alternative implementation)
└── PumpMechanicalDesign

Key Classes

Class Purpose
Pump Main pump equipment model
PumpChart Performance curve management
PumpChartInterface Interface for pump chart implementations

Usage Guide

Basic Pump Setup

// Create fluid and stream
SystemInterface fluid = new SystemSrkEos(298.15, 2.0);
fluid.addComponent("water", 1.0);
Stream feedStream = new Stream("Feed", fluid);
feedStream.run();

// Create pump with outlet pressure
Pump pump = new Pump("MainPump", feedStream);
pump.setOutletPressure(10.0, "bara");
pump.setIsentropicEfficiency(0.75);
pump.run();

System.out.println("Power: " + pump.getPower("kW") + " kW");
System.out.println("Outlet T: " + pump.getOutletTemperature() + " K");

Using Pump Curves

// Define pump curves at multiple speeds
double[] speed = {1000.0, 1500.0};
double[][] flow = {
    {10, 20, 30, 40, 50},      // m³/hr at 1000 rpm
    {15, 30, 45, 60, 75}       // m³/hr at 1500 rpm
};
double[][] head = {
    {120, 115, 108, 98, 85},   // meters at 1000 rpm
    {270, 259, 243, 220, 191}  // meters at 1500 rpm
};
double[][] efficiency = {
    {65, 75, 82, 80, 72},      // % at 1000 rpm
    {67, 77, 84, 82, 74}       // % at 1500 rpm
};

// chartConditions: [refMW, refTemp, refPressure, refZ, refDensity]
double[] chartConditions = {18.0, 298.15, 1.0, 1.0, 998.0};

Pump pump = new Pump("ChartPump", feedStream);
pump.getPumpChart().setCurves(chartConditions, speed, flow, head, efficiency);
pump.getPumpChart().setHeadUnit("meter");
pump.setSpeed(1200.0);
pump.run();

Density Correction

When pumping fluids with different density than the chart test fluid:

// Option 1: Set via chartConditions (5th element)
double[] chartConditions = {18.0, 298.15, 1.0, 1.0, 998.0}; // 998 kg/m³ reference
pump.getPumpChart().setCurves(chartConditions, speed, flow, head, efficiency);

// Option 2: Set directly
pump.getPumpChart().setReferenceDensity(998.0);

// Check if correction is active
if (pump.getPumpChart().hasDensityCorrection()) {
    double correctedHead = pump.getPumpChart().getCorrectedHead(flow, speed, actualDensity);
}

NPSH Monitoring

// Enable NPSH checking
pump.setCheckNPSH(true);
pump.setNPSHMargin(1.3);  // Safety factor

// Check cavitation risk
double npshAvailable = pump.getNPSHAvailable();
double npshRequired = pump.getNPSHRequired();
boolean cavitating = pump.isCavitating();

// Set NPSH curve from manufacturer data
double[][] npshCurve = {
    {2.0, 2.5, 3.2, 4.0, 5.2},  // NPSHr at 1000 rpm
    {4.5, 5.6, 7.2, 9.0, 11.7}  // NPSHr at 1500 rpm
};
pump.getPumpChart().setNPSHCurve(npshCurve);

Operating Status Monitoring

// Check operating status
String status = pump.getPumpChart().getOperatingStatus(flowRate, speed);
// Returns: "OPTIMAL", "NORMAL", "LOW_EFFICIENCY", "SURGE", or "STONEWALL"

// Check specific conditions
boolean surging = pump.getPumpChart().checkSurge2(flowRate, speed);
boolean stonewall = pump.getPumpChart().checkStoneWall(flowRate, speed);

// Get best efficiency point
double bepFlow = pump.getPumpChart().getBestEfficiencyFlowRate();
double specificSpeed = pump.getPumpChart().getSpecificSpeed();

Complete Example: Pump with Suction System (Python)

This example demonstrates a realistic pump system with:

import neqsim

# Get feed stream from upstream process (e.g., oil from separator)
pump_feed = oseberg_process.get('main process').getUnit('3RD stage separator').getOilOutStream()

# === Separator Outlet Control Valve ===
# Controls flow from separator to pump suction
separatorValve = neqsim.process.equipment.valve.ThrottlingValve("SeparatorOutletValve", pump_feed)
separatorValve.setCv(200)                   # Valve Cv (flow coefficient in US gpm/psi^0.5)
separatorValve.setPercentValveOpening(80)   # 80% open - allows for control margin
separatorValve.setIsCalcOutPressure(True)   # Calculate outlet pressure from Cv
separatorValve.run()

# === Suction Pipeline ===
# Models pressure drop and elevation effects on NPSH
suctionLine = neqsim.process.equipment.pipeline.PipeBeggsAndBrills("SuctionLine", separatorValve.getOutletStream())
suctionLine.setLength(20.0)              # Pipe length in meters
suctionLine.setDiameter(0.2)             # Pipe inner diameter in meters
suctionLine.setPipeWallRoughness(1.0e-5) # Internal roughness in meters
suctionLine.setElevation(-20)            # Negative = pump is 20m below separator (adds static head)
suctionLine.run()

# === Centrifugal Pump ===
pump1 = neqsim.process.equipment.pump.Pump('oil pump', suctionLine.getOutStream())
pump1.setOutletPressure(60.0, 'bara')    # Target discharge pressure

# Enable NPSH monitoring with 1.3x safety margin
pump1.setCheckNPSH(True)
pump1.setNPSHMargin(1.3)

# Define manufacturer pump curves (single speed)
speed = [3259]                           # rpm
flow = [[1, 50, 70, 130]]               # m³/hr
head = [[250, 240, 230, 180]]           # meters
eff = [[5, 40, 50, 52]]                 # efficiency %

# NPSHr curve (meters) - must match flow array dimensions
npsh = [[2.0, 4.3, 6.0, 8.0]]

# Configure pump chart
pump1.getPumpChart().setCurves([], speed, flow, head, eff)
pump1.getPumpChart().setNPSHCurve(npsh)
pump1.getPumpChart().setHeadUnit("meter")
pump1.setSpeed(3259)

pump1.run()

# === Results ===
print("=== Pump & Suction Line Results ===")
print(f"Flow rate (m3/hr): {pump_feed.getFlowRate('idSm3/hr'):.2f}")
print(f"Separator outlet pressure (bara): {pump_feed.getPressure('bara'):.2f}")
print(f"Pump inlet pressure (bara): {pump1.getInletPressure():.2f}")
print(f"Pump outlet pressure (bara): {pump1.getOutletPressure():.2f}")
print(f"Pump head (m): {pump1.getPumpChart().getHead(pump_feed.getFlowRate('m3/hr'), 3259):.1f}")
print(f"Pump efficiency (%): {pump1.getPumpChart().getEfficiency(pump_feed.getFlowRate('m3/hr'), 3259):.1f}")
print(f"Pump NPSHa (meter): {pump1.getNPSHAvailable():.2f}")
print(f"Pump NPSHr (meter): {pump1.getNPSHRequired():.2f}")
print(f"Pump power (kW): {pump1.getPower('kW'):.1f}")
print(f"Cavitation risk: {'YES - INCREASE SUCTION PRESSURE' if pump1.isCavitating() else 'NO'}")

Key Points

  1. Suction System Design: The suction line elevation affects NPSHₐ. Negative elevation (pump below source) adds static head, improving NPSH margin.

  2. Control Valve Sizing: The Cv value determines pressure drop at the given flow. Use setIsCalcOutPressure(True) to calculate outlet pressure from Cv.

  3. NPSH Monitoring: Enable with setCheckNPSH(True). The pump calculates:

    • NPSHₐ from suction conditions (pressure, temperature, vapor pressure)
    • NPSHᵣ from the manufacturer curve (interpolated at operating flow)
    • Warns if NPSHₐ < margin × NPSHᵣ
  4. Pump Curves: The setCurves() method accepts:

    • Empty array [] for chartConditions (or include reference density as 5th element)
    • Speed array (can be single or multiple speeds)
    • 2D arrays for flow, head, efficiency (one row per speed)
  5. NPSH Curve: Must be set separately via setNPSHCurve() with same dimensions as flow array.


API Reference

Pump Class

Key Methods

Method Description
setOutletPressure(double, String) Set target outlet pressure
setIsentropicEfficiency(double) Set pump efficiency (0-1)
setSpeed(double) Set pump speed in rpm
getPower() Get shaft power in watts
getPower(String) Get power in specified unit
getNPSHAvailable() Calculate available NPSH in meters
getNPSHRequired() Get required NPSH from chart or estimate
isCavitating() Check if pump is at cavitation risk
setCheckNPSH(boolean) Enable/disable NPSH monitoring
getPumpChart() Get the pump chart object

PumpChart Class

Curve Setup Methods

Method Description
setCurves(double[], double[], double[][], double[][], double[][]) Set complete pump curves
setHeadUnit(String) Set head unit: "meter" or "kJ/kg"
setNPSHCurve(double[][]) Set NPSH required curves
setReferenceDensity(double) Set reference density for correction

Performance Query Methods

Method Description
getHead(double, double) Get head at flow and speed
getCorrectedHead(double, double, double) Get density-corrected head
getEfficiency(double, double) Get efficiency at flow and speed
getNPSHRequired(double, double) Get NPSH required at flow and speed
getSpeed(double, double) Calculate speed for given flow and head

Monitoring Methods

Method Description
getOperatingStatus(double, double) Get operating status string
checkSurge2(double, double) Check if in surge condition
checkStoneWall(double, double) Check if at stonewall
getBestEfficiencyFlowRate() Get flow at BEP
getSpecificSpeed() Calculate pump specific speed
hasDensityCorrection() Check if density correction is active
hasNPSHCurve() Check if NPSH curve is available

Chart Conditions Array

The chartConditions array passed to setCurves() contains reference conditions:

Index Parameter Unit Description
0 refMW kg/kmol Reference molecular weight
1 refTemperature K Reference temperature
2 refPressure bara Reference pressure
3 refZ - Reference compressibility
4 refDensity kg/m³ Reference fluid density (optional)

Note: Element [4] is optional for backward compatibility. If omitted, no density correction is applied.


Viscosity Correction (Heavy Oil / Viscous Fluids)

Pump performance is significantly affected by fluid viscosity. Curves measured with water or light oil require correction when pumping viscous fluids like heavy crude oil.

Hydraulic Institute (HI) Method

NeqSim implements the Hydraulic Institute viscosity correction method for centrifugal pumps. The correction uses the B parameter:

B = 26.6 × ν^0.5 × H^0.0625 / (Q^0.375 × N^0.25)

Where:

Correction Factors

Parameter Factor Description
Flow Cq Q_viscous = Q_water × Cq
Head Ch H_viscous = H_water × Ch
Efficiency η_viscous = η_water × Cη

Valid range: 4 - 4000 cSt (below 4 cSt, water properties assumed)

Usage Example (Java)

// Create pump with chart
Pump pump = new Pump("ViscousPump", feedStream);
pump.getPumpChart().setCurves(chartConditions, speed, flow, head, efficiency);

// Enable viscosity correction
pump.getPumpChart().setReferenceViscosity(1.0);       // Chart measured with water (1 cSt)
pump.getPumpChart().setUseViscosityCorrection(true);  // Enable correction

// Set pump parameters
pump.getPumpChart().setReferenceFlow(100.0);          // BEP flow (m³/hr)
pump.getPumpChart().setReferenceHead(100.0);          // BEP head (meters)
pump.getPumpChart().setReferenceSpeed(1500.0);        // Reference speed (rpm)

pump.run();

// Check applied corrections
System.out.println("Flow correction factor Cq: " + pump.getPumpChart().getFlowCorrectionFactor());
System.out.println("Head correction factor Ch: " + pump.getPumpChart().getHeadCorrectionFactor());
System.out.println("Efficiency correction Cη: " + pump.getPumpChart().getEfficiencyCorrectionFactor());

Usage Example (Python)

import neqsim
from neqsim.process import stream, pump

# Create stream with viscous oil
oil = neqsim.thermo.system.SystemSrkEos(323.15, 5.0)
oil.addComponent("nC20", 1.0)  # Heavy hydrocarbon
oil.setMixingRule("classic")

feed = stream.stream("ViscousOilFeed", oil)
feed.setFlowRate(100.0, "kg/hr")
feed.run()

# Create pump with viscosity correction
viscous_pump = pump.pump("OilBooster", feed)
viscous_pump.getPumpChart().setReferenceViscosity(1.0)
viscous_pump.getPumpChart().setUseViscosityCorrection(True)
viscous_pump.setOutletPressure(10.0, "bara")
viscous_pump.run()

print(f"Actual viscosity: {feed.getFluid().getKinematicViscosity('cSt'):.1f} cSt")
print(f"Head correction: {viscous_pump.getPumpChart().getHeadCorrectionFactor():.3f}")
print(f"Efficiency correction: {viscous_pump.getPumpChart().getEfficiencyCorrectionFactor():.3f}")

ESP Pump (Electric Submersible Pump)

The ESPPump class extends Pump for handling multiphase gas-liquid flows commonly encountered in oil well production.

Key Features

GVF Degradation Model

Head degradation follows a quadratic relationship:

f = 1 - A × GVF - B × GVF²

Where default coefficients are: A = 0.5, B = 2.0

Operating Limits

Condition Default Threshold Description
Surging GVF > 15% Unstable operation begins
Gas Lock GVF > 30% Pump loses prime, flow stops

Usage Example (Java)

// Create multiphase stream (gas + liquid)
SystemInterface fluid = new SystemSrkEos(323.15, 30.0);
fluid.addComponent("methane", 0.05);     // 5% gas
fluid.addComponent("n-heptane", 0.95);   // 95% liquid
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

Stream wellStream = new Stream("WellProduction", fluid);
wellStream.setFlowRate(1000.0, "kg/hr");
wellStream.run();

// Create ESP pump
ESPPump esp = new ESPPump("ESP-1", wellStream);
esp.setNumberOfStages(100);           // 100-stage pump
esp.setHeadPerStage(10.0);            // 10 m head per stage

// Configure GVF handling
esp.setMaxGVF(0.30);                  // 30% max GVF before gas lock
esp.setSurgingGVF(0.15);              // 15% - surging onset
esp.setHasGasSeparator(true);         // Include rotary gas separator
esp.setGasSeparatorEfficiency(0.60);  // 60% gas separation

esp.run();

// Check operating status
System.out.println("GVF at inlet: " + (esp.getGasVoidFraction() * 100) + "%");
System.out.println("Head degradation: " + (1 - esp.getHeadDegradationFactor()) * 100 + "% loss");
System.out.println("Surging: " + esp.isSurging());
System.out.println("Gas locked: " + esp.isGasLocked());
System.out.println("Pressure boost: " + (esp.getOutletPressure() - esp.getInletPressure()) + " bara");

Usage Example (Python)

import neqsim
from neqsim.thermo.system import SystemSrkEos
from neqsim.process.equipment.pump import ESPPump

# Create multiphase well fluid
well_fluid = SystemSrkEos(353.15, 25.0)
well_fluid.addComponent("methane", 0.08)
well_fluid.addComponent("n-heptane", 0.92)
well_fluid.setMixingRule("classic")
well_fluid.setMultiPhaseCheck(True)

well_stream = neqsim.process.stream.stream("WellStream", well_fluid)
well_stream.setFlowRate(2000.0, "kg/hr")
well_stream.run()

# Create and configure ESP
esp = ESPPump("ESP-1", well_stream)
esp.setNumberOfStages(80)
esp.setHeadPerStage(12.0)
esp.setMaxGVF(0.25)
esp.setHasGasSeparator(True)
esp.setGasSeparatorEfficiency(0.70)
esp.run()

# Monitor performance
print(f"Inlet GVF: {esp.getGasVoidFraction()*100:.1f}%")
print(f"Head degradation factor: {esp.getHeadDegradationFactor():.3f}")
print(f"Effective head: {esp.calculateTotalHead():.1f} m")
print(f"Is surging: {esp.isSurging()}")

ESPPump API Reference

Method Description
setNumberOfStages(int) Set number of impeller stages
setHeadPerStage(double) Set head per stage (meters)
setMaxGVF(double) Set gas lock threshold (0-1)
setSurgingGVF(double) Set surging onset threshold (0-1)
setHasGasSeparator(boolean) Enable rotary gas separator
setGasSeparatorEfficiency(double) Set separator efficiency (0-1)
getGasVoidFraction() Get calculated inlet GVF
getHeadDegradationFactor() Get head degradation (0-1)
isSurging() Check if pump is surging
isGasLocked() Check if pump has lost prime
calculateTotalHead() Get total developed head

Head Unit Options

Unit Description Pressure Calculation
"meter" Meters of fluid ΔP = ρ·g·H
"kJ/kg" Specific energy ΔP = E·ρ·1000

Test Coverage

The pump implementation includes comprehensive tests:

Test Class Tests Coverage
PumpTest 3 Basic pump operations
PumpChartTest 3 Curve interpolation
PumpAffinityLawTest 6 Affinity law scaling
PumpNPSHTest 8 Cavitation detection
PumpNPSHCurveTest 12 NPSH curve handling
PumpDensityCorrectionTest 7 Density correction
PumpViscosityCorrectionTest 12 HI viscosity correction method
ESPPumpTest 12 ESP multiphase handling

Total: 63 tests


References

  1. Centrifugal Pumps, I.J. Karassik et al., McGraw-Hill
  2. Pump Handbook, Igor J. Karassik, McGraw-Hill
  3. API 610 - Centrifugal Pumps for Petroleum, Petrochemical and Natural Gas Industries
  4. ISO 9906 - Rotodynamic pumps - Hydraulic performance acceptance tests

Expanders

Expanders and Turbines

Documentation for expansion equipment in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.expander

Classes:

Class Description
Expander General gas expander
TurboExpander Turboexpander with shaft coupling
ExpanderCompressorModule Compander unit

Gas expanders are used for:


Expander Class

Basic Usage

import neqsim.process.equipment.expander.Expander;

// Create expander
Expander expander = new Expander("EX-100", gasStream);
expander.setOutletPressure(10.0, "bara");
expander.setIsentropicEfficiency(0.85);
expander.run();

// Results
double power = expander.getPower("kW");
double outletTemp = expander.getOutletTemperature("C");

Outlet Specification

// By outlet pressure
expander.setOutletPressure(10.0, "bara");

// By pressure ratio
expander.setPressureRatio(5.0);

// By outlet temperature
expander.setOutletTemperature(-50.0, "C");

Turboexpander

For direct shaft coupling to compressor.

Basic Usage

import neqsim.process.equipment.expander.TurboExpander;

TurboExpander turboExpander = new TurboExpander("TEX-100", gasStream);
turboExpander.setOutletPressure(10.0, "bara");
turboExpander.setIsentropicEfficiency(0.85);
turboExpander.run();

Shaft Coupling

// Couple expander to compressor
turboExpander.setCoupledCompressor(compressor);

// Power balance
turboExpander.run();
compressor.run();

double expanderPower = turboExpander.getPower("kW");
double compressorPower = compressor.getPower("kW");
double netPower = expanderPower - compressorPower;

Power Recovery

Isentropic Power

$$W_{isentropic} = \dot{m} \cdot (h_1 - h_{2s})$$

Where:

Actual Power

$$W_{actual} = \eta_{isentropic} \cdot W_{isentropic}$$

Temperature Drop

// Get temperatures
double T_in = expander.getInletTemperature("C");
double T_out = expander.getOutletTemperature("C");
double deltaT = T_in - T_out;

// Compare to JT expansion (throttling)
ThrottlingValve valve = new ThrottlingValve("JT", gasStream);
valve.setOutletPressure(10.0, "bara");
valve.run();

double T_out_JT = valve.getOutletTemperature("C");
double deltaT_JT = T_in - T_out_JT;

System.out.println("Expander cooling: " + deltaT + " C");
System.out.println("JT cooling: " + deltaT_JT + " C");

Compander Systems

Combined expander-compressor on single shaft.

Usage

import neqsim.process.equipment.expander.ExpanderCompressorModule;

ExpanderCompressorModule compander = new ExpanderCompressorModule("Compander");
compander.setExpanderInletStream(hotGas);
compander.setCompressorInletStream(coldGas);
compander.setExpanderOutletPressure(10.0, "bara");
compander.setCompressorOutletPressure(40.0, "bara");
compander.setIsentropicEfficiency(0.85);
compander.run();

double netPower = compander.getNetPower("kW");  // Can be positive or negative

Examples

Example 1: Simple Expander

import neqsim.thermo.system.SystemSrkEos;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.expander.Expander;

// High pressure gas
SystemSrkEos gas = new SystemSrkEos(320.0, 80.0);
gas.addComponent("methane", 0.90);
gas.addComponent("ethane", 0.07);
gas.addComponent("propane", 0.03);
gas.setMixingRule("classic");

Stream feed = new Stream("HP Gas", gas);
feed.setFlowRate(50000.0, "kg/hr");
feed.run();

// Expander
Expander expander = new Expander("EX-100", feed);
expander.setOutletPressure(20.0, "bara");
expander.setIsentropicEfficiency(0.85);
expander.run();

System.out.println("Inlet: " + feed.getTemperature("C") + " C, " + feed.getPressure("bara") + " bara");
System.out.println("Outlet: " + expander.getOutletTemperature("C") + " C, " + expander.getOutletPressure("bara") + " bara");
System.out.println("Power generated: " + expander.getPower("kW") + " kW");

Example 2: NGL Recovery with Turboexpander

// Rich gas feed
SystemSrkEos richGas = new SystemSrkEos(300.0, 70.0);
richGas.addComponent("nitrogen", 0.02);
richGas.addComponent("methane", 0.75);
richGas.addComponent("ethane", 0.10);
richGas.addComponent("propane", 0.08);
richGas.addComponent("n-butane", 0.05);
richGas.setMixingRule("classic");

Stream feed = new Stream("Rich Gas", richGas);
feed.setFlowRate(100000.0, "Sm3/day");
feed.run();

// Pre-cooling
Cooler precooler = new Cooler("Pre-cooler", feed);
precooler.setOutletTemperature(280.0, "K");
precooler.run();

// Turboexpander
TurboExpander expander = new TurboExpander("TEX-100", precooler.getOutletStream());
expander.setOutletPressure(25.0, "bara");
expander.setIsentropicEfficiency(0.82);
expander.run();

// Cold separator
Separator coldSep = new Separator("Cold Sep", expander.getOutletStream());
coldSep.run();

// Results
System.out.println("Expander outlet: " + expander.getOutletTemperature("C") + " C");
System.out.println("Power: " + expander.getPower("kW") + " kW");
System.out.println("NGL recovered: " + coldSep.getLiquidOutStream().getFlowRate("m3/hr") + " m³/hr");

Example 3: Expander vs JT Valve Comparison

// Same inlet conditions
SystemSrkEos gas = new SystemSrkEos(300.0, 60.0);
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.10);
gas.addComponent("propane", 0.05);
gas.setMixingRule("classic");

Stream feed1 = new Stream("Feed 1", gas);
feed1.setFlowRate(10000.0, "kg/hr");
feed1.run();

Stream feed2 = new Stream("Feed 2", gas.clone());
feed2.setFlowRate(10000.0, "kg/hr");
feed2.run();

// JT valve
ThrottlingValve valve = new ThrottlingValve("JT Valve", feed1);
valve.setOutletPressure(15.0, "bara");
valve.run();

// Expander
Expander expander = new Expander("Expander", feed2);
expander.setOutletPressure(15.0, "bara");
expander.setIsentropicEfficiency(0.85);
expander.run();

System.out.println("JT Valve outlet: " + valve.getOutletTemperature("C") + " C");
System.out.println("Expander outlet: " + expander.getOutletTemperature("C") + " C");
System.out.println("Extra cooling: " + (valve.getOutletTemperature("C") - expander.getOutletTemperature("C")) + " C");
System.out.println("Power recovered: " + expander.getPower("kW") + " kW");

Turboexpander

TurboExpanderCompressor Model

This document summarizes the mathematical basis of the coupled expander/compressor model, how reference curves are applied (and can be replaced), and provides a usage walkthrough for configuring and running the unit in a process simulation.


Mathematical Basis

The expander and compressor share a shaft speed that is iteratively adjusted until the expander power balances the compressor power plus bearing losses using a Newton-Raphson iteration. The key computational steps are described below.

Expander Calculations

1. Isentropic Enthalpy Drop

The isentropic enthalpy drop across the expander is calculated from an isentropic flash at the target outlet pressure:

$$ \Delta h_s = (h_{in} - h_{out,s}) \times 1000 \quad \text{[J/kg]} $$

where $h_{in}$ is the inlet enthalpy and $h_{out,s}$ is the isentropic outlet enthalpy.

2. Velocity Ratio Calculation

The tip speed $U$ and spouting (jet) velocity $C$ are computed as:

$$ U = \frac{\pi \cdot D \cdot N}{60} $$

$$ C = \sqrt{2 \cdot \Delta h_s} $$

The velocity ratio is then:

$$ u_c = \frac{U}{C \cdot u_{c,design}} $$

An efficiency correction factor is evaluated from the UC reference curve based on this ratio.

3. Efficiency Corrections

The actual expander isentropic efficiency is calculated by applying correction factors:

$$ \eta_s = \eta_{s,design} \cdot f_{UC}(u_c) \cdot f_{Q/N}\left(\frac{Q/N}{(Q/N)_{design}}\right) $$

where:

4. Expander Shaft Power

The expander shaft power is:

$$ W_{expander} = \dot{m} \cdot \Delta h_s \cdot \eta_s $$

where $\dot{m}$ is the mass flow rate.


Compressor Calculations

1. Head and Efficiency Corrections

The compressor polytropic head and efficiency are corrected for off-design operation:

$$ H_p = H_{p,design} \cdot f_{head}\left(\frac{Q/N}{(Q/N)_{design}}\right) \cdot \left(\frac{N}{N_{design}}\right)^2 $$

$$ \eta_p = \eta_{p,design} \cdot f_{\eta}\left(\frac{Q/N}{(Q/N)_{design}}\right) $$

where:

2. Compressor Shaft Power

The compressor shaft power is:

$$ W_{comp} = \frac{\dot{m} \cdot H_p}{\eta_p} $$


Power Balance and Speed Iteration

The Newton-Raphson iteration solves for the shaft speed $N$ that satisfies:

$$ f(N) = W_{expander} - \left(W_{comp} + W_{bearing}\right) = 0 $$

where the bearing losses are modeled as a quadratic function of speed:

$$ W_{bearing} = k \cdot N^2 $$

The iteration continues until the power mismatch is negligible or iteration limits are reached. The final speed is applied to compute outlet stream properties.


Reference Curves

Three types of reference curves tune performance away from the design point:

1. UC/Efficiency Curve

A constrained parabola through the peak at $(u_c = 1, \eta = 1)$:

$$ f_{UC}(u_c) = a \cdot u_c^2 + b \cdot u_c + c $$

The curve can be replaced with setUCcurve(ucValues, efficiencyValues) if alternate test data are available.

2. Q/N Efficiency Curve

A monotonic cubic Hermite spline built from paired Q/N and efficiency arrays via setQNEfficiencycurve:

$$ f_{\eta}\left(\frac{Q/N}{(Q/N)_{design}}\right) = \text{spline interpolation} $$

Values are extrapolated linearly outside the provided range, allowing off-map operation while preserving trend continuity.

3. Q/N Head Curve

A similar cubic Hermite spline created with setQNHeadcurve that scales polytropic head at off-design flows:

$$ f_{head}\left(\frac{Q/N}{(Q/N)_{design}}\right) = \text{spline interpolation} $$

Like the efficiency spline, it preserves monotonicity and extrapolates linearly beyond the data range.

Note: Curve coefficients are stored on the equipment instance and can be replaced at runtime to test alternative reference maps or updated dynamically from external performance monitoring tools.


Using the Model

Step 1: Construct the Unit and Streams

Clone feeds for the expander and compressor outputs when instantiating the equipment.

Step 2: Set Design Parameters

Provide impeller diameter, design speed, efficiencies, design Q/N, and optional expander design Q/N if expander flow corrections are needed. The defaults mirror the embedded design values but can be overridden through the available setters.

Step 3: Load Reference Curves (Optional)

If site-specific head or efficiency curves exist, call setUCcurve, setQNEfficiencycurve, and setQNHeadcurve with measured points before running the unit.

Step 4: Run the Model

Call run(UUID id) (or the no-argument overload) to iterate speed matching and populate result fields and outlet streams. Retrieve shaft powers with getPowerExpander(unit) and getPowerCompressor(unit) or inspect efficiencies, head, and Q/N ratios through the getters.

Example Code

A realistic setup that mirrors common plant data collection and map-updating workflows:

TurboExpanderCompressor turboExpanderComp = new TurboExpanderCompressor(
    "TurboExpanderCompressor", jt_tex_splitter.getSplitStream(0));
turboExpanderComp.setUCcurve(
    new double[] {0.9964751359624449, 0.7590835113213541, 0.984295619176559, 0.8827799803397821,
        0.9552460269880922, 1.0},
    new double[] {0.984090909090909, 0.796590909090909, 0.9931818181818183, 0.9363636363636364,
        0.9943181818181818, 1.0});
turboExpanderComp.setQNEfficiencycurve(
    new double[] {0.5, 0.7, 0.85, 1.0, 1.2, 1.4, 1.6},
    new double[] {0.88, 0.91, 0.95, 1.0, 0.97, 0.85, 0.6});
turboExpanderComp.setQNHeadcurve(
    new double[] {0.5, 0.8, 1.0, 1.2, 1.4, 1.6},
    new double[] {1.1, 1.05, 1.0, 0.9, 0.7, 0.4});
turboExpanderComp.setImpellerDiameter(0.424);
turboExpanderComp.setDesignSpeed(6850.0);
turboExpanderComp.setExpanderDesignIsentropicEfficiency(0.88);
turboExpanderComp.setDesignUC(0.7);
turboExpanderComp.setDesignQn(0.03328);
turboExpanderComp.setExpanderOutPressure(inp.expander_out_pressure);
turboExpanderComp.setCompressorDesignPolytropicEfficiency(0.81);
turboExpanderComp.setCompressorDesignPolytropicHead(20.47);
turboExpanderComp.setMaximumIGVArea(1.637e4);

// Run the coupled model and retrieve power with unit conversion
turboExpanderComp.run();
double expanderPowerMW = turboExpanderComp.getPowerExpander("MW");
double compressorPowerMW = turboExpanderComp.getPowerCompressor("MW");

Parameter Reference

Velocity Ratio (UC) Curve

Parameter Description
setUCcurve(ucValues, effValues) Normalizes the velocity ratio $u_c = \frac{U}{C \cdot u_{c,design}}$ to an efficiency multiplier via a constrained parabola fitted to the supplied points

Q/N Curves

Parameter Description
setQNEfficiencycurve(qnValues, effValues) Cubic Hermite spline that scales expander and compressor efficiencies against flow coefficient deviations $Q/N$
setQNHeadcurve(qnValues, headValues) Spline used to scale the compressor polytropic head for off-design flows before applying the $(N/N_{design})^2$ speed law

Geometry and Design Point

Parameter Description
setImpellerDiameter(D) Impeller diameter [m] — sets the peripheral velocity $U$ at design, anchoring UC corrections
setDesignSpeed(N) Design rotational speed [rpm] — anchor for Newton iteration speed matching
setExpanderDesignIsentropicEfficiency(η) Base isentropic efficiency multiplied by curve correction factors
setCompressorDesignPolytropicEfficiency(η) Base polytropic efficiency for the compressor
setCompressorDesignPolytropicHead(Hp) Design polytropic head [kJ/kg]
setDesignUC(uc) Design velocity ratio for the expander
setDesignQn(qn) Reference flow coefficient $(Q/N)_{design}$ for the compressor
setDesignExpanderQn(qn) Reference flow coefficient for the expander (optional)

Operating Conditions

Parameter Description
setExpanderOutPressure(P) Target outlet pressure for the isentropic flash that produces $\Delta h_s$

IGV Geometry

Parameter Description
setMaximumIGVArea(A) Maximum inlet guide vane throat area [mm²]
setIgvAreaIncreaseFactor(f) Optional factor to expand available IGV area

Tip: The same update paths can be invoked during runtime if monitoring identifies drift in the reference maps; supplying new curve points and re-running will propagate the new performance predictions.


IGV Handling

The Inlet Guide Vane (IGV) opening is computed from the last stage enthalpy drop, mass flow, and volumetric flow each time run() completes.

IGV Calculation Method

The helper evaluateIGV performs the following:

  1. Infer density from the fluid properties
  2. Estimate nozzle velocity from half the stage enthalpy drop:

$$ v_{nozzle} = \sqrt{\Delta h_{stage}} $$

  1. Derive required area to pass the flow:

$$ A_{required} = \frac{\dot{V}}{v_{nozzle}} $$

  1. Calculate IGV opening as the area ratio:

$$ \text{IGV}_{opening} = \min\left(\frac{A_{required}}{A_{throat}}, 1.0\right) $$

IGV Area Expansion

If the required area exceeds the installed IGV area, an optional enlargement factor (setIgvAreaIncreaseFactor) increases the available area:

$$ A_{available} = A_{max} \cdot f_{increase} $$

IGV Output Methods

Method Description
calcIGVOpening() Returns the calculated IGV opening fraction (0–1)
calcIGVOpenArea() Returns the actual open area [mm²]
getCurrentIGVArea() Returns the current IGV throat area [mm²]

Ejectors

Ejector Equipment

Documentation for ejector equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.ejector

Classes:

Class Description
Ejector Steam/gas ejector for compression
EjectorDesignResult Design calculation results

Ejectors use the kinetic energy of a high-pressure motive stream to entrain and compress a low-pressure suction stream. Common applications include:


Ejector Class

Basic Usage

import neqsim.process.equipment.ejector.Ejector;

// Create ejector with motive and suction streams
Ejector ejector = new Ejector("Ejector-100", motiveStream, suctionStream);
ejector.setDischargePressure(5.0);  // bara
ejector.setEfficiencyIsentropic(0.75);  // Nozzle efficiency
ejector.setDiffuserEfficiency(0.80);    // Diffuser efficiency
ejector.run();

// Get mixed stream
StreamInterface mixedStream = ejector.getMixedStream();
double dischargeT = mixedStream.getTemperature("C");
double dischargeP = mixedStream.getPressure("bara");

Stream Setup

// High-pressure motive stream (e.g., HP steam or gas)
SystemInterface motiveFluid = new SystemSrkEos(250.0, 10.0);
motiveFluid.addComponent("water", 1.0);
motiveFluid.setMixingRule("classic");

Stream motiveStream = new Stream("Motive Steam", motiveFluid);
motiveStream.setFlowRate(1000.0, "kg/hr");
motiveStream.run();

// Low-pressure suction stream
SystemInterface suctionFluid = new SystemSrkEos(300.0, 1.5);
suctionFluid.addComponent("methane", 0.95);
suctionFluid.addComponent("ethane", 0.05);
suctionFluid.setMixingRule("classic");

Stream suctionStream = new Stream("Suction Gas", suctionFluid);
suctionStream.setFlowRate(500.0, "kg/hr");
suctionStream.run();

Operating Principles

Ejector Sections

An ejector consists of four main sections:

  1. Nozzle: Converts motive stream pressure to velocity
  2. Suction Chamber: Entrains low-pressure gas
  3. Mixing Chamber: Momentum exchange between streams
  4. Diffuser: Converts velocity back to pressure

Energy Balance

The ejector performs isentropic expansion and compression:

$$\eta_{nozzle} = \frac{h_1 - h_2}{h_1 - h_{2s}}$$

$$\eta_{diffuser} = \frac{h_{4s} - h_3}{h_4 - h_3}$$

Where:

Entrainment Ratio

The entrainment ratio (ER) is defined as:

$$ER = \frac{\dot{m}_{suction}}{\dot{m}_{motive}}$$


Design Parameters

Efficiency Settings

// Nozzle isentropic efficiency (typically 0.7-0.9)
ejector.setEfficiencyIsentropic(0.75);

// Diffuser efficiency (typically 0.7-0.85)
ejector.setDiffuserEfficiency(0.80);

Discharge Pressure

// Set target discharge pressure
ejector.setDischargePressure(5.0);  // bara

Design Velocities

// Optional: Override default suction and diffuser velocities
ejector.setDesignSuctionVelocity(30.0);  // m/s
ejector.setDesignDiffuserOutletVelocity(20.0);  // m/s

Connection Lengths

// Optional: Set connection pipe lengths for pressure drop
ejector.setSuctionConnectionLength(2.0);  // m
ejector.setDischargeConnectionLength(3.0);  // m

Mechanical Design

// Get mechanical design parameters
EjectorMechanicalDesign mechDesign = ejector.getMechanicalDesign();

// Calculate sizing
mechDesign.calcDesign();

// Get geometry
double throatDiameter = mechDesign.getThroatDiameter();  // m
double nozzleLength = mechDesign.getNozzleLength();  // m
double mixingLength = mechDesign.getMixingLength();  // m
double diffuserLength = mechDesign.getDiffuserLength();  // m

Usage Examples

Flare Gas Recovery

ProcessSystem process = new ProcessSystem();

// High-pressure motive gas from compressor discharge
Stream motiveGas = new Stream("HP Gas", hpGasFluid);
motiveGas.setFlowRate(2000.0, "kg/hr");
motiveGas.setTemperature(60.0, "C");
motiveGas.setPressure(40.0, "bara");
process.add(motiveGas);

// Low-pressure flare header gas
Stream flareGas = new Stream("Flare Gas", flareGasFluid);
flareGas.setFlowRate(500.0, "kg/hr");
flareGas.setTemperature(40.0, "C");
flareGas.setPressure(1.2, "bara");
process.add(flareGas);

// Ejector to recover flare gas
Ejector fgr = new Ejector("FGR Ejector", motiveGas, flareGas);
fgr.setDischargePressure(8.0);  // bara
fgr.setEfficiencyIsentropic(0.75);
fgr.setDiffuserEfficiency(0.80);
process.add(fgr);

// Run process
process.run();

// Results
double entrainmentRatio = flareGas.getFlowRate("kg/hr") / motiveGas.getFlowRate("kg/hr");
System.out.println("Entrainment ratio: " + entrainmentRatio);
System.out.println("Discharge pressure: " + fgr.getMixedStream().getPressure("bara") + " bara");

Steam Ejector Vacuum System

// HP steam as motive fluid
Stream hpSteam = new Stream("HP Steam", steamFluid);
hpSteam.setFlowRate(1500.0, "kg/hr");
hpSteam.setTemperature(200.0, "C");
hpSteam.setPressure(10.0, "bara");

// Vacuum overhead vapor
Stream vacuumVapor = new Stream("Vacuum Vapor", vaporFluid);
vacuumVapor.setFlowRate(300.0, "kg/hr");
vacuumVapor.setTemperature(50.0, "C");
vacuumVapor.setPressure(0.1, "bara");

// First stage ejector
Ejector ejector1 = new Ejector("1st Stage", hpSteam, vacuumVapor);
ejector1.setDischargePressure(0.5);  // bara
ejector1.run();

// Intercondenser
Cooler intercondenser = new Cooler("Intercondenser", ejector1.getMixedStream());
intercondenser.setOutTemperature(40.0, "C");

// Second stage ejector
Ejector ejector2 = new Ejector("2nd Stage", hpSteam2, intercondenser.getOutletStream());
ejector2.setDischargePressure(1.1);  // bara
ejector2.run();

Performance Curves

Entrainment vs Compression Ratio

For a given motive pressure and geometry, ejector performance follows characteristic curves:

// Calculate performance at different suction pressures
double[] suctionPressures = {0.5, 1.0, 1.5, 2.0};  // bara
for (double Ps : suctionPressures) {
    suctionStream.setPressure(Ps, "bara");
    suctionStream.run();
    ejector.run();

    double compressionRatio = ejector.getMixedStream().getPressure("bara") / Ps;
    System.out.println("Suction P: " + Ps + " bara, CR: " + compressionRatio);
}

Chapter 16: Heat Transfer Equipment

Heat Exchangers

Heat Exchanger Equipment

Documentation for heat transfer equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.heatexchanger

Classes:


Equipment Types

Heater

import neqsim.process.equipment.heatexchanger.Heater;

// Specify outlet temperature
Heater heater = new Heater("E-100", inletStream);
heater.setOutTemperature(80.0, "C");
heater.run();

double duty = heater.getDuty();  // W
System.out.println("Heating duty: " + duty/1000.0 + " kW");

// Or specify duty
Heater heater2 = new Heater("E-101", inletStream);
heater2.setEnergyInput(500000.0);  // 500 kW
heater2.run();

Cooler

import neqsim.process.equipment.heatexchanger.Cooler;

Cooler cooler = new Cooler("E-200", hotStream);
cooler.setOutTemperature(30.0, "C");
cooler.run();

double duty = cooler.getDuty();  // Negative for cooling
System.out.println("Cooling duty: " + (-duty/1000.0) + " kW");

Heat Exchanger

Two-stream heat exchanger with hot and cold sides.

import neqsim.process.equipment.heatexchanger.HeatExchanger;

HeatExchanger hx = new HeatExchanger("E-300", hotStream, coldStream);

// Specify UA value
hx.setUAvalue(10000.0);  // W/K

// Or specify approach temperature
hx.setApproachTemperature(10.0, "C");

hx.run();

// Results
double hotOut = hx.getOutStream(0).getTemperature("C");
double coldOut = hx.getOutStream(1).getTemperature("C");
double duty = hx.getDuty();
double LMTD = hx.getLMTD();

Multi-Stream Heat Exchanger

import neqsim.process.equipment.heatexchanger.MultiStreamHeatExchanger;

MultiStreamHeatExchanger mshx = new MultiStreamHeatExchanger("LNG-100");
mshx.addStream(stream1, "hot");
mshx.addStream(stream2, "hot");
mshx.addStream(stream3, "cold");

mshx.setUAvalue(50000.0);
mshx.run();

Operating Modes

Temperature Specification

// Outlet temperature
heater.setOutTemperature(100.0, "C");

// Temperature change
heater.setdT(50.0, "C");  // ΔT = 50°C

Duty Specification

// Fixed duty
heater.setEnergyInput(1000000.0);  // 1 MW

// Duty from energy stream
EnergyStream energyIn = new EnergyStream("Heat Source");
energyIn.setEnergyFlow(500.0, "kW");
heater.setEnergyStream(energyIn);

UA Specification

// For heat exchangers
hx.setUAvalue(5000.0);  // W/K

// Calculate UA from geometry
double U = 500.0;  // Overall HTC, W/(m²·K)
double A = 100.0;  // Area, m²
hx.setUAvalue(U * A);

Condenser

import neqsim.process.equipment.heatexchanger.Condenser;

Condenser condenser = new Condenser("Overhead Condenser", vaporStream);

// Total condensation
condenser.setOutTemperature(40.0, "C");
condenser.run();

// Partial condensation
condenser.setDewPointTemperature(true);  // Operate at dew point
condenser.run();

// Sub-cooling
condenser.setSubCooling(5.0, "C");  // 5°C subcooling

Dynamic Simulation

heater.setCalculateSteadyState(false);

// Set thermal mass
heater.setThermalMass(10000.0);  // J/K

// Transient response
for (double t = 0; t < 3600; t += 1.0) {
    heater.runTransient();
    double Tout = heater.getOutletStream().getTemperature("C");
}

Example: Gas Cooling Train

ProcessSystem process = new ProcessSystem();

// Hot gas inlet
Stream hotGas = new Stream("Hot Gas", gasFluid);
hotGas.setFlowRate(50000.0, "kg/hr");
hotGas.setTemperature(150.0, "C");
hotGas.setPressure(80.0, "bara");
process.add(hotGas);

// Air cooler
Cooler airCooler = new Cooler("Air Cooler", hotGas);
airCooler.setOutTemperature(60.0, "C");
process.add(airCooler);

// Trim cooler (seawater)
Cooler trimCooler = new Cooler("Trim Cooler", airCooler.getOutletStream());
trimCooler.setOutTemperature(25.0, "C");
process.add(trimCooler);

// Separator for condensate
Separator separator = new Separator("Inlet Sep", trimCooler.getOutletStream());
process.add(separator);

process.run();

// Total cooling duty
double airDuty = -airCooler.getDuty() / 1e6;  // MW
double trimDuty = -trimCooler.getDuty() / 1e6;  // MW
System.out.println("Air cooler duty: " + airDuty + " MW");
System.out.println("Trim cooler duty: " + trimDuty + " MW");

Heat Exchanger Design

LMTD Method

HeatExchanger hx = new HeatExchanger("E-400", hotIn, coldIn);
hx.setUAvalue(ua);
hx.run();

double LMTD = hx.getLMTD();
double duty = hx.getDuty();
double UA = duty / LMTD;
double area = UA / overallHTC;

Effectiveness-NTU

double NTU = hx.getNTU();
double effectiveness = hx.getEffectiveness();

Air Cooler

Air cooler unit operation

The AirCooler is a simple process unit for estimating the amount of cooling air needed when a process stream is cooled by ambient air. The calculation makes use of the humid air utility in NeqSim to evaluate the enthalpy rise of the air between the inlet and outlet temperature.

For a given inlet temperature, outlet temperature and relative humidity of the air, the mass flow of dry air is obtained from

[ \dot m_{air} = \frac{Q}{h_{out}-h_{in}} ]

where Q is the heat removed from the process stream in watt and h_{in} and h_{out} are the specific humid–air enthalpies in kJ per kg dry air. The volumetric flow is calculated from the ideal–gas relation at the inlet conditions.

Basic usage:

AirCooler cooler = new AirCooler("air cooler", stream);
cooler.setOutTemperature(40.0, "C");
cooler.setAirInletTemperature(20.0, "C");
cooler.setAirOutletTemperature(30.0, "C");
cooler.setRelativeHumidity(0.5);

Water Cooler

Water cooler

The WaterCooler equipment cools water streams using the dedicated water physical property package. It also estimates the required cooling water flow rate using the IAPWS IF97 steam tables. Both the inlet and outlet process streams are forced to use PhysicalPropertyModel.WATER.

SystemInterface water = new SystemSrkEos(298.15, 1.0);
water.addComponent("water", 1.0);
water.setPhysicalPropertyModel(PhysicalPropertyModel.WATER);

Stream feed = new Stream("water feed", water);
feed.setTemperature(40.0, "C");

WaterCooler cooler = new WaterCooler("cooler", feed);
cooler.setOutTemperature(20.0, "C");
cooler.setWaterInletTemperature(25.0, "C");
cooler.setWaterOutletTemperature(35.0, "C");
cooler.setWaterPressure(1.0, "bara");

// After running the process system the calculated cooling water flow can be obtained
double cwFlow = cooler.getCoolingWaterFlowRate("kg/hr");

Steam Heater

Steam heater

The SteamHeater heats process streams while forcing the water property package. It estimates the required steam flow rate using the IAPWS IF97 steam tables.

SystemInterface water = new SystemSrkEos(298.15, 1.0);
water.addComponent("water", 1.0);
water.setPhysicalPropertyModel(PhysicalPropertyModel.WATER);

Stream feed = new Stream("water feed", water);
feed.setTemperature(25.0, "C");

SteamHeater heater = new SteamHeater("heater", feed);
heater.setOutTemperature(80.0, "C");
heater.setSteamInletTemperature(180.0, "C");
heater.setSteamOutletTemperature(100.0, "C");
heater.setSteamPressure(2.0, "bara");

// After running the process system the calculated steam flow can be obtained
double steamFlow = heater.getSteamFlowRate("kg/hr");

Mechanical Design

Heat Exchanger Mechanical Design

Overview

The HeatExchangerMechanicalDesign class provides sizing estimates for shell-and-tube, plate-and-frame, air cooler, and double-pipe exchangers. It can be attached to a full two-stream HeatExchanger or to single-stream Heater and Cooler units that supply an auxiliary utility specification. The mechanical design routine evaluates the candidate exchanger types, computes the required UA and approach temperatures, and selects a preferred configuration based on area, weight, or pressure-drop criteria.

Prerequisites

Two-Stream Heat Exchangers

For a HeatExchanger, the design routine uses the duty, stream inlet/outlet temperatures, and (if provided) UA or thermal effectiveness to compute the log-mean temperature difference (LMTD) and overall heat-transfer requirements. The selected exchanger geometry is exposed through HeatExchangerSizingResult.

HeatExchanger exchanger = new HeatExchanger("hx", hotStream, coldStream);
exchanger.run();

exchanger.initMechanicalDesign();
HeatExchangerMechanicalDesign design = exchanger.getMechanicalDesign();
design.calcDesign();

HeatExchangerSizingResult result = design.getSelectedSizingResult();
System.out.println(result.getType() + " area = " + result.getRequiredArea());

Single-Stream Heaters and Coolers

A Heater or Cooler needs a UtilityStreamSpecification so the mechanical design can derive the utility-side temperatures or heat capacity rate.

Heater heater = new Heater("heater", feed);
heater.setOutTemperature(80.0, "C");

UtilityStreamSpecification utility = new UtilityStreamSpecification();
utility.setSupplyTemperature(180.0, "C");
utility.setReturnTemperature(160.0, "C");
utility.setOverallHeatTransferCoefficient(650.0);
heater.setUtilitySpecification(utility);

heater.run();
heater.initMechanicalDesign();
HeatExchangerMechanicalDesign design = heater.getMechanicalDesign();
design.calcDesign();

The specification supports setting:

You can also configure the utility through convenience setters such as setUtilitySupplyTemperature, setUtilityReturnTemperature, setUtilityHeatCapacityRate, and setUtilityApproachTemperature.

Inspecting Sizing Alternatives

All evaluated designs are available through getSizingResults(). Use the selection helpers to control the preferred configuration:

design.setCandidateTypes(
    HeatExchangerType.SHELL_AND_TUBE,
    HeatExchangerType.PLATE_AND_FRAME);

design.setSelectionCriterion(SelectionCriterion.MIN_WEIGHT);
design.calcDesign();

Chapter 17: Valves & Flow Control

Valves

Valve Equipment

Documentation for valve equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.valve

Classes:

Mechanical Design: neqsim.process.mechanicaldesign.valve


Throttling Valve

Basic Usage

import neqsim.process.equipment.valve.ThrottlingValve;

ThrottlingValve valve = new ThrottlingValve("FV-100", inletStream);
valve.setOutletPressure(30.0, "bara");
valve.run();

// Joule-Thomson cooling
double Tin = inletStream.getTemperature("C");
double Tout = valve.getOutletStream().getTemperature("C");
double deltaT = Tout - Tin;

System.out.println("Temperature change: " + deltaT + " °C");

Isenthalpic Expansion

Throttling is isenthalpic (constant enthalpy):

double H_in = inletStream.getEnthalpy("J/mol");
double H_out = valve.getOutletStream().getEnthalpy("J/mol");
// H_in ≈ H_out (within numerical precision)

Valve Sizing

Flow Coefficient (Cv)

// Set Cv
valve.setCv(150.0, "US");  // US gallons/min at 1 psi ΔP
// Or
valve.setCv(150.0, "SI");  // m³/hr at 1 bar ΔP

// Get Cv at current conditions
double Cv = valve.getCv("US");

Valve Opening

// Set valve position
valve.setPercentValveOpening(50.0);  // 50% open

// Cv varies with opening (inherent characteristic)
valve.setValveCharacteristic("linear");
// or
valve.setValveCharacteristic("equal_percentage");
// or
valve.setValveCharacteristic("quick_opening");

Calculate Pressure Drop

// Given Cv and flow, calculate ΔP
valve.setCv(100.0, "US");
valve.setPercentValveOpening(75.0);
valve.run();

double Pin = inletStream.getPressure("bara");
double Pout = valve.getOutletStream().getPressure("bara");
double deltaP = Pin - Pout;

Valve Characteristics

Control valves have inherent flow characteristics that define how Cv varies with valve opening.

Available Characteristics

Characteristic Description Best Application
Linear Flow proportional to opening Constant ΔP systems, bypass valves
Equal Percentage Equal opening increments = equal % flow change Variable ΔP, most process control
Quick Opening Large flow change at small openings On/off service, safety applications
Modified Parabolic Compromise between linear and equal % General purpose

Setting Valve Characteristic

import neqsim.process.mechanicaldesign.valve.ValveMechanicalDesign;

ValveMechanicalDesign mechDesign = (ValveMechanicalDesign) valve.getMechanicalDesign();
mechDesign.setValveCharacterization("equal percentage");

Characteristic Curves

         │
   100%  │                    ●─── Quick Opening
         │               ●───●
   Flow  │          ●───●    ●── Linear
   (Cv)  │     ●───●        ●
         │ ●───●        ●───── Equal Percentage
         │●        ●───●
    0%   └────────────────────
         0%    Opening    100%

Sizing Standards

NeqSim supports multiple valve sizing standards for different applications.

Available Standards

Standard Code Description
Default default Simplified IEC 60534 for gas, standard for liquid
IEC 60534 IEC 60534 Full IEC 60534-2-1 implementation
Extended IEC 60534 full IEC 60534 with all correction factors
Prod Choke prod choke Production choke with discharge coefficient

Setting Sizing Standard

ValveMechanicalDesign mechDesign = (ValveMechanicalDesign) valve.getMechanicalDesign();
mechDesign.setValveSizingStandard("IEC 60534");

IEC 60534 Gas Sizing

For compressible fluids (gas/vapor):

$$K_v = \frac{Q}{N_9 \cdot P_1 \cdot Y} \sqrt{\frac{M \cdot T \cdot Z}{x}}$$

Where:

Choked Flow Detection

Flow becomes choked when: $$x \geq F_\gamma \cdot x_T$$

Where:


Mechanical Design

Complete mechanical design calculations are available for valve body sizing, weight estimation, and actuator requirements.

Accessing Mechanical Design

ValveMechanicalDesign mechDesign = (ValveMechanicalDesign) valve.getMechanicalDesign();
mechDesign.calcDesign();

Design Results

Property Method Unit
ANSI Pressure Class getAnsiPressureClass() -
Nominal Size getNominalSizeInches() inches
Face-to-Face getFaceToFace() mm
Body Wall Thickness getBodyWallThickness() mm
Design Pressure getDesignPressure() bara
Design Temperature getDesignTemperature() °C
Actuator Thrust getRequiredActuatorThrust() N
Actuator Weight getActuatorWeight() kg
Total Weight getWeightTotal() kg

Example: Full Mechanical Design

// Create and run valve
ThrottlingValve valve = new ThrottlingValve("PCV-101", gasStream);
valve.setOutletPressure(60.0, "bara");
valve.run();

// Calculate mechanical design
ValveMechanicalDesign mechDesign = (ValveMechanicalDesign) valve.getMechanicalDesign();
mechDesign.calcDesign();

// Print results
System.out.println("=== VALVE MECHANICAL DESIGN ===");
System.out.println("Cv: " + valve.getCv());
System.out.println("ANSI Class: " + mechDesign.getAnsiPressureClass());
System.out.println("Size: " + mechDesign.getNominalSizeInches() + " inches");
System.out.println("Face-to-Face: " + mechDesign.getFaceToFace() + " mm");
System.out.println("Wall Thickness: " + mechDesign.getBodyWallThickness() + " mm");
System.out.println("Total Weight: " + mechDesign.getWeightTotal() + " kg");
System.out.println("Actuator Thrust: " + mechDesign.getRequiredActuatorThrust() + " N");

Detailed Documentation

See Valve Mechanical Design for complete documentation including:


Critical Flow

For high pressure drops, flow becomes critical (choked).

// Check if flow is critical
boolean isCritical = valve.isCriticalFlow();

// Critical flow factor
double Cf = valve.getCriticalFlowFactor();

Safety Valve (PSV)

Basic Setup

import neqsim.process.equipment.valve.SafetyValve;

SafetyValve psv = new SafetyValve("PSV-100", vessel);
psv.setSetPressure(100.0, "barg");  // Set pressure
psv.setBlowdownPressure(10.0, "%"); // 10% blowdown
psv.run();

// Check if valve is open
boolean isOpen = psv.isOpen();
double relievingFlow = psv.getRelievingFlow("kg/hr");

Sizing

// Required relieving capacity
psv.setRequiredCapacity(10000.0, "kg/hr");

// Get required orifice area
double area = psv.getRequiredOrificeArea("cm2");

// Select API orifice
String orifice = psv.selectAPIorifice();  // e.g., "J", "K", "L"

Choke Valve

For wellhead and production applications:

import neqsim.process.equipment.valve.ChokeValve;

ChokeValve choke = new ChokeValve("Wellhead Choke", wellStream);
choke.setOutletPressure(50.0, "bara");
choke.run();

// Or specify bean size
choke.setBeanSize(32, "64ths");  // 32/64" = 0.5"
choke.run();

double Pout = choke.getOutletStream().getPressure("bara");

Dynamic Simulation

// Valve dynamics
valve.setCalculateSteadyState(false);

// Valve stroke time
valve.setStrokeTime(10.0);  // seconds for 0-100%

// Step change
valve.setPercentValveOpening(80.0);

for (double t = 0; t < 30; t += 0.1) {
    valve.runTransient();
    double opening = valve.getActualOpening();  // Lags setpoint
}

Example: Pressure Letdown Station

ProcessSystem process = new ProcessSystem();

// HP gas inlet
Stream hpGas = new Stream("HP Gas", gasFluid);
hpGas.setFlowRate(50000.0, "kg/hr");
hpGas.setTemperature(50.0, "C");
hpGas.setPressure(100.0, "bara");
process.add(hpGas);

// Stage 1: 100 -> 50 bar
ThrottlingValve pv1 = new ThrottlingValve("PV-100", hpGas);
pv1.setOutletPressure(50.0, "bara");
pv1.setCv(200.0, "US");
process.add(pv1);

// Heater (compensate JT cooling)
Heater heater = new Heater("E-100", pv1.getOutletStream());
heater.setOutTemperature(40.0, "C");
process.add(heater);

// Stage 2: 50 -> 10 bar
ThrottlingValve pv2 = new ThrottlingValve("PV-101", heater.getOutletStream());
pv2.setOutletPressure(10.0, "bara");
pv2.setCv(300.0, "US");
process.add(pv2);

process.run();

// JT effects
System.out.println("After PV-100: " + pv1.getOutletStream().getTemperature("C") + " °C");
System.out.println("After E-100: " + heater.getOutletStream().getTemperature("C") + " °C");
System.out.println("After PV-101: " + pv2.getOutletStream().getTemperature("C") + " °C");

Joule-Thomson Coefficient

For ideal gases, $\mu_{JT} = 0$. For real gases:

$$\mu_{JT} = \left(\frac{\partial T}{\partial P}\right)_H = \frac{1}{C_p}\left[T\left(\frac{\partial V}{\partial T}\right)_P - V\right]$$

// Get JT coefficient
double muJT = inletStream.getJouleThomsonCoefficient();  // K/bar

Valve Design

Valve Mechanical Design

This document describes the mechanical design calculations for control valves in NeqSim, implemented in the ValveMechanicalDesign class.

Overview

The valve mechanical design module provides sizing and design calculations for control valves based on IEC 60534, ANSI/ISA-75, and ASME B16.34 standards. The calculations enable:

Design Standards Reference

Standard Description
IEC 60534 Industrial-process control valves
ANSI/ISA-75.01 Flow Equations for Sizing Control Valves
ANSI/ISA-75.08 Face-to-Face Dimensions for Flanged Globe-Style Control Valve Bodies
ASME B16.34 Valves - Flanged, Threaded, and Welding End
API 6D Pipeline and Piping Valves

Design Calculations

1. ANSI Pressure Class Selection

The pressure class is automatically selected based on the design pressure:

Design Pressure ANSI Class
≤ 19.6 bara Class 150
≤ 51.1 bara Class 300
≤ 102.1 bara Class 600
≤ 153.2 bara Class 900
≤ 255.3 bara Class 1500
≤ 425.5 bara Class 2500
// Design pressure with 10% margin
designPressure = operatingPressure × 1.10

2. Nominal Valve Size

The nominal valve size is calculated from the Cv coefficient using the ISA correlation for globe valves:

Cv ≈ 10 × d²

Where d is the nominal pipe size in inches. Rearranging:

d = sqrt(Cv / 10)

The calculated size is then rounded to the nearest standard pipe size:

3. Face-to-Face Dimensions

Face-to-face dimensions are per ANSI/ISA-75.08 for globe-style control valves:

Nominal Size (in) Face-to-Face (mm)
≤ 1.0 108
1.5 117
2.0 152
3.0 203
4.0 241
6.0 292
8.0 356
10.0 432
12.0 495
> 12.0 508 + (size - 12) × 30

Adjustment for Pressure Class:

4. Body Wall Thickness

Wall thickness is calculated using the ASME B16.34 pressure vessel formula:

t = (P × R) / (S × E - 0.6 × P) + CA

Where:

Minimum: 3.0 mm wall thickness

5. Actuator Sizing

The required actuator thrust is calculated from:

  1. Fluid Force: Force to overcome pressure across the seat

    F_fluid = P_design × A_seat
    
  2. Packing Friction: Typically 15% of fluid force

    F_packing = 0.15 × F_fluid
    
  3. Seat Load: For tight shutoff (Class IV/V)

    F_seat = π × d_seat × 7 N/mm
    
  4. Total Thrust:

    F_total = (F_fluid + F_packing + F_seat) × 1.25
    

6. Weight Estimation

Valve weight is estimated using empirical correlations:

Body Weight

W_body = 2.5 × (size_inches)^2.5 × (class / 150)^0.5

Trim and Bonnet

W_trim = 0.3 × W_body

Actuator Weight

W_actuator = 0.015 × F_thrust + 5.0 kg (minimum 10 kg)

Total Weight

W_total = W_body + W_trim + W_actuator

Usage Example

import neqsim.process.equipment.valve.ThrottlingValve;
import neqsim.process.mechanicaldesign.valve.ValveMechanicalDesign;

// Create and run valve
ThrottlingValve valve = new ThrottlingValve("PCV-101", inletStream);
valve.setOutletPressure(60.0, "bara");
valve.run();

// Get mechanical design
ValveMechanicalDesign mechDesign = (ValveMechanicalDesign) valve.getMechanicalDesign();
mechDesign.calcDesign();

// Access results
System.out.println("ANSI Class: " + mechDesign.getAnsiPressureClass());
System.out.println("Nominal Size: " + mechDesign.getNominalSizeInches() + " inches");
System.out.println("Face-to-Face: " + mechDesign.getFaceToFace() + " mm");
System.out.println("Body Wall: " + mechDesign.getBodyWallThickness() + " mm");
System.out.println("Actuator Thrust: " + mechDesign.getRequiredActuatorThrust() + " N");
System.out.println("Total Weight: " + mechDesign.getWeightTotal() + " kg");

API Reference

ValveMechanicalDesign Class

Method Return Description
calcDesign() void Performs all mechanical design calculations
getAnsiPressureClass() int Returns ANSI class (150, 300, 600, 900, 1500, 2500)
getNominalSizeInches() double Returns nominal valve size in inches
getFaceToFace() double Returns face-to-face dimension in mm
getBodyWallThickness() double Returns body wall thickness in mm
getRequiredActuatorThrust() double Returns required actuator thrust in N
getActuatorWeight() double Returns estimated actuator weight in kg
getDesignPressure() double Returns design pressure in bara
getDesignTemperature() double Returns design temperature in °C
getWeightTotal() double Returns total valve weight in kg

Valve Sizing Standards

NeqSim supports multiple valve sizing standards that can be selected via setValveSizingStandard():

Standard Description Best For
default IEC 60534-based calculation General control valves
IEC 60534 Full IEC 60534-2-1 implementation Engineering calculations
IEC 60534 full Extended IEC 60534 with all factors Detailed sizing studies
prod choke Production choke sizing with Cd Wellhead chokes

Example: Setting Sizing Standard

ValveMechanicalDesign mechDesign = (ValveMechanicalDesign) valve.getMechanicalDesign();
mechDesign.setValveSizingStandard("IEC 60534");

Valve Characteristics

Available valve characteristics for flow control:

Characteristic Formula Application
Linear Cv/Cv₁₀₀ = opening/100 Constant ΔP systems
Equal Percentage Cv/Cv₁₀₀ = R^((opening/100)-1) Variable ΔP, process control
Quick Opening Cv/Cv₁₀₀ = sqrt(opening/100) On/off, safety applications
Modified Parabolic Cv/Cv₁₀₀ = opening²/10000 Compromise between linear/EQ%

Example: Setting Valve Characteristic

ValveMechanicalDesign mechDesign = (ValveMechanicalDesign) valve.getMechanicalDesign();
mechDesign.setValveCharacterization("equal percentage");

IEC 60534 Gas Sizing Formula

For compressible fluids, the Kv (or Cv) is calculated using:

$$K_v = \frac{Q}{N_9 \cdot P_1 \cdot Y} \sqrt{\frac{M \cdot T \cdot Z}{x}}$$

Where:

Choked Flow

When $x \geq F_\gamma \cdot x_T$, flow becomes choked and:

$$Y = \frac{2}{3}$$ $$x_{effective} = F_\gamma \cdot x_T$$

Flow Meters

Flow meter models

This page documents the equations implemented in the Orifice equipment for computing flow through differential pressure meters. All variables are in SI units.

Orifice plate

The discharge coefficient $C$ is calculated with the Reader–Harris/Gallagher correlation as implemented in ISO 5167:

$$ C = 0.5961 + 0.0261\beta^2 - 0.216\beta^8 + 0.000521\left(\frac{10^6\beta}{Re_D}\right)^{0.7} +(0.0188 + 0.0063A)\beta^{3.5}\left(\frac{10^6}{Re_D}\right)^{0.3} +(0.043 + 0.080e^{-10L_1}-0.123e^{-7L_1})(1-0.11A)\frac{\beta^4}{1-\beta^4} -0.031(M_2' -0.8M_2'^{1.1})\beta^{1.3} $$

The expansibility factor is $$ \epsilon = 1 - (0.351 +0.256\beta^4 +0.93\beta^8)\left[1-\left(\frac{P_2}{P_1}\right)^{1/\kappa}\right] $$

The mass flow rate is obtained iteratively from $$ m = \left(\tfrac{\pi D^2\beta^2}{4}\right) C \epsilon \frac{\sqrt{2\rho(P_1-P_2)}}{\sqrt{1-\beta^4}}. $$

Venturi

Venturi Flow Calculation in NeqSim

This document describes the Venturi flow meter calculation methods implemented in NeqSim for computing mass flow rates from differential pressure measurements, and vice versa.

Overview

NeqSim implements Venturi flow calculations primarily in the DifferentialPressureFlowCalculator class, which is a utility for calculating mass flow rates from various differential pressure devices using NeqSim thermodynamic properties. The calculator supports both:

  1. Flow from dP: Calculate mass flow rate given differential pressure
  2. dP from Flow: Calculate differential pressure given mass flow rate (inverse calculation)

Location: DifferentialPressureFlowCalculator.java

Supported Flow Meter Types

The calculator supports multiple differential pressure device types:

Flow Type Default Discharge Coefficient
Venturi 0.985
Orifice Calculated (Reader-Harris/Gallagher)
V-Cone 0.82
Nozzle Calculated
DallTube Calculated
Annubar Calculated
Simplified User-provided Cv
Perrys-Orifice Subsonic: 0.62, Sonic: 0.75-0.84

Venturi Calculation Method

Fundamental Equation

The Venturi flow calculation uses the compressible flow equation with an expansibility (expansion) factor:

$$ \dot{m} = \frac{C}{\sqrt{1 - \beta^4}} \cdot \varepsilon \cdot \frac{\pi d^2}{4} \cdot \sqrt{2 \rho \Delta P} $$

Where:

Expansibility Factor (ε)

The expansibility factor accounts for compressibility effects in gas flow and is calculated using an isentropic expansion model:

$$ \varepsilon = \sqrt{\frac{\kappa \cdot \tau^{2/\kappa}}{\kappa - 1} \cdot \frac{1 - \beta^4}{1 - \beta^4 \tau^{2/\kappa}} \cdot \frac{1 - \tau^{(\kappa-1)/\kappa}}{1 - \tau}} $$

Where:

Implementation in NeqSim

private static double[] calcVenturi(double[] dp, double[] p, double[] rho, double[] kappa,
    double D, double d, double C) {
  double beta = d / D;
  double beta4 = Math.pow(beta, 4.0);
  double betaTerm = Math.sqrt(Math.max(1.0 - beta4, 1e-30));
  double[] massFlow = new double[dp.length];

  for (int i = 0; i < dp.length; i++) {
    double tau = p[i] / (p[i] + dp[i]);
    double k = kappa[i];
    double tau2k = Math.pow(tau, 2.0 / k);

    // Expansibility factor calculation
    double numerator = k * tau2k / (k - 1.0) * (1.0 - beta4)
        / (1.0 - beta4 * tau2k) * (1.0 - Math.pow(tau, (k - 1.0) / k)) / (1.0 - tau);
    double eps = Math.sqrt(Math.max(numerator, 0.0));

    // Mass flow calculation
    double rootTerm = Math.sqrt(Math.max(dp[i] * rho[i] * 2.0, 0.0));
    double value = C / betaTerm * eps * Math.PI / 4.0 * d * d * rootTerm;
    massFlow[i] = tau == 1.0 ? 0.0 : value * 3600.0;  // Convert to kg/h
  }
  return massFlow;
}

Inverse Calculation: Differential Pressure from Flow

Fundamental Equation

To calculate the differential pressure from a known mass flow rate, we rearrange the Venturi equation:

$$ \Delta P = \frac{1}{2\rho} \left( \frac{\dot{m} \cdot \sqrt{1 - \beta^4}}{C \cdot \varepsilon \cdot A} \right)^2 $$

Where:

Since the expansibility factor $\varepsilon$ depends on the differential pressure (through the pressure ratio $\tau$), an iterative solution is required.

Algorithm

  1. Initial estimate (assuming incompressible flow, $\varepsilon = 1$): $$ \Delta P_0 = \frac{1}{2\rho} \left( \frac{\dot{m} \cdot \sqrt{1 - \beta^4}}{C \cdot A} \right)^2 $$

  2. Iterate until convergence:

    • Calculate pressure ratio: $\tau = \frac{P_1}{P_1 + \Delta P}$
    • Calculate expansibility factor $\varepsilon$ from $\tau$ and $\kappa$
    • Update: $\Delta P_{n+1} = \frac{1}{2\rho} \left( \frac{\dot{m} \cdot \sqrt{1 - \beta^4}}{C \cdot \varepsilon \cdot A} \right)^2$
    • Check convergence: $|\Delta P_{n+1} - \Delta P_n| < 0.01$ Pa

Implementation in NeqSim

public static double calculateDpFromFlowVenturi(double massFlowKgPerHour, double pressureBara,
    double density, double kappa, double pipeDiameterMm, double throatDiameterMm,
    double dischargeCoefficient) {

  double D = pipeDiameterMm / 1000.0;
  double d = throatDiameterMm / 1000.0;
  double C = dischargeCoefficient;
  double massFlowKgPerSec = massFlowKgPerHour / 3600.0;

  double beta = d / D;
  double beta4 = Math.pow(beta, 4.0);
  double betaTerm = Math.sqrt(Math.max(1.0 - beta4, 1e-30));

  // Initial estimate (incompressible)
  double A = Math.PI / 4.0 * d * d;
  double dpInitial = Math.pow(massFlowKgPerSec * betaTerm / (C * A), 2) / (2.0 * density);

  // Iterate to account for expansibility factor
  double dpPa = dpInitial;
  double pPa = pressureBara * 1.0e5;

  for (int iter = 0; iter < 100; iter++) {
    double tau = pPa / (pPa + dpPa);
    double tau2k = Math.pow(tau, 2.0 / kappa);
    double numerator = kappa * tau2k / (kappa - 1.0) * (1.0 - beta4)
        / (1.0 - beta4 * tau2k) * (1.0 - Math.pow(tau, (kappa - 1.0) / kappa)) / (1.0 - tau);
    double eps = Math.sqrt(Math.max(numerator, 1e-30));

    double dpNew = Math.pow(massFlowKgPerSec * betaTerm / (C * eps * A), 2) / (2.0 * density);

    if (Math.abs(dpNew - dpPa) < 0.01) {
      dpPa = dpNew;
      break;
    }
    dpPa = dpNew;
  }

  return dpPa / 100.0;  // Convert Pa to mbar
}

Input Parameters

The calculator requires the following inputs:

Geometry Parameters (flowData array)

Index Parameter Unit
0 Pipe diameter (D) mm
1 Throat diameter (d) mm
2 Discharge coefficient (optional) -

Operating Conditions

Parameter Unit
Pressure barg
Temperature °C
Differential Pressure mbar

Fluid Composition

Thermodynamic Properties

NeqSim uses the SRK (Soave-Redlich-Kwong) equation of state to calculate the required thermodynamic properties:

  1. Density (ρ) - Calculated at actual flowing conditions
  2. Viscosity (μ) - Used for Reynolds number calculations in orifice/nozzle
  3. Isentropic exponent (κ) - Cp/Cv ratio, calculated at low pressure conditions
  4. Molecular weight - For standard flow conversions

Output Results

The FlowCalculationResult class provides:

Output Unit
Mass flow rate kg/h
Volumetric flow rate (actual) m³/h
Standard volumetric flow MSm³/day
Molecular weight g/mol

Usage Example

import neqsim.process.equipment.diffpressure.DifferentialPressureFlowCalculator;
import neqsim.process.equipment.diffpressure.DifferentialPressureFlowCalculator.FlowCalculationResult;
import java.util.Arrays;
import java.util.List;

// Operating conditions
double[] pressureBarg = {50.0};        // 50 barg
double[] temperatureC = {25.0};        // 25°C
double[] dpMbar = {200.0};             // 200 mbar differential pressure

// Venturi geometry: D=300mm, d=200mm, Cd=0.985
double[] flowData = {300.0, 200.0, 0.985};

// Gas composition
List<String> components = Arrays.asList("methane", "ethane", "propane");
double[] fractions = {0.85, 0.10, 0.05};

// Calculate flow
FlowCalculationResult result = DifferentialPressureFlowCalculator.calculate(
    pressureBarg, temperatureC, dpMbar, "Venturi", flowData,
    components, fractions, true);

double massFlowKgH = result.getMassFlowKgPerHour()[0];
double stdFlowMSm3Day = result.getStandardFlowMSm3PerDay()[0];

Example 2: Calculate Differential Pressure from Flow (Inverse)

import neqsim.process.equipment.diffpressure.DifferentialPressureFlowCalculator;
import java.util.Arrays;
import java.util.List;

// Known mass flow rate
double massFlowKgPerHour = 50000.0;  // 50,000 kg/h

// Operating conditions
double pressureBarg = 50.0;          // 50 barg
double temperatureC = 25.0;          // 25°C

// Venturi geometry: D=300mm, d=200mm, Cd=0.985
double[] flowData = {300.0, 200.0, 0.985};

// Gas composition
List<String> components = Arrays.asList("methane", "ethane", "propane");
double[] fractions = {0.85, 0.10, 0.05};

// Calculate differential pressure
double dpMbar = DifferentialPressureFlowCalculator.calculateDpFromFlow(
    massFlowKgPerHour, pressureBarg, temperatureC, "Venturi", flowData,
    components, fractions, true);

System.out.println("Differential pressure: " + dpMbar + " mbar");

Example 3: Direct Calculation with Known Fluid Properties

// If you already have fluid properties calculated
double massFlowKgPerHour = 50000.0;
double pressureBara = 51.0125;       // bara
double density = 42.5;               // kg/m³
double kappa = 1.28;                 // isentropic exponent
double pipeDiameterMm = 300.0;       // mm
double throatDiameterMm = 200.0;     // mm
double Cd = 0.985;                   // discharge coefficient

double dpMbar = DifferentialPressureFlowCalculator.calculateDpFromFlowVenturi(
    massFlowKgPerHour, pressureBara, density, kappa, 
    pipeDiameterMm, throatDiameterMm, Cd);

System.out.println("Differential pressure: " + dpMbar + " mbar");

Comparison with Other Flow Meter Types

Orifice Plate

Uses the Reader-Harris/Gallagher correlation (ISO 5167) for discharge coefficient with iterative solution:

$$ C = 0.5961 + 0.0261\beta^2 - 0.216\beta^8 + 0.000521\left(\frac{10^6\beta}{Re_D}\right)^{0.7} + \ldots $$

Expansibility factor: $$ \varepsilon = 1 - (0.351 + 0.256\beta^4 + 0.93\beta^8)\left[1 - \left(\frac{P_2}{P_1}\right)^{1/\kappa}\right] $$

ISA 1932 Nozzle

Uses a similar approach to Venturi but with a different discharge coefficient correlation: $$ C = 0.99 - 0.2262\beta^{4.1} - (0.00175\beta^2 - 0.0033\beta^{4.15})\left(\frac{10^7}{Re_D}\right)^{1.15} $$

V-Cone

Uses a modified beta ratio based on cone geometry: $$ \beta_{V-Cone} = \sqrt{1 - \frac{d_{cone}^2}{D^2}} $$

Expansibility factor: $$ \varepsilon = 1 - (0.649 + 0.696\beta^4)\frac{\Delta P}{\kappa \cdot P} $$

Standards and References

The implementations are based on:

Key Considerations

  1. Compressibility: The expansibility factor is critical for gas flow; for liquids, ε ≈ 1.0
  2. Beta Ratio Limits: Typically valid for 0.2 ≤ β ≤ 0.75
  3. Reynolds Number: Correlations are valid for Re > 4000 (turbulent flow)
  4. Pressure Recovery: Venturi meters have better pressure recovery (~90%) compared to orifice plates (~40%)
  5. Accuracy: Typical uncertainty is ±0.5% to ±1.5% depending on installation and calibration

Tanks

Storage Tanks

Documentation for liquid storage tanks in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.tank

Classes:

Class Description
Tank Basic storage tank
LNGTank LNG storage tank with boil-off

Tank Class

Basic Usage

import neqsim.process.equipment.tank.Tank;

Tank tank = new Tank("T-100", liquidStream);
tank.setVolume(1000.0, "m3");
tank.setLiquidLevel(0.5);  // 50% full
tank.run();

Operating Pressure

// Atmospheric tank
tank.setPressure(1.013, "bara");

// Pressurized storage
tank.setPressure(5.0, "bara");

Dynamic Operation

Fill and Drain

// Initial conditions
tank.setLiquidLevel(0.3);  // 30% full
tank.run();

// Simulate filling
for (double t = 0; t < 3600; t += 60) {
    tank.setInletStream(inletStream);
    tank.runTransient();

    double level = tank.getLiquidLevel();
    System.out.println("Time: " + t + " s, Level: " + level * 100 + " %");
}

Level Calculation

$$\frac{dV_{liq}}{dt} = \dot{Q}_{in} - \dot{Q}_{out}$$

$$L = \frac{V_{liq}}{V_{tank}}$$


Boil-off Gas

For cryogenic storage (LNG, LPG).

LNG Tank

import neqsim.process.equipment.tank.LNGTank;

LNGTank lngTank = new LNGTank("LNG Storage", lngStream);
lngTank.setVolume(160000.0, "m3");
lngTank.setHeatInput(500.0, "kW");  // Heat leak
lngTank.run();

// Boil-off rate
double bogRate = lngTank.getBoilOffGasRate("kg/hr");
Stream bog = lngTank.getBoilOffGasStream();

Boil-off Calculation

$$\dot{m}_{BOG} = \frac{\dot{Q}_{heat}}{\Delta H_{vap}}$$

Where:


Examples

Example 1: Simple Storage Tank

import neqsim.thermo.system.SystemSrkEos;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.tank.Tank;

// Crude oil
SystemSrkEos oil = new SystemSrkEos(298.15, 1.013);
oil.addComponent("n-heptane", 0.50);
oil.addComponent("n-decane", 0.50);
oil.setMixingRule("classic");

Stream oilStream = new Stream("Crude", oil);
oilStream.setFlowRate(100.0, "m3/hr");
oilStream.run();

// Storage tank
Tank storage = new Tank("Crude Storage", oilStream);
storage.setVolume(10000.0, "m3");
storage.setLiquidLevel(0.6);
storage.run();

double inventory = storage.getLiquidVolume("m3");
System.out.println("Inventory: " + inventory + " m³");

Example 2: LNG Storage with Boil-off

// LNG composition
SystemSrkEos lng = new SystemSrkEos(112.0, 1.013);  // -161°C
lng.addComponent("nitrogen", 0.01);
lng.addComponent("methane", 0.92);
lng.addComponent("ethane", 0.05);
lng.addComponent("propane", 0.02);
lng.setMixingRule("classic");

Stream lngIn = new Stream("LNG In", lng);
lngIn.setFlowRate(1000.0, "m3/hr");
lngIn.run();

// LNG tank
LNGTank tank = new LNGTank("LNG Tank", lngIn);
tank.setVolume(160000.0, "m3");  // 160,000 m³ tank
tank.setHeatInput(300.0, "kW");  // Heat leak
tank.run();

// Results
System.out.println("BOG rate: " + tank.getBoilOffGasRate("kg/hr") + " kg/hr");
System.out.println("BOG temp: " + tank.getBoilOffGasStream().getTemperature("C") + " °C");
System.out.println("BOG rate %: " + tank.getBoilOffRate() * 100 + " %/day");

Example 3: Tank with Level Control

ProcessSystem process = new ProcessSystem();

// Feed stream
Stream feed = new Stream("Feed", oilFluid);
feed.setFlowRate(100.0, "m3/hr");
process.add(feed);

// Storage tank
Tank tank = new Tank("T-100", feed);
tank.setVolume(5000.0, "m3");
tank.setLiquidLevel(0.5);
process.add(tank);

// Outlet with level control
ThrottlingValve outlet = new ThrottlingValve("LV-100", tank.getOutletStream());
outlet.setOutletPressure(1.0, "bara");
process.add(outlet);

// Level controller
PIDController lc = new PIDController("LC-100");
lc.setMeasuredVariable(tank, "liquidLevel");
lc.setControlledVariable(outlet, "opening");
lc.setSetPoint(0.5);
lc.setKp(5.0);
lc.setKi(0.1);
process.add(lc);

// Run transient
for (double t = 0; t < 7200; t += 60) {
    // Disturb inlet at t=1800
    if (Math.abs(t - 1800) < 30) {
        feed.setFlowRate(150.0, "m3/hr");
    }

    process.runTransient();
    System.out.printf("%.0f, %.3f, %.1f%n", 
        t, tank.getLiquidLevel(), outlet.getOpening() * 100);
}

Chapter 18: Special Equipment

Reactors

Reactors

Documentation for chemical reactor equipment in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.reactor

Classes:

Class Description
Reactor Base reactor class
CSTRReactor Continuous stirred tank reactor
PFRReactor Plug flow reactor
EquilibriumReactor Chemical equilibrium reactor
GibbsReactor Gibbs energy minimization reactor

Reactor Types

Selection Guide

Reactor When to Use
CSTR Liquid-phase reactions, good mixing
PFR Gas-phase reactions, no back-mixing
Equilibrium Fast reactions at equilibrium
Gibbs Complex equilibrium without specifying reactions

CSTR

Continuous Stirred Tank Reactor with perfect mixing.

Basic Usage

import neqsim.process.equipment.reactor.CSTRReactor;

CSTRReactor cstr = new CSTRReactor("R-100", feedStream);
cstr.setVolume(10.0, "m3");
cstr.setTemperature(400.0, "K");
cstr.run();

With Reaction

// Define reaction: A + B → C
cstr.addReaction("component_A", -1);  // reactant
cstr.addReaction("component_B", -1);  // reactant
cstr.addReaction("component_C", 1);   // product

// Reaction rate constant
cstr.setRateConstant(0.1, "1/s");

cstr.run();

Residence Time

$$\tau = \frac{V}{\dot{Q}}$$

Where:


PFR

Plug Flow Reactor with no back-mixing.

Basic Usage

import neqsim.process.equipment.reactor.PFRReactor;

PFRReactor pfr = new PFRReactor("R-100", feedStream);
pfr.setLength(10.0, "m");
pfr.setDiameter(0.5, "m");
pfr.run();

With Kinetics

// Set reaction kinetics
pfr.setReaction(reaction);
pfr.setNumberOfReactorSegments(100);
pfr.run();

Equilibrium Reactor

For reactions at chemical equilibrium.

Basic Usage

import neqsim.process.equipment.reactor.EquilibriumReactor;

EquilibriumReactor eqReactor = new EquilibriumReactor("R-100", feedStream);
eqReactor.setTemperature(500.0, "K");
eqReactor.setPressure(10.0, "bara");
eqReactor.run();

Reaction Definition

// Water-gas shift: CO + H2O ⇌ CO2 + H2
eqReactor.addReaction("CO", -1);
eqReactor.addReaction("H2O", -1);
eqReactor.addReaction("CO2", 1);
eqReactor.addReaction("H2", 1);

// Equilibrium constant
eqReactor.setEquilibriumConstant(Keq);

Gibbs Reactor

Minimize Gibbs free energy to find equilibrium composition.

Basic Usage

import neqsim.process.equipment.reactor.GibbsReactor;

GibbsReactor gibbs = new GibbsReactor("R-100", feedStream);
gibbs.setTemperature(1000.0, "K");
gibbs.setPressure(10.0, "bara");
gibbs.run();

// Get equilibrium composition
Stream outlet = gibbs.getOutletStream();

Constrained Minimization

// Specify which elements to balance
gibbs.setElementBalanceCheck(true);

// Specify inert components
gibbs.setInertComponent("N2", true);

Examples

Example 1: Simple CSTR

import neqsim.thermo.system.SystemSrkEos;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.reactor.CSTRReactor;

// Feed with reactants
SystemSrkEos feed = new SystemSrkEos(350.0, 5.0);
feed.addComponent("methanol", 0.5);
feed.addComponent("water", 0.5);
feed.setMixingRule("classic");

Stream feedStream = new Stream("Feed", feed);
feedStream.setFlowRate(1000.0, "kg/hr");
feedStream.run();

// Reactor
CSTRReactor reactor = new CSTRReactor("R-100", feedStream);
reactor.setVolume(5.0, "m3");
reactor.run();

double residenceTime = reactor.getResidenceTime("min");
System.out.println("Residence time: " + residenceTime + " min");

Example 2: Steam Methane Reforming (Gibbs)

// SMR: CH4 + H2O ⇌ CO + 3H2
SystemSrkEos feed = new SystemSrkEos(700.0, 20.0);
feed.addComponent("methane", 1.0);
feed.addComponent("water", 3.0);  // Steam to carbon ratio = 3
feed.setMixingRule("classic");

// Add possible products
feed.addComponent("CO", 0.0);
feed.addComponent("CO2", 0.0);
feed.addComponent("hydrogen", 0.0);

Stream feedStream = new Stream("SMR Feed", feed);
feedStream.setFlowRate(100.0, "kmol/hr");
feedStream.run();

// Gibbs reactor for equilibrium
GibbsReactor smr = new GibbsReactor("SMR Reactor", feedStream);
smr.setTemperature(1100.0, "K");
smr.setPressure(20.0, "bara");
smr.run();

// Results
Stream product = smr.getOutletStream();
System.out.println("H2 mole fraction: " + product.getFluid().getMoleFraction("hydrogen"));
System.out.println("CO mole fraction: " + product.getFluid().getMoleFraction("CO"));
System.out.println("CH4 conversion: " + 
    (1 - product.getFluid().getMoleFraction("methane") / 
     feedStream.getFluid().getMoleFraction("methane")) * 100 + " %");

Example 3: Ammonia Synthesis

// N2 + 3H2 ⇌ 2NH3
SystemSrkEos synthGas = new SystemSrkEos(700.0, 200.0);
synthGas.addComponent("nitrogen", 1.0);
synthGas.addComponent("hydrogen", 3.0);
synthGas.addComponent("ammonia", 0.0);
synthGas.setMixingRule("classic");

Stream feed = new Stream("Syngas", synthGas);
feed.setFlowRate(1000.0, "kmol/hr");
feed.run();

EquilibriumReactor ammoniaReactor = new EquilibriumReactor("Ammonia Reactor", feed);
ammoniaReactor.setTemperature(700.0, "K");
ammoniaReactor.setPressure(200.0, "bara");
ammoniaReactor.run();

double nh3Prod = ammoniaReactor.getOutletStream().getFluid().getMoleFraction("ammonia");
System.out.println("Ammonia mole fraction: " + nh3Prod);

Gibbs Reactor

Gibbs Reactor

The Gibbs Reactor is a chemical equilibrium reactor that computes outlet compositions by minimizing the total Gibbs free energy of the system. It is used for modeling chemical reactions at thermodynamic equilibrium.

Overview

The GibbsReactor class performs chemical equilibrium calculations using Gibbs free energy minimization with Lagrange multipliers. The reactor automatically determines the equilibrium composition based on:

Key Features

Mathematical Background

The reactor minimizes the objective function:

$$G = \sum_i n_i \left( \mu_i^0 + RT \ln(\phi_i y_i P) \right) - \sum_j \lambda_j \left( \sum_i a_{ij} n_i - b_j \right)$$

Where:

The Newton-Raphson method iteratively solves for compositions and Lagrange multipliers until convergence.

Basic Usage

import neqsim.process.equipment.reactor.GibbsReactor;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create inlet stream
SystemSrkEos system = new SystemSrkEos(298.15, 10.0);
system.addComponent("methane", 1.0, "mol/sec");
system.addComponent("oxygen", 2.0, "mol/sec");
system.addComponent("CO2", 0.0, "mol/sec");
system.addComponent("water", 0.0, "mol/sec");
system.setMixingRule(2);

Stream inlet = new Stream("inlet", system);
inlet.run();

// Create and configure reactor
GibbsReactor reactor = new GibbsReactor("combustion reactor", inlet);
reactor.setEnergyMode(GibbsReactor.EnergyMode.ISOTHERMAL);
reactor.setMaxIterations(5000);
reactor.setConvergenceTolerance(1e-6);
reactor.setDampingComposition(0.01);
reactor.run();

// Get results
Stream outlet = (Stream) reactor.getOutletStream();
System.out.println("Outlet temperature: " + outlet.getTemperature("C") + " °C");
System.out.println("Conversion completed: " + reactor.hasConverged());

Configuration Options

Energy Mode

// Isothermal: temperature remains constant
reactor.setEnergyMode(GibbsReactor.EnergyMode.ISOTHERMAL);

// Adiabatic: temperature changes based on reaction enthalpy
reactor.setEnergyMode(GibbsReactor.EnergyMode.ADIABATIC);

// Using string (case-insensitive)
reactor.setEnergyMode("adiabatic");

Solver Parameters

Parameter Method Default Description
Max Iterations setMaxIterations(int) 5000 Maximum Newton-Raphson iterations
Convergence Tolerance setConvergenceTolerance(double) 1e-3 Convergence criterion for delta norm
Damping Factor setDampingComposition(double) 0.05 Step size for composition updates
reactor.setMaxIterations(10000);
reactor.setConvergenceTolerance(1e-8);
reactor.setDampingComposition(0.001);  // Smaller = more stable, slower

Inert Components

Mark components that should not participate in reactions:

// By name
reactor.setComponentAsInert("nitrogen");
reactor.setComponentAsInert("argon");

// By index
reactor.setComponentAsInert(0);

Database Species

// Use only components present in inlet stream (default)
reactor.setUseAllDatabaseSpecies(false);

// Add all species from Gibbs database (for product prediction)
reactor.setUseAllDatabaseSpecies(true);

Results and Diagnostics

Convergence Status

if (reactor.hasConverged()) {
    System.out.println("Solution converged in " + reactor.getActualIterations() + " iterations");
} else {
    System.out.println("Failed to converge. Final error: " + reactor.getFinalConvergenceError());
}

Thermodynamic Results

// Enthalpy of reaction (kJ)
double deltaH = reactor.getEnthalpyOfReactions();

// Temperature change in adiabatic mode (K)
double deltaT = reactor.getTemperatureChange();

// Reactor power (W, kW, or MW)
double powerW = reactor.getPower("W");
double powerKW = reactor.getPower("kW");

Mass Balance Verification

// Check mass balance closure
double massError = reactor.getMassBalanceError();  // Percentage error
boolean balanced = reactor.getMassBalanceConverged();  // True if error < 0.1%

// Element-wise balance
double[] elementIn = reactor.getElementMoleBalanceIn();
double[] elementOut = reactor.getElementMoleBalanceOut();
double[] elementDiff = reactor.getElementMoleBalanceDiff();
String[] elementNames = reactor.getElementNames();  // ["O", "N", "C", "H", "S", "Ar", "Z"]

Molar Flows

List<Double> inletMoles = reactor.getInletMoles();
List<Double> outletMoles = reactor.getOutletMoles();

Specialized Reactor: GibbsReactorCO2

For CO2/acid gas systems, use GibbsReactorCO2 which provides pre-configured reaction pathways:

import neqsim.process.equipment.reactor.GibbsReactorCO2;

GibbsReactorCO2 acidGasReactor = new GibbsReactorCO2("acid gas reactor", inlet);
acidGasReactor.run();

Important Limitations of GibbsReactorCO2:

Troubleshooting

Convergence Issues

  1. Reduce damping factor: Try setDampingComposition(0.001) or smaller
  2. Increase iterations: Use setMaxIterations(20000)
  3. Check initial compositions: Ensure products have small non-zero initial amounts
  4. Mark inerts: Components that don't react should be marked as inert

Mass Balance Errors

If mass balance doesn't close:

Numerical Instabilities

For stiff systems:

reactor.setDampingComposition(0.0001);  // Very small steps
reactor.setMaxIterations(50000);        // Allow more iterations
reactor.setConvergenceTolerance(1e-4);  // Relax tolerance slightly

Gibbs Database

The reactor uses thermodynamic data from CSV files in src/main/resources/data/GibbsReactDatabase/:

Supported Elements

The reactor tracks mass balance for: O, N, C, H, S, Ar, Z (charge)

Adding Custom Components

Custom components can be added to the database files following the existing format. Each component requires:

See Also

Electrolyzers

Electrolyzer Equipment

Documentation for electrolyzer equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.electrolyzer

Classes:

Class Description
Electrolyzer Base electrolyzer class
CO2Electrolyzer CO₂ electrolysis unit

Electrolyzers convert electrical energy into chemical energy through electrochemical reactions. Key applications:


Electrolyzer Class

Basic Usage

import neqsim.process.equipment.electrolyzer.Electrolyzer;

// Create electrolyzer with water feed
Electrolyzer electrolyzer = new Electrolyzer("PEM Electrolyzer", waterStream);
electrolyzer.setPower(1e6);  // 1 MW
electrolyzer.setEfficiency(0.70);  // 70% efficiency
electrolyzer.run();

// Get hydrogen production
StreamInterface h2Stream = electrolyzer.getHydrogenStream();
double h2Rate = h2Stream.getFlowRate("kg/hr");
System.out.println("H2 production: " + h2Rate + " kg/hr");

CO₂ Electrolyzer

The CO2Electrolyzer converts CO₂ to valuable products through electrolysis.

Basic Usage

import neqsim.process.equipment.electrolyzer.CO2Electrolyzer;

// Create CO2 electrolyzer
CO2Electrolyzer co2Elec = new CO2Electrolyzer("CO2 Electrolyzer", co2Stream);
co2Elec.setPower(500e3);  // 500 kW
co2Elec.run();

// Get products
StreamInterface products = co2Elec.getOutletStream();

Products

CO₂ electrolysis can produce various products depending on the catalyst:


Energy Calculations

Power Consumption

The electrical power required for electrolysis:

$$P = \frac{\Delta G}{\eta_{elec}}$$

Where:

Efficiency

// Set efficiency (energy efficiency)
electrolyzer.setEfficiency(0.75);  // 75%

// Get actual power consumption
double power = electrolyzer.getPower();  // W

Faradaic Efficiency

The fraction of electrical current that drives the desired reaction:

$$\eta_F = \frac{n \times F \times \dot{n}_{product}}{I}$$

Where:


Water Electrolysis

Reaction

$$2H_2O \rightarrow 2H_2 + O_2$$

Energy Requirement

Theoretical minimum: 39.4 kWh/kg H₂ (based on HHV)

Typical actual consumption:

Technology Energy (kWh/kg H₂)
Alkaline 50-55
PEM 50-60
SOEC 35-45

Example

// Water feed
SystemInterface waterFluid = new SystemSrkEos(298.15, 1.0);
waterFluid.addComponent("water", 1.0);
waterFluid.setMixingRule("classic");

Stream waterFeed = new Stream("Water Feed", waterFluid);
waterFeed.setFlowRate(1000.0, "kg/hr");
waterFeed.run();

// PEM Electrolyzer
Electrolyzer pemElec = new Electrolyzer("PEM Stack", waterFeed);
pemElec.setPower(10e6);  // 10 MW
pemElec.setEfficiency(0.70);  // 70%
pemElec.run();

// Hydrogen output
double h2Production = pemElec.getHydrogenStream().getFlowRate("kg/hr");
double specificEnergy = pemElec.getPower() / 1000 / h2Production;  // kWh/kg

System.out.println("H2 production: " + h2Production + " kg/hr");
System.out.println("Specific energy: " + specificEnergy + " kWh/kg H2");

Usage Examples

Green Hydrogen Plant

ProcessSystem process = new ProcessSystem();

// Deionized water feed
SystemInterface diWater = new SystemSrkEos(298.15, 1.0);
diWater.addComponent("water", 1.0);

Stream waterFeed = new Stream("DI Water", diWater);
waterFeed.setFlowRate(2000.0, "kg/hr");
process.add(waterFeed);

// Electrolyzer stack
Electrolyzer electrolyzer = new Electrolyzer("H2 Electrolyzer", waterFeed);
electrolyzer.setPower(20e6);  // 20 MW from renewable source
electrolyzer.setEfficiency(0.72);
process.add(electrolyzer);

// Hydrogen purification (PSA or membrane)
MembraneSeparator h2Purifier = new MembraneSeparator("H2 Purifier", 
    electrolyzer.getHydrogenStream());
h2Purifier.setPermeateFraction("hydrogen", 0.999);
h2Purifier.setPermeateFraction("water", 0.01);
process.add(h2Purifier);

// Compression for storage
Compressor h2Comp = new Compressor("H2 Compressor", h2Purifier.getPermeateStream());
h2Comp.setOutletPressure(350.0, "bara");
h2Comp.setPolytropicEfficiency(0.80);
process.add(h2Comp);

// Run process
process.run();

// Results
double h2Output = h2Comp.getOutletStream().getFlowRate("kg/hr");
double h2Purity = h2Comp.getOutletStream().getFluid().getMoleFraction("hydrogen") * 100;
System.out.println("H2 output: " + h2Output + " kg/hr");
System.out.println("H2 purity: " + h2Purity + " %");

Power-to-Methanol

// CO2 capture stream
Stream co2Stream = new Stream("Captured CO2", co2Fluid);
co2Stream.setFlowRate(1000.0, "kg/hr");

// CO2 electrolyzer producing syngas
CO2Electrolyzer co2Elec = new CO2Electrolyzer("CO2 Electrolyzer", co2Stream);
co2Elec.setPower(5e6);  // 5 MW
co2Elec.run();

// Additional hydrogen from water electrolysis
Electrolyzer h2Elec = new Electrolyzer("H2 Electrolyzer", waterStream);
h2Elec.setPower(15e6);  // 15 MW
h2Elec.run();

// Mix syngas and H2 for methanol synthesis
Mixer syngasMixer = new Mixer("Syngas Mixer");
syngasMixer.addStream(co2Elec.getOutletStream());
syngasMixer.addStream(h2Elec.getHydrogenStream());

// Methanol reactor (downstream)
// ...

Energy Storage Application

// Variable renewable power input
double[] powerProfile = loadRenewablePowerProfile();  // hourly MW

// Electrolyzer with variable power
Electrolyzer electrolyzer = new Electrolyzer("Variable Electrolyzer", waterFeed);
electrolyzer.setEfficiency(0.70);

double totalH2 = 0.0;
for (int hour = 0; hour < 24; hour++) {
    double power = powerProfile[hour] * 1e6;  // Convert MW to W

    if (power > 0) {
        electrolyzer.setPower(power);
        electrolyzer.run();

        double h2Rate = electrolyzer.getHydrogenStream().getFlowRate("kg/hr");
        totalH2 += h2Rate;

        System.out.println("Hour " + hour + ": Power=" + power/1e6 + 
            " MW, H2=" + h2Rate + " kg/hr");
    }
}

System.out.println("Total H2 production: " + totalH2 + " kg");

Operating Parameters

Temperature and Pressure

// Set operating conditions
electrolyzer.setTemperature(80.0, "C");  // PEM typical
electrolyzer.setPressure(30.0, "bara");  // Pressurized operation

// High-temperature electrolysis (SOEC)
electrolyzer.setTemperature(800.0, "C");
electrolyzer.setPressure(1.0, "bara");

Stack Configuration

// Set number of cells
electrolyzer.setNumberOfCells(100);

// Set cell voltage
electrolyzer.setCellVoltage(1.8);  // V

// Calculate current
double current = electrolyzer.getCurrent();  // A

CO2 Electrolyzer

CO₂ Electrolyzer usage example

The snippet below illustrates how to couple the new CO2Electrolyzer with a CO₂-rich feed, a power supply, and a downstream separator in a ProcessSystem.

import neqsim.process.equipment.electrolyzer.CO2Electrolyzer;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.stream.StreamInterface;
import neqsim.process.equipment.battery.BatteryStorage;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.Fluid;
import neqsim.thermo.system.SystemInterface;

SystemInterface feedFluid = new Fluid().create2(
    new String[] {"CO2", "water"},
    new double[] {0.95, 0.05},
    "mole/sec");
feedFluid.setTemperature(298.15);
feedFluid.setPressure(20.0);
Stream feedStream = new Stream("CO2 feed", feedFluid);

CO2Electrolyzer electrolyzer = new CO2Electrolyzer("CO2 electrolyzer", feedStream);
electrolyzer.setCO2Conversion(0.55);
electrolyzer.setGasProductSelectivity("CO", 0.7);
electrolyzer.setGasProductSelectivity("H2", 0.3);
electrolyzer.setProductFaradaicEfficiency("CO", 0.9);
electrolyzer.setElectronsPerMoleProduct("H2", 2.0);

// Downstream separation polishing the gas product
Separator syngasPolisher = new Separator("syngas polisher");
syngasPolisher.setInletStream((StreamInterface) electrolyzer.getGasProductStream());

ProcessSystem process = new ProcessSystem("CO2 to fuels");
process.add(feedStream);
process.add(electrolyzer);
process.add(syngasPolisher);
process.run();

double powerDraw = electrolyzer.getEnergyStream().getDuty();
BatteryStorage battery = new BatteryStorage("renewable battery", 5.0e8);
battery.discharge(powerDraw, 1.0 / 3600.0);
System.out.println("Electrolyzer power demand: " + powerDraw + " W");

The gas product stream carries the vapor-phase synthesis gas, the liquid product stream (accessible through getLiquidProductStream()) contains soluble products such as formate or methanol, and the EnergyStream keeps track of the instantaneous electrical duty demanded from the battery.

Flares

Flare Systems

Documentation for flare equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.flare

Classes:

Class Description
Flare Main flare combustion unit
FlareStack Flare stack with dispersion
FlareCapacityDTO Capacity check results
FlarePerformanceDTO Performance metrics

Flares are safety devices that combust hydrocarbon gases that cannot be recovered. They are used for:


Flare Class

Basic Usage

import neqsim.process.equipment.flare.Flare;

// Create flare with inlet stream
Flare flare = new Flare("HP Flare", inletStream);
flare.run();

// Get results
double heatRelease = flare.getHeatDuty();  // W
double co2Emission = flare.getCO2Emission();  // kg/s

System.out.println("Heat release: " + heatRelease/1e6 + " MW");
System.out.println("CO2 emission: " + co2Emission * 3600 + " kg/hr");

Constructor Options

// With name only
Flare flare = new Flare("Flare");
flare.setInletStream(flareGas);

// With name and inlet stream
Flare flare = new Flare("Flare", flareGas);

Combustion Calculations

Heat Release

The flare calculates heat release based on the Lower Calorific Value (LCV):

$$Q = LCV \times \dot{V}_{Sm3}$$

// Get heat release rate
double heatDuty = flare.getHeatDuty();  // W

// Convert to MW
double heatMW = heatDuty / 1e6;
System.out.println("Heat release: " + heatMW + " MW");

CO₂ Emissions

CO₂ emissions are calculated from the carbon content of the flared gas:

$$\dot{m}_{CO_2} = \sum_i (x_i \times \dot{n}_{total} \times n_{C,i} \times M_{CO_2})$$

Where:

// Get CO2 emission rate
double co2Rate = flare.getCO2Emission();  // kg/s

// Convert to tonnes per hour
double co2TonHr = co2Rate * 3.6;
System.out.println("CO2 emissions: " + co2TonHr + " t/hr");

Flare Design Parameters

Radiation

// Set radiant fraction (fraction of heat released as radiation)
flare.setRadiantFraction(0.20);  // 20%

// Set flame height for radiation calculations
flare.setFlameHeight(40.0);  // m

// Get radiation at ground level
double radiation = flare.getRadiationAtDistance(100.0);  // W/m² at 100m

Tip Diameter

// Set tip diameter for velocity calculations
flare.setTipDiameter(0.5);  // m

// Get tip velocity
double tipVelocity = flare.getTipVelocity();  // m/s

Capacity Monitoring

Design Capacity

// Set design capacity limits
flare.setDesignHeatDutyCapacity(100e6);  // 100 MW
flare.setDesignMassFlowCapacity(50.0);   // 50 kg/s
flare.setDesignMolarFlowCapacity(2000.0); // 2000 mol/s

Capacity Check

// Check if within capacity
flare.run();
CapacityCheckResult result = flare.getCapacityCheckResult();

if (result.isOverCapacity()) {
    System.out.println("WARNING: Flare over capacity!");
    System.out.println("Heat duty: " + result.getHeatDutyPercent() + "% of design");
    System.out.println("Mass flow: " + result.getMassFlowPercent() + "% of design");
}

Dynamic Operation

Transient Simulation

// Track cumulative values during transient
double timeStep = 1.0;  // seconds
double totalTime = 900.0;  // 15 minutes

for (double t = 0; t < totalTime; t += timeStep) {
    // Update inlet conditions (e.g., from blowdown)
    updateFlareInlet(t);

    flare.run();
    flare.updateCumulative(timeStep);
}

// Get cumulative values
double totalHeatGJ = flare.getCumulativeHeatReleased();  // GJ
double totalGasBurned = flare.getCumulativeGasBurned();  // kg
double totalCO2 = flare.getCumulativeCO2Emission();  // kg

System.out.println("Total heat released: " + totalHeatGJ + " GJ");
System.out.println("Total gas burned: " + totalGasBurned + " kg");
System.out.println("Total CO2 emitted: " + totalCO2 + " kg");

Reset Cumulative Counters

// Reset for new transient event
flare.resetCumulative();

FlareStack Class

The FlareStack class includes additional features for dispersion modeling:

import neqsim.process.equipment.flare.FlareStack;

FlareStack flareStack = new FlareStack("Main Flare Stack", inletStream);
flareStack.setStackHeight(100.0);  // m
flareStack.setTipDiameter(0.6);    // m
flareStack.run();

// Get dispersion parameters
FlareDispersionSurrogateDTO dispersion = flareStack.getDispersionSurrogate();
double effectiveHeight = dispersion.getEffectiveStackHeight();
double plumeMomentum = dispersion.getPlumeMomentum();

Usage Examples

Emergency Blowdown Flare Load

ProcessSystem process = new ProcessSystem();

// Vessel being depressured
Tank vessel = new Tank("HP Vessel", vesselFluid);
vessel.setVolume(100.0, "m3");
vessel.setPressure(100.0, "bara");
process.add(vessel);

// Blowdown valve
BlowdownValve bdv = new BlowdownValve("BDV-100", vessel);
bdv.setOrificeSize(100.0, "mm");
bdv.setDownstreamPressure(1.5, "barg");
process.add(bdv);

// Flare receiving blowdown
Flare flare = new Flare("HP Flare", bdv.getOutletStream());
flare.setDesignHeatDutyCapacity(200e6);  // 200 MW design
process.add(flare);

// Run transient blowdown
double dt = 1.0;
for (double t = 0; t < 900; t += dt) {
    vessel.runTransient(dt);
    bdv.run();
    flare.run();
    flare.updateCumulative(dt);

    System.out.println("t=" + t + "s, P=" + vessel.getPressure("barg") + 
        " barg, Q=" + flare.getHeatDuty()/1e6 + " MW");
}

Flare Header System

// Multiple sources to common flare header
Mixer flareHeader = new Mixer("Flare Header");
flareHeader.addStream(lpFlareSource1);
flareHeader.addStream(lpFlareSource2);
flareHeader.addStream(lpFlareSource3);
process.add(flareHeader);

// Flare KO drum
Separator flareKODrum = new Separator("Flare KO Drum", flareHeader.getOutletStream());
process.add(flareKODrum);

// Flare tip
Flare flareTip = new Flare("LP Flare", flareKODrum.getGasOutStream());
flareTip.setDesignHeatDutyCapacity(50e6);
process.add(flareTip);

process.run();

// Check overall flare load
double totalLoad = flareTip.getHeatDuty();
double percentCapacity = totalLoad / flareTip.getDesignHeatDutyCapacity() * 100;
System.out.println("Flare load: " + percentCapacity + "% of design");

Relief Scenario Calculation

// Calculate flare load for blocked outlet scenario
double reliefRate = calculateBlockedOutletRelief();  // kg/hr

// Create relief stream
SystemInterface reliefFluid = vesselFluid.clone();
Stream reliefStream = new Stream("Relief Flow", reliefFluid);
reliefStream.setFlowRate(reliefRate, "kg/hr");
reliefStream.run();

// Calculate flare load
Flare flare = new Flare("Scenario Flare", reliefStream);
flare.run();

System.out.println("Relief scenario:");
System.out.println("  Flow rate: " + reliefRate + " kg/hr");
System.out.println("  Heat release: " + flare.getHeatDuty()/1e6 + " MW");
System.out.println("  CO2 emission: " + flare.getCO2Emission()*3600 + " kg/hr");

Performance Reporting

Get Performance Summary

FlarePerformanceDTO performance = flare.getPerformance();

System.out.println("=== Flare Performance ===");
System.out.println("Heat release: " + performance.getHeatDutyMW() + " MW");
System.out.println("Mass flow: " + performance.getMassFlowKgS() + " kg/s");
System.out.println("Tip velocity: " + performance.getTipVelocity() + " m/s");
System.out.println("CO2 emission: " + performance.getCO2EmissionKgS() + " kg/s");
System.out.println("Radiant heat: " + performance.getRadiantHeatMW() + " MW");

Environmental Calculations

Carbon Footprint

// Calculate annual CO2 emissions from continuous flaring
double avgFlowRate = 1000.0;  // Sm3/hr average
double hoursPerYear = 8760.0;

flare.getInletStream().setFlowRate(avgFlowRate, "Sm3/hr");
flare.run();

double annualCO2 = flare.getCO2Emission() * 3600 * hoursPerYear / 1000;  // tonnes/year
System.out.println("Annual CO2 emissions: " + annualCO2 + " tonnes/year");

Adsorbers

Adsorbers

Documentation for adsorption equipment in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.adsorber

The adsorber package provides equipment for modeling gas treatment processes using solid adsorbents. Adsorption is commonly used for:


SimpleAdsorber

The SimpleAdsorber class models a simplified adsorption column for gas treatment applications.

Class Hierarchy

ProcessEquipmentBaseClass
└── SimpleAdsorber

Constructor

import neqsim.process.equipment.adsorber.SimpleAdsorber;
import neqsim.process.equipment.stream.Stream;

// Basic constructor
SimpleAdsorber adsorber = new SimpleAdsorber("CO2 Adsorber");

// Constructor with inlet stream
SimpleAdsorber adsorber = new SimpleAdsorber("CO2 Adsorber", feedStream);

Key Properties

Property Description Default
numberOfStages Number of theoretical stages 5
numberOfTheoreticalStages Theoretical stages (continuous) 3.0
absorptionEfficiency Removal efficiency (0-1) 0.5
HTU Height of Transfer Unit (m) 0.85
NTU Number of Transfer Units 2.0
stageEfficiency Per-stage efficiency 0.25

Key Features

CO2 Absorption with MDEA

The SimpleAdsorber is configured for CO2 removal using MDEA (methyldiethanolamine) solvent:

// Create gas feed with CO2
SystemInterface gasFluid = new SystemSrkEos(298.15, 50.0);
gasFluid.addComponent("methane", 0.85);
gasFluid.addComponent("CO2", 0.10);
gasFluid.addComponent("nitrogen", 0.05);
gasFluid.setMixingRule("classic");

Stream feedGas = new Stream("Feed Gas", gasFluid);
feedGas.setFlowRate(10000.0, "Sm3/hr");

// Create adsorber
SimpleAdsorber adsorber = new SimpleAdsorber("CO2 Removal", feedGas);

// Run
adsorber.run();

// Get treated gas
StreamInterface treatedGas = adsorber.getOutStream(0);
System.out.println("CO2 in treated gas: " + 
    treatedGas.getFluid().getComponent("CO2").getx() * 100 + " mol%");

Multiple Output Streams

The adsorber provides two output streams:


Usage Examples

Basic CO2 Removal

import neqsim.process.equipment.adsorber.SimpleAdsorber;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create sour gas
SystemInterface sourGas = new SystemSrkEos(298.15, 50.0);
sourGas.addComponent("methane", 0.80);
sourGas.addComponent("ethane", 0.05);
sourGas.addComponent("CO2", 0.10);
sourGas.addComponent("H2S", 0.02);
sourGas.addComponent("nitrogen", 0.03);
sourGas.setMixingRule("classic");

Stream feed = new Stream("Sour Gas Feed", sourGas);
feed.setFlowRate(5.0, "MSm3/day");

// Create and run adsorber
SimpleAdsorber acidGasRemoval = new SimpleAdsorber("AGRU", feed);
acidGasRemoval.setAbsorptionEfficiency(0.95);
acidGasRemoval.run();

// Results
StreamInterface sweetGas = acidGasRemoval.getOutStream(0);
System.out.println("Sweet gas CO2: " + 
    sweetGas.getFluid().getComponent("CO2").getx() * 1e6 + " ppm");

Integration in Process System

import neqsim.process.processmodel.ProcessSystem;

ProcessSystem process = new ProcessSystem();

// Add equipment
process.add(feedStream);
process.add(adsorber);
process.add(treatedGasExport);

// Run complete process
process.run();

Parameters

Setting Absorption Efficiency

// Set 95% removal efficiency
adsorber.setAbsorptionEfficiency(0.95);

Setting Stage Parameters

adsorber.setNumberOfStages(10);
adsorber.setNumberOfTheoreticalStages(7.5);
adsorber.setStageEfficiency(0.75);

Transfer Unit Parameters

adsorber.setHTU(0.5);  // Height of Transfer Unit in meters
adsorber.setNTU(4.0);  // Number of Transfer Units

Mechanical Design

The SimpleAdsorber supports mechanical design calculations through AdsorberMechanicalDesign:

import neqsim.process.mechanicaldesign.adsorber.AdsorberMechanicalDesign;

AdsorberMechanicalDesign design = adsorber.getMechanicalDesign();
design.calcDesign();

System.out.println("Vessel diameter: " + design.getInnerDiameter() + " m");
System.out.println("Vessel height: " + design.getTotalHeight() + " m");


See Also

Power Generation

Power Generation Equipment

Documentation for power generation equipment in NeqSim, including gas turbines, fuel cells, wind turbines, and solar panels.

Table of Contents


Overview

Location: neqsim.process.equipment.powergeneration

The power generation package provides equipment models for converting chemical and renewable energy sources into electrical power:

Equipment Energy Source Output
GasTurbine Fuel gas combustion Electricity + heat
FuelCell Hydrogen + oxygen Electricity + water
WindTurbine Wind Electricity
SolarPanel Solar radiation Electricity
BatteryStorage Stored electricity Electricity

Gas Turbine

The GasTurbine class models a simple cycle gas turbine with integrated air compression, combustion, and expansion.

Class Hierarchy

TwoPortEquipment
└── GasTurbine

Constructor

import neqsim.process.equipment.powergeneration.GasTurbine;
import neqsim.process.equipment.stream.Stream;

// Basic constructor
GasTurbine turbine = new GasTurbine("GT-101");

// Constructor with fuel stream
GasTurbine turbine = new GasTurbine("GT-101", fuelGasStream);

Key Properties

Property Description Unit
combustionPressure Combustor pressure bara
airGasRatio Air to fuel ratio -
power Net electrical power output W
heat Heat output W
compressorPower Air compressor power W
expanderPower Expander power W

Example Usage

import neqsim.process.equipment.powergeneration.GasTurbine;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create fuel gas
SystemInterface fuelGas = new SystemSrkEos(288.15, 25.0);
fuelGas.addComponent("methane", 0.90);
fuelGas.addComponent("ethane", 0.05);
fuelGas.addComponent("propane", 0.03);
fuelGas.addComponent("nitrogen", 0.02);
fuelGas.setMixingRule("classic");

Stream fuelStream = new Stream("Fuel Gas", fuelGas);
fuelStream.setFlowRate(1000.0, "kg/hr");

// Create gas turbine
GasTurbine turbine = new GasTurbine("Power Turbine", fuelStream);
turbine.setCombustionPressure(15.0);  // bara
turbine.setAirGasRatio(3.0);

// Run simulation
turbine.run();

// Results
System.out.println("Net power: " + turbine.getPower() / 1e6 + " MW");
System.out.println("Heat output: " + turbine.getHeat() / 1e6 + " MW");
System.out.println("Thermal efficiency: " + turbine.getEfficiency() * 100 + "%");

Fuel Cell

The FuelCell class models a hydrogen fuel cell that converts hydrogen and oxygen to electricity and water.

Class Hierarchy

TwoPortEquipment
└── FuelCell

Constructor

import neqsim.process.equipment.powergeneration.FuelCell;

// Basic constructor
FuelCell cell = new FuelCell("FC-101");

// Constructor with fuel and oxidant streams
FuelCell cell = new FuelCell("FC-101", hydrogenStream, airStream);

Key Properties

Property Description Unit
efficiency Electrical efficiency 0-1
power Electrical power output W
heatLoss Heat loss to environment W

Example Usage

import neqsim.process.equipment.powergeneration.FuelCell;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create hydrogen fuel stream
SystemInterface h2Fluid = new SystemSrkEos(298.15, 5.0);
h2Fluid.addComponent("hydrogen", 1.0);
h2Fluid.setMixingRule("classic");

Stream hydrogenFeed = new Stream("Hydrogen", h2Fluid);
hydrogenFeed.setFlowRate(10.0, "kg/hr");

// Create air stream
SystemInterface airFluid = new SystemSrkEos(298.15, 1.01325);
airFluid.addComponent("nitrogen", 0.79);
airFluid.addComponent("oxygen", 0.21);
airFluid.setMixingRule("classic");

Stream airFeed = new Stream("Air", airFluid);
airFeed.setFlowRate(100.0, "kg/hr");

// Create fuel cell
FuelCell fuelCell = new FuelCell("SOFC", hydrogenFeed, airFeed);
fuelCell.setEfficiency(0.55);

// Run simulation
fuelCell.run();

// Results
System.out.println("Electrical power: " + fuelCell.getPower() / 1000 + " kW");
System.out.println("Heat loss: " + fuelCell.getHeatLoss() / 1000 + " kW");

Wind Turbine

The WindTurbine class models wind power generation based on wind speed and turbine characteristics.

Constructor

import neqsim.process.equipment.powergeneration.WindTurbine;

WindTurbine turbine = new WindTurbine("WT-01");
turbine.setWindSpeed(12.0);        // m/s
turbine.setRotorDiameter(120.0);   // m
turbine.setEfficiency(0.45);

Key Properties

Property Description Unit
windSpeed Wind velocity m/s
rotorDiameter Rotor diameter m
efficiency Power coefficient 0-0.593 (Betz limit)
power Electrical power output W

Solar Panel

The SolarPanel class models photovoltaic power generation.

Constructor

import neqsim.process.equipment.powergeneration.SolarPanel;

SolarPanel panel = new SolarPanel("PV-Array");
panel.setPanelArea(1000.0);        // m²
panel.setSolarIrradiance(800.0);   // W/m²
panel.setEfficiency(0.20);

Key Properties

Property Description Unit
panelArea Total panel area
solarIrradiance Solar radiation W/m²
efficiency Panel efficiency 0-1
power Electrical power output W

Battery Storage

Location: neqsim.process.equipment.battery

The BatteryStorage class models electrical energy storage systems.

Constructor

import neqsim.process.equipment.battery.BatteryStorage;

BatteryStorage battery = new BatteryStorage("BESS-01");
battery.setCapacity(100.0);          // MWh
battery.setMaxPower(25.0);           // MW
battery.setRoundTripEfficiency(0.90);

Key Properties

Property Description Unit
capacity Total energy capacity MWh
maxPower Maximum charge/discharge rate MW
roundTripEfficiency Charge-discharge efficiency 0-1
stateOfCharge Current energy level 0-1

Usage Examples

Combined Heat and Power (CHP) System

import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.powergeneration.GasTurbine;
import neqsim.process.equipment.heatexchanger.Heater;

ProcessSystem chpSystem = new ProcessSystem("CHP Plant");

// Create fuel gas stream
Stream fuelGas = new Stream("Fuel", fuelFluid);
fuelGas.setFlowRate(500.0, "kg/hr");
chpSystem.add(fuelGas);

// Gas turbine
GasTurbine turbine = new GasTurbine("GT", fuelGas);
turbine.setCombustionPressure(12.0);
chpSystem.add(turbine);

// Heat recovery steam generator (simplified)
Heater hrsg = new Heater("HRSG", turbine.getExhaustStream());
hrsg.setOutTemperature(150.0, "C");
chpSystem.add(hrsg);

// Run
chpSystem.run();

// Calculate efficiency
double electricalPower = turbine.getPower();
double thermalPower = hrsg.getDuty();
double fuelInput = fuelGas.getFlowRate("kg/hr") * 50e6 / 3600;  // LHV ~ 50 MJ/kg

double electricalEff = electricalPower / fuelInput;
double totalEff = (electricalPower + thermalPower) / fuelInput;

System.out.println("Electrical efficiency: " + electricalEff * 100 + "%");
System.out.println("Total CHP efficiency: " + totalEff * 100 + "%");

Hybrid Renewable System

// Solar + Wind + Battery system
SolarPanel solar = new SolarPanel("PV");
solar.setPanelArea(5000.0);
solar.setSolarIrradiance(600.0);
solar.setEfficiency(0.18);

WindTurbine wind = new WindTurbine("Wind");
wind.setWindSpeed(8.0);
wind.setRotorDiameter(80.0);

BatteryStorage battery = new BatteryStorage("Battery");
battery.setCapacity(10.0);  // MWh
battery.setMaxPower(5.0);   // MW

// Calculate total renewable generation
solar.run();
wind.run();

double totalGeneration = solar.getPower() + wind.getPower();
System.out.println("Total renewable power: " + totalGeneration / 1e6 + " MW");

Diff. Pressure

Differential Pressure Devices

Documentation for differential pressure measurement and flow restriction equipment in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.diffpressure

The differential pressure package provides equipment for:

Class Description
Orifice ISO 5167 orifice plate flow restriction
DifferentialPressureFlowCalculator ΔP-based flow calculation

Orifice Plate

The Orifice class models an orifice plate flow restriction device compliant with ISO 5167.

Class Hierarchy

TwoPortEquipment
└── Orifice

Key Features

Constructor

import neqsim.process.equipment.diffpressure.Orifice;

// Basic constructor
Orifice orifice = new Orifice("FO-101");

// Full constructor with ISO 5167 parameters
Orifice orifice = new Orifice(
    "FO-101",           // Name
    0.1,                // Pipe diameter (m)
    0.05,               // Orifice diameter (m)
    50.0,               // Upstream pressure (bara)
    5.0,                // Downstream pressure (bara)
    0.61                // Discharge coefficient
);

Key Properties

Property Description Unit
diameter Upstream pipe internal diameter m
orificeDiameter Orifice bore diameter m
pressureUpstream Upstream pressure bara
pressureDownstream Downstream boundary pressure bara
dischargeCoefficient Cd (typically 0.60-0.62) -
dp Pressure differential bar

Beta Ratio

The beta ratio (β) is the ratio of orifice diameter to pipe diameter:

β = d/D

Where:

Typical range: 0.2 ≤ β ≤ 0.75


ISO 5167 Implementation

Discharge Coefficient

The orifice uses the Reader-Harris/Gallagher equation for the discharge coefficient:

C = f(β, ReD, L₁, L₂)

Where:

Expansibility Factor

For compressible flow, the expansibility factor ε accounts for gas expansion:

ε = f(β, κ, τ)

Where:

Flow Equation

Mass flow rate through the orifice:

ṁ = C · ε · (π/4) · d² · √(2 · ρ₁ · ΔP)

Differential Pressure Flow Calculator

The DifferentialPressureFlowCalculator provides utilities for ΔP-based flow calculations.

Example Usage

import neqsim.process.equipment.diffpressure.DifferentialPressureFlowCalculator;

DifferentialPressureFlowCalculator calc = new DifferentialPressureFlowCalculator();
calc.setPipeDiameter(0.1);
calc.setOrificeDiameter(0.05);
calc.setDifferentialPressure(0.5);  // bar

double massFlow = calc.calculateMassFlow(fluid);
System.out.println("Mass flow: " + massFlow + " kg/s");

Usage Examples

Flow Measurement

import neqsim.process.equipment.diffpressure.Orifice;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create gas stream
SystemInterface gas = new SystemSrkEos(288.15, 50.0);
gas.addComponent("methane", 0.90);
gas.addComponent("ethane", 0.05);
gas.addComponent("propane", 0.03);
gas.addComponent("nitrogen", 0.02);
gas.setMixingRule("classic");

Stream gasStream = new Stream("Process Gas", gas);
gasStream.setFlowRate(10000.0, "Sm3/hr");

// Create orifice meter
Orifice meter = new Orifice("FO-101");
meter.setInletStream(gasStream);
meter.setDiameter(0.15);           // 150mm pipe
meter.setOrificeDiameter(0.075);   // 75mm orifice (β = 0.5)

// Run
meter.run();

// Get differential pressure
System.out.println("ΔP: " + meter.getDp() * 1000 + " mbar");
System.out.println("Flow rate: " + meter.getOutletStream().getFlowRate("Sm3/hr") + " Sm3/hr");

Transient Blowdown Simulation

The orifice can be used for transient depressurization simulations:

import neqsim.process.equipment.diffpressure.Orifice;
import neqsim.process.equipment.tank.Tank;

// Create vessel
Tank vessel = new Tank("V-101", vesselFluid);
vessel.setVolume(100.0);  // m³

// Create blowdown orifice
Orifice blowdownOrifice = new Orifice(
    "RO-101",
    0.1,     // 100mm pipe
    0.025,   // 25mm orifice
    vessel.getPressure(),
    1.5,     // Flare header pressure
    0.61     // Discharge coefficient
);
blowdownOrifice.setInletStream(vessel.getOutletStream());

// Run transient simulation
for (double t = 0; t < 3600; t += 1.0) {
    blowdownOrifice.setPressureUpstream(vessel.getPressure());
    blowdownOrifice.run();

    double massFlow = blowdownOrifice.getOutletStream().getFlowRate("kg/s");
    vessel.removeMass(massFlow * 1.0);  // Remove mass for 1 second

    if (t % 60 == 0) {
        System.out.println("t=" + t + "s, P=" + vessel.getPressure() + " bara");
    }
}

Integration in Process System

import neqsim.process.processmodel.ProcessSystem;

ProcessSystem process = new ProcessSystem("Flow Metering");

// Feed
process.add(feedStream);

// Flow meter
Orifice flowMeter = new Orifice("FO-101");
flowMeter.setInletStream(feedStream);
flowMeter.setDiameter(0.1);
flowMeter.setOrificeDiameter(0.05);
process.add(flowMeter);

// Downstream equipment
Separator separator = new Separator("V-101", flowMeter.getOutletStream());
process.add(separator);

// Run
process.run();

Design Considerations

Orifice Sizing

For accurate flow measurement:

Pressure Recovery

Permanent pressure loss is approximately:

ΔP_permanent ≈ (1 - β⁴) × ΔP_measured

Cavitation

Avoid cavitation by ensuring:

P₂ > 2 × P_vapor

Manifolds

Manifolds

Documentation for manifold equipment that combines stream mixing and splitting in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.manifold

A manifold is a process equipment that combines the functionality of a mixer and a splitter. It can:

This is particularly useful for:

Class Description
Manifold Combined mixer/splitter unit

Manifold Class

The Manifold class extends ProcessEquipmentBaseClass and contains both a Mixer and a Splitter internally.

Class Hierarchy

ProcessEquipmentBaseClass
└── Manifold
    ├── contains: Mixer
    └── contains: Splitter

Key Features

Constructor

import neqsim.process.equipment.manifold.Manifold;

// Basic constructor
Manifold manifold = new Manifold("PM-101");

Methods

Method Description
addStream(Stream) Add an input stream to the manifold
getSplitStream(int index) Get a specific output stream by index
setSplitNumber(int n) Set number of output streams
setSplitFactors(double[]) Set split ratios for outputs
getMixer() Access the internal mixer
getSplitter() Access the internal splitter
run() Execute mixing then splitting

Usage Examples

Basic Manifold Operation

import neqsim.process.equipment.manifold.Manifold;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create input streams (e.g., from multiple wells)
SystemInterface well1Fluid = new SystemSrkEos(350.0, 100.0);
well1Fluid.addComponent("methane", 0.85);
well1Fluid.addComponent("ethane", 0.10);
well1Fluid.addComponent("propane", 0.05);
well1Fluid.setMixingRule("classic");

Stream well1Stream = new Stream("Well-1", well1Fluid);
well1Stream.setFlowRate(50000, "Sm3/day");

SystemInterface well2Fluid = new SystemSrkEos(340.0, 95.0);
well2Fluid.addComponent("methane", 0.82);
well2Fluid.addComponent("ethane", 0.12);
well2Fluid.addComponent("propane", 0.06);
well2Fluid.setMixingRule("classic");

Stream well2Stream = new Stream("Well-2", well2Fluid);
well2Stream.setFlowRate(75000, "Sm3/day");

// Run inlet streams
well1Stream.run();
well2Stream.run();

// Create manifold
Manifold productionManifold = new Manifold("PM-101");
productionManifold.addStream(well1Stream);
productionManifold.addStream(well2Stream);

// Configure splitting (2 outlet lines)
productionManifold.setSplitNumber(2);
productionManifold.setSplitFactors(new double[] {0.6, 0.4});

// Run manifold
productionManifold.run();

// Access output streams
Stream toSeparator1 = (Stream) productionManifold.getSplitStream(0);
Stream toSeparator2 = (Stream) productionManifold.getSplitStream(1);

System.out.println("Total inlet: " + (50000 + 75000) + " Sm3/day");
System.out.println("Outlet 1: " + toSeparator1.getFlowRate("Sm3/day") + " Sm3/day");
System.out.println("Outlet 2: " + toSeparator2.getFlowRate("Sm3/day") + " Sm3/day");

Production Manifold System

import neqsim.process.processmodel.ProcessSystem;

ProcessSystem facility = new ProcessSystem("Offshore Platform");

// Wells
Stream well1 = createWellStream("Well-1", 50000);
Stream well2 = createWellStream("Well-2", 45000);
Stream well3 = createWellStream("Well-3", 60000);
facility.add(well1);
facility.add(well2);
facility.add(well3);

// Production manifold
Manifold manifold = new Manifold("Production Manifold");
manifold.addStream(well1);
manifold.addStream(well2);
manifold.addStream(well3);
manifold.setSplitNumber(2);  // Two production trains
manifold.setSplitFactors(new double[] {0.5, 0.5});
facility.add(manifold);

// Train separators
Separator separator1 = new Separator("V-101");
separator1.setInletStream(manifold.getSplitStream(0));
facility.add(separator1);

Separator separator2 = new Separator("V-201");
separator2.setInletStream(manifold.getSplitStream(1));
facility.add(separator2);

// Run facility
facility.run();

Accessing Internal Components

Manifold manifold = new Manifold("PM-101");
manifold.addStream(stream1);
manifold.addStream(stream2);

// Access internal mixer for mixed stream properties
Mixer mixer = manifold.getMixer();
mixer.run();
Stream mixedStream = mixer.getOutletStream();
System.out.println("Mixed temperature: " + mixedStream.getTemperature("C") + " °C");
System.out.println("Mixed pressure: " + mixedStream.getPressure("bara") + " bara");

// Access internal splitter
Splitter splitter = manifold.getSplitter();

Integration Patterns

Well Gathering System

Well-1 ──┐
         │
Well-2 ──┼──► [Manifold] ──┬──► Train A
         │                 │
Well-3 ──┘                 └──► Train B

Load Balancing

// Distribute load evenly across processing trains
int numTrains = 3;
double[] splitFactors = new double[numTrains];
for (int i = 0; i < numTrains; i++) {
    splitFactors[i] = 1.0 / numTrains;
}
manifold.setSplitFactors(splitFactors);

Dynamic Rerouting

// Redirect all flow to Train A (e.g., Train B offline)
manifold.setSplitFactors(new double[] {1.0, 0.0});
manifold.run();

Design Considerations

Pressure Matching

Input streams should have similar pressures. If pressures differ significantly, use chokes or control valves upstream.

Temperature Mixing

The manifold performs adiabatic mixing. The outlet temperature is calculated from energy balance.

Composition

The mixed composition is the flow-weighted average of all inlet compositions.


Comparison with Mixer/Splitter

Aspect Manifold Mixer + Splitter
Construction Single equipment Two separate units
Modeling Combined unit Sequential execution
Use Case Production headers General mixing/splitting
Intermediate Access Via getMixer()/getSplitter() Direct access

Battery Storage

Battery storage unit

The BatteryStorage unit stores electrical energy for later use. It maintains an internal state-of-charge and exchanges power with the rest of a process through its energy stream. Positive duty on the energy stream represents charging while negative duty represents power delivery.

The battery can be combined with other power generation units such as FuelCell or GasTurbine to buffer excess electricity or supply power during demand peaks.

BatteryStorage battery = new BatteryStorage("battery", 5.0e5);
FuelCell cell = new FuelCell("cell", fuel, oxidant);

cell.run();
// store half of the produced power for one hour
battery.charge(-cell.getEnergyStream().getDuty() / 2.0, 1.0);
battery.run();

Solar Panel

Solar Panel Unit Operation

The SolarPanel unit converts solar irradiance into electrical power using a simple relation between the incoming radiation, panel area and efficiency:

Power = irradiance [W/m^2] × panel area [m^2] × efficiency

The produced power is available from the unit's energy stream as a negative duty (indicating power generation).

Example

SolarPanel panel = new SolarPanel("panel");
panel.setIrradiance(800.0); // W/m^2
panel.setPanelArea(2.0);    // m^2
panel.setEfficiency(0.2);   // 20%
panel.run();
System.out.println(panel.getPower());

Chapter 19: Wells, Pipelines & Subsea

Wells

Well and Reservoir Equipment

Documentation for well and reservoir equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.well

Classes:

Related: neqsim.process.equipment.reservoir


SimpleWell

Basic Usage

import neqsim.process.equipment.well.SimpleWell;

// Create reservoir fluid
SystemInterface reservoirFluid = new SystemPrEos(373.15, 250.0);
reservoirFluid.addComponent("methane", 0.70);
reservoirFluid.addComponent("ethane", 0.10);
reservoirFluid.addComponent("propane", 0.05);
reservoirFluid.addComponent("n-heptane", 0.10);
reservoirFluid.addComponent("water", 0.05);
reservoirFluid.setMixingRule("classic");

// Create well
SimpleWell well = new SimpleWell("Producer 1", reservoirFluid);
well.setWellheadPressure(50.0, "bara");
well.setFlowRate(10000.0, "Sm3/day");
well.run();

Stream wellheadStream = well.getOutletStream();

IPR Curves

Vogel IPR (Oil Wells)

$$q = q_{max} \left[1 - 0.2\frac{P_{wf}}{P_r} - 0.8\left(\frac{P_{wf}}{P_r}\right)^2\right]$$

well.setIPRModel("vogel");
well.setReservoirPressure(300.0, "bara");
well.setMaxFlowRate(50000.0, "Sm3/day");
well.setWellheadPressure(50.0, "bara");
well.run();

double flowRate = well.getFlowRate("Sm3/day");

Darcy IPR (Gas Wells)

$$q = \frac{k \cdot h \cdot (P_r^2 - P_{wf}^2)}{1422 \cdot T \cdot \mu \cdot Z \cdot \ln(r_e/r_w)}$$

well.setIPRModel("darcy");
well.setPermeability(100.0, "mD");
well.setPayThickness(50.0, "m");
well.setDrainageRadius(500.0, "m");
well.setWellboreRadius(0.1, "m");

Backpressure Equation

$$q = C (P_r^2 - P_{wf}^2)^n$$

well.setIPRModel("backpressure");
well.setBackpressureCoefficient(0.001);
well.setBackpressureExponent(0.85);

Wellbore Hydraulics

Vertical Lift Performance

well.setWellDepth(3000.0, "m");
well.setTubingDiameter(0.1, "m");
well.setWallRoughness(0.00005, "m");

// Correlation selection
well.setPressureDropCorrelation("beggs-brill");
// Options: "beggs-brill", "hagedorn-brown", "duns-ros", "gray"

well.run();

double bottomholePressure = well.getBottomholePressure("bara");
double pressureDrop = well.getTubingPressureDrop("bar");

Temperature Profile

well.setReservoirTemperature(120.0, "C");
well.setSurfaceTemperature(30.0, "C");
well.setGeothermalGradient(0.03, "C/m");

well.run();

double wellheadT = well.getWellheadTemperature("C");

Choke Modeling

Wellhead Choke

import neqsim.process.equipment.valve.ChokeValve;

ChokeValve choke = new ChokeValve("Wellhead Choke", well.getOutletStream());
choke.setOutletPressure(30.0, "bara");
choke.run();

// Or specify bean size
choke.setBeanSize(24, "64ths");  // 24/64" choke
choke.run();

double chokeDP = choke.getPressureDrop("bar");

Critical Flow

boolean isCritical = choke.isCriticalFlow();
double criticalRatio = choke.getCriticalPressureRatio();

Nodal Analysis

Find operating point by intersecting IPR and VLP curves.

// IPR curve (reservoir deliverability)
double[] Pwf_ipr = new double[20];
double[] q_ipr = new double[20];

double Pr = 300.0;  // Reservoir pressure, bar
double qmax = 50000.0;  // Max rate, Sm3/day

for (int i = 0; i < 20; i++) {
    Pwf_ipr[i] = Pr * i / 20.0;
    // Vogel equation
    q_ipr[i] = qmax * (1 - 0.2 * (Pwf_ipr[i]/Pr) - 0.8 * Math.pow(Pwf_ipr[i]/Pr, 2));
}

// VLP curve (tubing performance)
double[] Pwf_vlp = new double[20];
double[] q_vlp = new double[20];

for (int i = 0; i < 20; i++) {
    q_vlp[i] = i * 3000.0;  // Flow rate
    well.setFlowRate(q_vlp[i], "Sm3/day");
    well.run();
    Pwf_vlp[i] = well.getBottomholePressure("bara");
}

// Find intersection (operating point)

Gas Lift

import neqsim.process.equipment.well.GasLiftWell;

GasLiftWell glWell = new GasLiftWell("GL Producer", reservoirFluid);
glWell.setGasLiftRate(1.0, "MMSm3/day");
glWell.setGasLiftDepth(2500.0, "m");
glWell.run();

double liftedRate = glWell.getOilRate("Sm3/day");

ESP (Electrical Submersible Pump)

import neqsim.process.equipment.well.ESPWell;

ESPWell espWell = new ESPWell("ESP Producer", reservoirFluid);
espWell.setPumpDepth(2800.0, "m");
espWell.setPumpDifferentialPressure(100.0, "bar");
espWell.setPumpEfficiency(0.6);
espWell.run();

double pumpPower = espWell.getPumpPower("kW");
double liftedRate = espWell.getOilRate("Sm3/day");

Example: Production System

ProcessSystem process = new ProcessSystem();

// Reservoir fluid
SystemInterface oil = new SystemPrEos(380.0, 280.0);
oil.addComponent("methane", 0.40);
oil.addComponent("ethane", 0.08);
oil.addComponent("propane", 0.06);
oil.addComponent("n-butane", 0.04);
oil.addComponent("n-pentane", 0.03);
oil.addComponent("n-heptane", 0.15);
oil.addTBPfraction("C10+", 0.20, 200.0/1000.0, 0.85);
oil.addComponent("water", 0.04);
oil.setMixingRule("classic");

// Well 1
SimpleWell well1 = new SimpleWell("P-1", oil.clone());
well1.setReservoirPressure(280.0, "bara");
well1.setWellheadPressure(50.0, "bara");
well1.setFlowRate(3000.0, "Sm3/day");
process.add(well1);

// Choke 1
ChokeValve choke1 = new ChokeValve("XV-1", well1.getOutletStream());
choke1.setOutletPressure(30.0, "bara");
process.add(choke1);

// Well 2
SimpleWell well2 = new SimpleWell("P-2", oil.clone());
well2.setReservoirPressure(260.0, "bara");
well2.setWellheadPressure(45.0, "bara");
well2.setFlowRate(2500.0, "Sm3/day");
process.add(well2);

// Choke 2
ChokeValve choke2 = new ChokeValve("XV-2", well2.getOutletStream());
choke2.setOutletPressure(30.0, "bara");
process.add(choke2);

// Manifold
Mixer manifold = new Mixer("Production Manifold");
manifold.addStream(choke1.getOutletStream());
manifold.addStream(choke2.getOutletStream());
process.add(manifold);

// First stage separator
ThreePhaseSeparator hpSep = new ThreePhaseSeparator("HP Separator", 
    manifold.getOutletStream());
process.add(hpSep);

process.run();

// Results
System.out.println("Total oil rate: " + hpSep.getOilOutStream().getFlowRate("Sm3/day") + " Sm3/day");
System.out.println("Total gas rate: " + hpSep.getGasOutStream().getFlowRate("MSm3/day") + " MSm3/day");
System.out.println("Total water rate: " + hpSep.getWaterOutStream().getFlowRate("m3/day") + " m3/day");

GOR and Water Cut

// Gas-oil ratio
double GOR = hpSep.getGasOutStream().getFlowRate("Sm3/day") / 
    hpSep.getOilOutStream().getFlowRate("Sm3/day");

// Water cut
double waterCut = hpSep.getWaterOutStream().getFlowRate("m3/day") / 
    (hpSep.getOilOutStream().getFlowRate("m3/day") + 
     hpSep.getWaterOutStream().getFlowRate("m3/day"));

System.out.println("GOR: " + GOR + " Sm3/Sm3");
System.out.println("Water cut: " + (waterCut * 100) + " %");

Well Simulation

NeqSim Well Simulation Guide

This guide covers NeqSim's well simulation capabilities, providing functionality for production system modeling including IPR models, VLP correlations, operating point calculation, lift curve generation, and multi-layer commingled production.

Overview

NeqSim provides three main classes for well simulation:

Class Purpose Key Features
WellFlow Inflow Performance (IPR) Vogel, Fetkovich, Backpressure, Table, Multi-layer
TubingPerformance Vertical Lift (VLP) Beggs-Brill, Hagedorn-Brown, Gray, Hasan-Kabir, Duns-Ros
WellSystem Integrated Well Model IPR+VLP coupling, Operating point solver, Lift curves

Table of Contents

  1. Inflow Performance Relationships (IPR)
  2. Vertical Lift Performance (VLP)
  3. Operating Point Calculation
  4. Lift Curve Generation
  5. Multi-Layer Commingled Wells
  6. Temperature Models
  7. Integration with Process Simulation
  8. Complete Examples

Inflow Performance Relationships (IPR)

The WellFlow class models reservoir-to-wellbore inflow using several IPR models.

Available IPR Models

1. Production Index (Darcy Flow)

For single-phase or undersaturated liquid flow:

q = PI × (P_res² - P_wf²)
WellFlow well = new WellFlow("producer");
well.setInletStream(reservoirStream);
well.setWellProductionIndex(1.5e-6); // Sm³/day/bar²
well.setOutletPressure(150.0, "bara");
well.solveFlowFromOutletPressure(true);
well.run();
System.out.println("Flow rate: " + well.getOutletStream().getFlowRate("MSm3/day"));

2. Vogel Equation (1968)

For solution-gas-drive reservoirs below bubble point:

q/q_max = 1 - 0.2(P_wf/P_res) - 0.8(P_wf/P_res)²
// From well test data: 500 Sm³/day at 120 bara, reservoir at 200 bara
well.setVogelIPR(500.0, 120.0, 200.0);
well.setOutletPressure(100.0, "bara");
well.solveFlowFromOutletPressure(true);
well.run();

3. Fetkovich Equation (1973)

Empirical model for gas wells:

q = C × (P_res² - P_wf²)^n
well.setFetkovichIPR(0.012, 0.85);  // C and n coefficients

4. Backpressure Equation

Gas wells with non-Darcy (turbulent) flow:

P_res² - P_wf² = A×q + B×q²

Where A is the Darcy term and B is the non-Darcy (rate-dependent) term.

well.setBackpressureIPR(0.5, 0.001);  // A and B coefficients

5. Table-Driven IPR

For measured IPR curves from well tests:

double[] pressures = {50, 80, 100, 120, 150, 180};  // bara
double[] rates = {2.5, 2.0, 1.6, 1.2, 0.7, 0.2};    // MSm³/day
well.setTableIPR(pressures, rates);

6. Loading IPR from CSV File

Load IPR curves from external files (e.g., from well test analysis software):

// Load IPR curve from CSV file
WellFlow well = new WellFlow("producer");
well.setInletStream(reservoirStream);
well.loadIPRFromFile("path/to/ipr_curve.csv");
well.run();

CSV file format:

Pwf(bara),Rate(MSm3/day)
50,5.2
80,4.1
100,3.2
120,2.4
150,1.5
180,0.8
200,0.2

Vertical Lift Performance (VLP)

The TubingPerformance class calculates pressure drop in tubing using multiphase correlations.

Available VLP Correlations

Correlation Best For Flow Patterns
Beggs-Brill All inclinations All patterns
Hagedorn-Brown Vertical oil wells Slug, bubble
Gray Gas wells Mist, annular
Hasan-Kabir Mechanistic All patterns
Duns-Ros Gas-liquid All patterns

Basic VLP Calculation

import neqsim.process.equipment.pipeline.TubingPerformance;
import neqsim.thermo.system.SystemSrkEos;

// Create tubing model
TubingPerformance tubing = new TubingPerformance("tubing");
tubing.setInletStream(feedStream);
tubing.setDiameter(0.1);          // 100 mm ID
tubing.setLength(3000.0);         // 3000 m TVD
tubing.setInclination(90.0);      // Vertical
tubing.setRoughness(0.00005);     // 50 microns

// Select correlation
tubing.setCorrelationType(TubingPerformance.CorrelationType.BEGGS_BRILL);

// Run calculation
tubing.run();

// Get results
double outletPressure = tubing.getOutletStream().getPressure("bara");
double pressureDrop = tubing.getPressureDrop();

Setting VLP Correlation

// Beggs-Brill (default, all inclinations)
tubing.setCorrelationType(TubingPerformance.CorrelationType.BEGGS_BRILL);

// Hagedorn-Brown (vertical oil wells)
tubing.setCorrelationType(TubingPerformance.CorrelationType.HAGEDORN_BROWN);

// Gray (gas wells)
tubing.setCorrelationType(TubingPerformance.CorrelationType.GRAY);

// Hasan-Kabir (mechanistic)
tubing.setCorrelationType(TubingPerformance.CorrelationType.HASAN_KABIR);

// Duns-Ros (gas-liquid)
tubing.setCorrelationType(TubingPerformance.CorrelationType.DUNS_ROS);

Table-Based VLP

Use pre-calculated or measured VLP curves instead of correlations:

// Set VLP table programmatically
double[] flowRates = {0.5, 1.0, 2.0, 3.0, 4.0, 5.0};  // MSm³/day
double[] bhpValues = {85, 92, 115, 145, 182, 225};    // bara
double whp = 50.0;  // Wellhead pressure (bara)

TubingPerformance tubing = new TubingPerformance("tubing");
tubing.setTableVLP(flowRates, bhpValues, whp);

// Interpolate BHP for a given flow rate
double bhp = tubing.interpolateBHPFromTable(2.5);  // MSm³/day

Loading VLP from CSV File

Load VLP curves from external files (e.g., from PROSPER, Pipesim, or other tools):

TubingPerformance tubing = new TubingPerformance("tubing");
tubing.loadVLPFromFile("path/to/vlp_curve.csv", 50.0);  // WHP = 50 bara

// Use interpolation
double bhp = tubing.interpolateBHPFromTable(3.0);  // Get BHP at 3 MSm³/day

CSV file format:

FlowRate(MSm3/day),BHP(bara)
0.5,85
1.0,92
2.0,115
3.0,145
4.0,182
5.0,225

Operating Point Calculation

The WellSystem class finds the intersection of IPR and VLP curves using an optimized bisection algorithm.

Using WellSystem for Operating Point

import neqsim.process.equipment.reservoir.WellSystem;
import neqsim.process.equipment.stream.Stream;

// Create reservoir stream
Stream reservoirStream = new Stream("reservoir", reservoirFluid);
reservoirStream.setFlowRate(5000.0, "Sm3/day");
reservoirStream.setTemperature(100.0, "C");
reservoirStream.setPressure(280.0, "bara");

// Create well system with inlet stream
WellSystem well = new WellSystem("production_well", reservoirStream);

// Configure IPR model
well.setIPRModel(WellSystem.IPRModel.PRODUCTION_INDEX);
well.setProductionIndex(2.5e-6, "Sm3/day/bar2");

// Configure tubing (VLP)
well.setWellheadPressure(60.0, "bara");
well.setTubingDiameter(4.0, "in");
well.setTubingLength(3000.0, "m");
well.setInclination(85.0);  // degrees from horizontal

// Configure temperature model
well.setBottomHoleTemperature(100.0, "C");
well.setWellheadTemperature(50.0, "C");

// Find operating point
well.run();

// Results
double flowRate = well.getOperatingFlowRate("Sm3/day");
double bhp = well.getBottomHolePressure("bara");
double drawdown = well.getDrawdown("bar");
System.out.println("Operating point: " + flowRate + " Sm³/day at " + bhp + " bara BHP");
System.out.println("Drawdown: " + drawdown + " bar");

IPR Models Available

Model Enum Value Parameters
Production Index PRODUCTION_INDEX setProductionIndex(pi, unit)
Vogel (1968) VOGEL setVogelParameters(qMax, pwfTest, pRes)
Fetkovich (1973) FETKOVICH setFetkovichParameters(C, n, pRes)
Backpressure BACKPRESSURE setBackpressureParameters(A, B)

Operating Point Methods

Method Description
getOperatingFlowRate(unit) Flow rate at IPR-VLP intersection
getBottomHolePressure(unit) Bottom-hole pressure at operating point
getWellheadPressure(unit) Wellhead pressure (target constraint)
getDrawdown(unit) Reservoir pressure - BHP
getOutletStream() Output stream for downstream connection

Lift Curve Generation

Generate IPR and VLP curves for nodal analysis.

Generating VLP Curve (Tubing Performance)

TubingPerformance tubing = new TubingPerformance("tubing");
tubing.setInletStream(gasStream);
tubing.setDiameter(0.1);
tubing.setLength(3000.0);
tubing.setCorrelationType(TubingPerformance.CorrelationType.BEGGS_BRILL);

// Generate VLP curve
double[] flowRates = {0.1, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0};  // MSm³/day
double[][] vlpCurve = tubing.generateVLPCurve(flowRates);

// vlpCurve[0] = flow rates
// vlpCurve[1] = required bottom-hole pressures
for (int i = 0; i < vlpCurve[0].length; i++) {
    System.out.printf("Flow: %.2f MSm³/day, BHP: %.1f bara%n",
        vlpCurve[0][i], vlpCurve[1][i]);
}

Generating IPR Curve

WellSystem well = new WellSystem("producer");
well.setReservoirPressure(280.0, "bara");
well.setProductivityIndex(2.5e-6);
well.setIprModel(WellSystem.IPRModel.PRODUCTION_INDEX);

// Generate IPR curve (flowing BHP vs flow rate)
double minPwf = 50.0;
double maxPwf = 270.0;
int points = 20;
double[][] iprCurve = well.generateIPRCurve(minPwf, maxPwf, points);

// iprCurve[0] = flowing BHP values
// iprCurve[1] = corresponding flow rates

Combined Nodal Analysis Plot Data

WellSystem well = new WellSystem("nodal_analysis");
// ... configure well ...

// Get both curves
double[][] iprCurve = well.generateIPRCurve(50, 270, 20);
double[][] vlpCurve = well.generateVLPCurve(new double[]{0.5, 1.0, 2.0, 3.0, 4.0, 5.0});

// Operating point
well.run();
double opFlow = well.getOperatingFlowRate("MSm3/day");
double opBHP = well.getOperatingBHP("bara");

// Export to CSV or plot
System.out.println("IPR Curve:");
for (int i = 0; i < iprCurve[0].length; i++) {
    System.out.printf("%.1f, %.3f%n", iprCurve[0][i], iprCurve[1][i]);
}
System.out.println("\nVLP Curve:");
for (int i = 0; i < vlpCurve[0].length; i++) {
    System.out.printf("%.3f, %.1f%n", vlpCurve[0][i], vlpCurve[1][i]);
}
System.out.printf("\nOperating Point: %.3f MSm³/day at %.1f bara%n", opFlow, opBHP);

Multi-Layer Commingled Wells

Model wells producing from multiple reservoir layers.

Using WellFlow for Multi-Layer

// Create fluid streams for each layer
SystemInterface layer1Fluid = new SystemSrkEos(80, 200);
layer1Fluid.addComponent("methane", 0.90);
layer1Fluid.addComponent("ethane", 0.07);
layer1Fluid.addComponent("propane", 0.03);
layer1Fluid.setMixingRule("classic");
Stream layer1Stream = new Stream("layer1", layer1Fluid);
layer1Stream.run();

SystemInterface layer2Fluid = new SystemSrkEos(95, 220);
layer2Fluid.addComponent("methane", 0.85);
layer2Fluid.addComponent("ethane", 0.10);
layer2Fluid.addComponent("propane", 0.05);
layer2Fluid.setMixingRule("classic");
Stream layer2Stream = new Stream("layer2", layer2Fluid);
layer2Stream.run();

// Create multi-layer well
WellFlow well = new WellFlow("commingled_well");
well.addLayer("Upper Sand", layer1Stream, 200.0, 1.0e-6);  // 200 bara, PI
well.addLayer("Lower Sand", layer2Stream, 220.0, 1.5e-6);  // 220 bara, PI
well.setOutletPressure(150.0, "bara");  // Common BHP
well.run();

// Get individual layer contributions
double[] layerRates = well.getLayerFlowRates("MSm3/day");
System.out.println("Layer 1 flow: " + layerRates[0] + " MSm³/day");
System.out.println("Layer 2 flow: " + layerRates[1] + " MSm³/day");
System.out.println("Total flow: " + well.getOutletStream().getFlowRate("MSm3/day"));

Using WellSystem for Multi-Layer

WellSystem well = new WellSystem("multi_zone_producer");
well.setWellheadPressure(50.0, "bara");
well.setTubingDiameter(0.1);
well.setTubingLength(3500.0);

// Add layers with different properties
well.addLayer("Zone A", streamA, 250.0, 1.2e-6);
well.addLayer("Zone B", streamB, 280.0, 0.8e-6);
well.addLayer("Zone C", streamC, 265.0, 1.5e-6);

// Find operating point for commingled production
well.run();

double totalFlow = well.getOperatingFlowRate("MSm3/day");
double bhp = well.getOperatingBHP("bara");
double[] zoneFlows = well.getLayerFlowRates("MSm3/day");

Temperature Models

Configure wellbore temperature profile for accurate property calculations.

Available Temperature Models

Model Description Use Case
ISOTHERMAL Constant temperature Quick estimates
LINEAR_GRADIENT Linear geothermal Simple wells
RAMEY Ramey (1962) steady-state Established production
HASAN_KABIR Energy balance Transient, accurate

Setting Temperature Model

// Isothermal (default)
tubing.setTemperatureModel(TubingPerformance.TemperatureModel.ISOTHERMAL);

// Linear gradient (specify surface and BH temperatures)
tubing.setTemperatureModel(TubingPerformance.TemperatureModel.LINEAR_GRADIENT);
tubing.setSurfaceTemperature(25.0);   // °C
tubing.setBottomholeTemperature(90.0);

// Ramey model (needs formation properties)
tubing.setTemperatureModel(TubingPerformance.TemperatureModel.RAMEY);
tubing.setFormationThermalConductivity(2.5);  // W/m·K
tubing.setOverallHeatTransferCoefficient(25.0);

// Hasan-Kabir energy balance
tubing.setTemperatureModel(TubingPerformance.TemperatureModel.HASAN_KABIR);

Ramey Temperature Model

The Ramey (1962) model accounts for:

tubing.setTemperatureModel(TubingPerformance.TemperatureModel.RAMEY);
tubing.setGeothermalGradient(0.03);    // °C/m
tubing.setSurfaceTemperature(15.0);     // °C
tubing.setFormationThermalConductivity(2.5);
tubing.setOverallHeatTransferCoefficient(20.0);
tubing.setProductionTime(365.0);        // days

Integration with Process Simulation

Complete Well + Process System

import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.reservoir.*;
import neqsim.process.equipment.pipeline.TubingPerformance;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.equipment.compressor.Compressor;

// 1. Reservoir and Well
SimpleReservoir reservoir = new SimpleReservoir("reservoir");
reservoir.setReservoirFluid(reservoirFluid);
reservoir.setReservoirPressure(280.0, "bara");
reservoir.setTemperature(85.0, "C");

WellFlow inflow = new WellFlow("IPR");
inflow.setInletStream(reservoir.getOutletStream());
inflow.setWellProductionIndex(2.0e-6);

// 2. Tubing
TubingPerformance tubing = new TubingPerformance("tubing");
tubing.setInletStream(inflow.getOutletStream());
tubing.setDiameter(0.1);
tubing.setLength(3000.0);
tubing.setCorrelationType(TubingPerformance.CorrelationType.BEGGS_BRILL);

// 3. Surface Facilities
Separator separator = new Separator("HP_sep");
separator.setInletStream(tubing.getOutletStream());

Compressor compressor = new Compressor("export_comp");
compressor.setInletStream(separator.getGasOutStream());
compressor.setOutletPressure(150.0, "bara");

// 4. Build Process System
ProcessSystem plant = new ProcessSystem();
plant.add(reservoir);
plant.add(inflow);
plant.add(tubing);
plant.add(separator);
plant.add(compressor);

// 5. Run
plant.run();

// 6. Results
System.out.println("Wellhead pressure: " + tubing.getOutletStream().getPressure("bara"));
System.out.println("Gas export rate: " + compressor.getOutletStream().getFlowRate("MSm3/day"));

Coupling with WellFlowlineNetwork

// Multiple wells feeding a gathering network
WellFlowlineNetwork network = new WellFlowlineNetwork("field_network");

// Add wells
network.addWell(well1);
network.addWell(well2);
network.addWell(well3);

// Set manifold back-pressure
network.setManifoldPressure(40.0, "bara");

// Solve network
network.run();

// Get individual well rates
for (WellFlow well : network.getWells()) {
    System.out.println(well.getName() + ": " + 
        well.getOutletStream().getFlowRate("MSm3/day") + " MSm³/day");
}

Complete Examples

Example 1: Gas Well Analysis

// Rich gas well with Vogel IPR and Beggs-Brill VLP
SystemInterface gas = new SystemSrkEos(85, 250);
gas.addComponent("nitrogen", 0.02);
gas.addComponent("CO2", 0.03);
gas.addComponent("methane", 0.80);
gas.addComponent("ethane", 0.08);
gas.addComponent("propane", 0.04);
gas.addComponent("n-butane", 0.02);
gas.addComponent("n-pentane", 0.01);
gas.setMixingRule("classic");
gas.init(0);

Stream reservoir = new Stream("reservoir", gas);
reservoir.setFlowRate(3.0, "MSm3/day");
reservoir.run();

// Well system
WellSystem well = new WellSystem("gas_producer");
well.setInletStream(reservoir);
well.setReservoirPressure(250.0, "bara");
well.setProductivityIndex(3.0e-6);
well.setWellheadPressure(50.0, "bara");
well.setTubingDiameter(0.088);  // 3.5" tubing
well.setTubingLength(2800.0);
well.setVlpCorrelation(WellSystem.VLPCorrelation.GRAY);
well.run();

System.out.println("=== Gas Well Operating Point ===");
System.out.println("Flow rate: " + well.getOperatingFlowRate("MSm3/day") + " MSm³/day");
System.out.println("BHP: " + well.getOperatingBHP("bara") + " bara");
System.out.println("Drawdown: " + well.getDrawdown() + " bar");

Example 2: Oil Well with Artificial Lift Comparison

// Compare production with different wellhead pressures (simulating lift)
WellSystem well = new WellSystem("oil_producer");
well.setReservoirPressure(180.0, "bara");
well.setProductivityIndex(5.0e-6);
well.setTubingDiameter(0.076);  // 3" tubing
well.setTubingLength(2000.0);
well.setVlpCorrelation(WellSystem.VLPCorrelation.HAGEDORN_BROWN);

System.out.println("Wellhead Pressure | Flow Rate | BHP");
for (double whp = 10; whp <= 50; whp += 10) {
    well.setWellheadPressure(whp, "bara");
    well.run();
    System.out.printf("%17.0f | %9.2f | %6.1f%n",
        whp, well.getOperatingFlowRate("Sm3/day"), well.getOperatingBHP("bara"));
}

Example 3: Multi-Zone Completion

// Three-zone commingled gas well
WellSystem well = new WellSystem("multizone_gas");
well.setWellheadPressure(45.0, "bara");
well.setTubingDiameter(0.1);
well.setTubingLength(3200.0);

// Zone A: Shallow gas, high perm
well.addLayer("Zone_A", shallowStream, 180.0, 4.0e-6);
// Zone B: Middle, moderate perm
well.addLayer("Zone_B", middleStream, 220.0, 2.0e-6);
// Zone C: Deep, low perm but high pressure
well.addLayer("Zone_C", deepStream, 280.0, 0.8e-6);

well.run();

System.out.println("=== Multi-Zone Production ===");
System.out.println("Total rate: " + well.getOperatingFlowRate("MSm3/day") + " MSm³/day");
double[] zoneFlows = well.getLayerFlowRates("MSm3/day");
System.out.println("Zone A: " + zoneFlows[0] + " MSm³/day");
System.out.println("Zone B: " + zoneFlows[1] + " MSm³/day");
System.out.println("Zone C: " + zoneFlows[2] + " MSm³/day");

API Reference Summary

WellFlow (IPR)

Method Description
setWellProductionIndex(pi) Set productivity index
setVogelIPR(qTest, pwfTest, pRes) Configure Vogel IPR
setFetkovichIPR(c, n) Configure Fetkovich IPR
setBackpressureIPR(a, b) Configure Backpressure IPR
setTableIPR(pwf[], rate[]) Set table-driven IPR
addLayer(name, stream, pRes, pi) Add reservoir layer
getLayerFlowRates(unit) Get layer flow contributions

TubingPerformance (VLP)

Method Description
setDiameter(d) Set tubing ID (meters)
setLength(L) Set tubing length (meters)
setInclination(angle) Set inclination (degrees from horizontal)
setRoughness(eps) Set pipe roughness (meters)
setCorrelationType(type) Select VLP correlation
setTemperatureModel(model) Select temperature model
generateVLPCurve(rates) Generate lift curve
getPressureDrop() Get calculated pressure drop

WellSystem (Integrated)

Method Description
setReservoirPressure(p, unit) Set reservoir pressure
setProductivityIndex(pi) Set well PI
setWellheadPressure(p, unit) Set tubing outlet pressure
setIprModel(model) Select IPR model
setVlpCorrelation(corr) Select VLP correlation
getOperatingFlowRate(unit) Get operating flow rate
getOperatingBHP(unit) Get operating BHP
generateIPRCurve(min, max, n) Generate IPR curve
generateVLPCurve(rates) Generate VLP curve

Complete Production System Example

For a comprehensive example demonstrating the full integration of well simulation with downstream processing, see WellToOilStabilizationExample.java.

This example includes:

Using WellSystem in ProcessSystem

The WellSystem class integrates seamlessly with ProcessSystem for building complete production flowsheets. It uses an optimized IPR+VLP solver with:

// Create reservoir and reservoir stream
SimpleReservoir reservoir = new SimpleReservoir("Main Reservoir");
reservoir.setReservoirFluid(reservoirFluid, 1e6, 10.0, 10.0);

Stream reservoirStream = new Stream("Reservoir Stream", reservoir.getReservoirFluid());
reservoirStream.setFlowRate(5000.0, "Sm3/day");
reservoirStream.setTemperature(100.0, "C");
reservoirStream.setPressure(250.0, "bara");

// Create WellSystem with integrated IPR and VLP
WellSystem well = new WellSystem("Producer-1", reservoirStream);

// Configure IPR model (Vogel for solution gas drive)
well.setIPRModel(WellSystem.IPRModel.VOGEL);
well.setVogelParameters(8000.0, 180.0, 250.0); // qMax, testPwf, Pr

// Configure VLP (tubing)
well.setTubingLength(2500.0, "m");
well.setTubingDiameter(4.0, "in");
well.setWellheadPressure(80.0, "bara");
well.setBottomHoleTemperature(100.0, "C");
well.setWellheadTemperature(65.0, "C");

// Connect downstream equipment
PipeBeggsAndBrills flowline = new PipeBeggsAndBrills("Flowline");
flowline.setInletStream(well.getOutletStream());
flowline.setLength(5000.0);
flowline.setDiameter(0.2);

// Build complete ProcessSystem
ProcessSystem process = new ProcessSystem();
process.add(well);          // WellSystem as first equipment
process.add(flowline);
process.add(inletChoke);
process.add(hpSeparator);
// ... add remaining equipment

// Run complete simulation
process.run();

// Access well operating point results
double opRate = well.getOperatingFlowRate("Sm3/day");
double bhp = well.getBottomHolePressure("bara");
double drawdown = well.getDrawdown("bar");

Performance Considerations

The WellSystem solver is optimized for speed:

Feature Description
Simplified VLP Uses hydrostatic + friction correlation instead of full TubingPerformance iteration
Bisection solver Robust convergence in ~15-20 iterations
Single flash per iteration Minimizes thermodynamic calculations
Typical solve time < 1 second for complex fluids

For detailed VLP calculations with full correlation support, use TubingPerformance directly.

VLP Solver Modes

WellSystem supports multiple VLP solver modes for different accuracy/speed tradeoffs:

import neqsim.process.equipment.reservoir.WellSystem.VLPSolverMode;

// Default: Fast simplified solver (hydrostatic + friction)
well.setVLPSolverMode(VLPSolverMode.SIMPLIFIED);

// Traditional correlations (via TubingPerformance)
well.setVLPSolverMode(VLPSolverMode.BEGGS_BRILL);
well.setVLPSolverMode(VLPSolverMode.HAGEDORN_BROWN);
well.setVLPSolverMode(VLPSolverMode.GRAY);
well.setVLPSolverMode(VLPSolverMode.HASAN_KABIR);
well.setVLPSolverMode(VLPSolverMode.DUNS_ROS);

// Advanced multiphase models
well.setVLPSolverMode(VLPSolverMode.DRIFT_FLUX);  // Drift-flux with slip
well.setVLPSolverMode(VLPSolverMode.TWO_FLUID);   // Separate momentum equations
VLP Solver Mode Description Speed Accuracy
SIMPLIFIED Hydrostatic + friction correlation Fastest Approximate
BEGGS_BRILL Beggs & Brill empirical correlation Medium Good for general use
HAGEDORN_BROWN Hagedorn-Brown for vertical wells Medium Good for oil wells
GRAY Gray correlation for gas wells Medium Good for gas wells
HASAN_KABIR Mechanistic model Slow High accuracy
DUNS_ROS Duns & Ros correlation Medium Good for gas-liquid
DRIFT_FLUX Accounts for phase slip velocity Medium Better for high GOR
TWO_FLUID Separate gas/liquid momentum Slowest Highest accuracy

See Also

References

  1. Vogel, J.V. (1968). "Inflow Performance Relationships for Solution-Gas Drive Wells"
  2. Fetkovich, M.J. (1973). "The Isochronal Testing of Oil Wells"
  3. Beggs, H.D. and Brill, J.P. (1973). "A Study of Two-Phase Flow in Inclined Pipes"
  4. Hagedorn, A.R. and Brown, K.E. (1965). "Experimental Study of Pressure Gradients..."
  5. Gray, H.E. (1974). "Vertical Flow Correlation in Gas Wells"
  6. Hasan, A.R. and Kabir, C.S. (2002). "Fluid Flow and Heat Transfer in Wellbores"
  7. Ramey, H.J. (1962). "Wellbore Heat Transmission"

Well & Choke

Well and choke simulation in NeqSim

Overview

NeqSim combines well inflow performance relationships with hydraulic flowline models and production chokes to represent surface networks. A WellFlowlineNetwork assembles wells, optional chokes, and pipelines into branches that are gathered in manifolds for steady-state or transient calculations.

Well inflow models

WellFlow supports several inflow performance relationships that can either solve for outlet pressure from a specified flow or compute flow from a specified outlet pressure:

All models can switch between computing outlet pressure or flow via solveFlowFromOutletPressure(boolean), enabling backpressure solves from downstream network pressure when desired.

Choke representation

Production chokes are modeled as ThrottlingValve instances using IEC 60534 sizing. Chokes can be attached per branch and run in steady-state or transient mode. Valve travel and characterization are captured through the underlying valve model, and choking conditions can be toggled and tuned at the valve level.

Network coupling

WellFlowlineNetwork wires wells and optional chokes into PipeBeggsAndBrills flowlines, collects them in manifolds, and optionally sends the combined stream downstream. The network offers steady-state and transient execution modes, supports target endpoint pressure solving, and can propagate arrival pressures back to well outlets for iterative backpressure calculations.

Pipelines

Pipelines and Pipes

Documentation for pipeline equipment in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.pipeline

Classes:

Class Description
PipeBeggsAndBrills Beggs-Brill correlation
AdiabaticPipe Adiabatic pipe segment
OnePhasePipe Single-phase pipe
TwoPhasePipeLine Two-phase pipeline

For detailed pipe flow modeling, see also Fluid Mechanics.


Pipe Segment

Basic Usage

import neqsim.process.equipment.pipeline.PipeBeggsAndBrills;

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("Pipe", inletStream);
pipe.setLength(1000.0, "m");
pipe.setDiameter(0.3, "m");
pipe.setAngle(0.0);  // Horizontal
pipe.run();

double pressureDrop = pipe.getPressureDrop("bara");
Stream outlet = pipe.getOutletStream();

Geometry

// Length
pipe.setLength(5000.0, "m");

// Inner diameter
pipe.setDiameter(0.254, "m");  // 10 inch
pipe.setInnerDiameter(0.254, "m");

// Wall roughness
pipe.setRoughness(0.0001, "m");

// Elevation change
pipe.setElevationChange(100.0, "m");  // 100m rise
pipe.setAngle(5.7);  // degrees from horizontal

Pipeline

For longer pipelines with multiple segments.

Basic Usage

import neqsim.process.equipment.pipeline.TwoPhasePipeLine;

TwoPhasePipeLine pipeline = new TwoPhasePipeLine("Export Pipeline", inletStream);
pipeline.setLength(50000.0, "m");
pipeline.setDiameter(0.4, "m");
pipeline.setNumberOfNodes(100);
pipeline.run();

// Get profile
double[] pressure = pipeline.getPressureProfile();
double[] temperature = pipeline.getTemperatureProfile();
double[] holdup = pipeline.getLiquidHoldupProfile();

Pressure Drop

Friction Factor Correlations

Method Application
Beggs-Brill Two-phase flow
Moody Single-phase turbulent
Colebrook Single-phase implicit

Beggs-Brill Correlation

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("Pipe", stream);
pipe.setLength(1000.0, "m");
pipe.setDiameter(0.3, "m");
pipe.run();

// Flow regime
String regime = pipe.getFlowRegime();  // Segregated, Intermittent, Distributed

// Liquid holdup
double holdup = pipe.getLiquidHoldup();

Pressure Drop Components

$$\Delta P_{total} = \Delta P_{friction} + \Delta P_{elevation} + \Delta P_{acceleration}$$


Heat Transfer

Adiabatic Pipe

import neqsim.process.equipment.pipeline.AdiabaticPipe;

AdiabaticPipe pipe = new AdiabaticPipe("Adiabatic Pipe", stream);
pipe.setLength(500.0, "m");
pipe.setDiameter(0.2, "m");
pipe.run();

// Temperature remains constant (no heat loss)
double Tin = pipe.getInletStream().getTemperature("C");
double Tout = pipe.getOutletStream().getTemperature("C");

Pipe with Heat Transfer

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("Pipe", stream);
pipe.setLength(1000.0, "m");
pipe.setDiameter(0.3, "m");

// Heat transfer coefficient
pipe.setOverallHeatTransferCoefficient(25.0, "W/m2K");

// Ambient temperature
pipe.setAmbientTemperature(5.0, "C");

pipe.run();

double heatLoss = pipe.getHeatLoss("kW");

Examples

Example 1: Simple Pipe Segment

import neqsim.thermo.system.SystemSrkEos;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.pipeline.PipeBeggsAndBrills;

// Natural gas
SystemSrkEos gas = new SystemSrkEos(303.15, 80.0);
gas.addComponent("methane", 0.90);
gas.addComponent("ethane", 0.07);
gas.addComponent("propane", 0.03);
gas.setMixingRule("classic");

Stream inlet = new Stream("Inlet", gas);
inlet.setFlowRate(1000000.0, "Sm3/day");
inlet.run();

// Pipe
PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("Pipe", inlet);
pipe.setLength(5000.0, "m");
pipe.setDiameter(0.254, "m");  // 10 inch
pipe.setRoughness(0.00005, "m");
pipe.run();

System.out.println("Inlet P: " + inlet.getPressure("bara") + " bara");
System.out.println("Outlet P: " + pipe.getOutletStream().getPressure("bara") + " bara");
System.out.println("ΔP: " + pipe.getPressureDrop("bara") + " bara");
System.out.println("Velocity: " + pipe.getVelocity("m/s") + " m/s");

Example 2: Two-Phase Pipeline

// Wellstream (gas + condensate)
SystemSrkEos wellstream = new SystemSrkEos(350.0, 100.0);
wellstream.addComponent("methane", 0.70);
wellstream.addComponent("ethane", 0.10);
wellstream.addComponent("propane", 0.08);
wellstream.addComponent("n-pentane", 0.05);
wellstream.addComponent("n-heptane", 0.05);
wellstream.addComponent("n-decane", 0.02);
wellstream.setMixingRule("classic");

Stream inlet = new Stream("Wellstream", wellstream);
inlet.setFlowRate(50000.0, "kg/hr");
inlet.run();

// Two-phase pipeline
TwoPhasePipeLine pipeline = new TwoPhasePipeLine("Flowline", inlet);
pipeline.setLength(10000.0, "m");
pipeline.setDiameter(0.2, "m");
pipeline.setNumberOfNodes(50);
pipeline.run();

System.out.println("Inlet: " + inlet.getPressure("bara") + " bara, " + 
                   inlet.getTemperature("C") + " °C");
System.out.println("Outlet: " + pipeline.getOutletStream().getPressure("bara") + " bara, " +
                   pipeline.getOutletStream().getTemperature("C") + " °C");

Example 3: Subsea Pipeline with Heat Loss

// Subsea conditions
SystemSrkEos gas = new SystemSrkEos(350.0, 150.0);
gas.addComponent("methane", 0.92);
gas.addComponent("ethane", 0.05);
gas.addComponent("CO2", 0.02);
gas.addComponent("water", 0.01);
gas.setMixingRule("classic");

Stream inlet = new Stream("Wellhead", gas);
inlet.setFlowRate(5000000.0, "Sm3/day");
inlet.run();

// Subsea pipeline
PipeBeggsAndBrills pipeline = new PipeBeggsAndBrills("Subsea", inlet);
pipeline.setLength(50000.0, "m");
pipeline.setDiameter(0.4, "m");
pipeline.setOverallHeatTransferCoefficient(15.0, "W/m2K");
pipeline.setAmbientTemperature(4.0, "C");  // Seabed temperature
pipeline.run();

System.out.println("Outlet temperature: " + pipeline.getOutletStream().getTemperature("C") + " °C");
System.out.println("Heat loss: " + pipeline.getHeatLoss("MW") + " MW");

// Check hydrate temperature
double hdtTemp = pipeline.getOutletStream().getFluid().getHydrateTemperature();
System.out.println("Hydrate temperature: " + (hdtTemp - 273.15) + " °C");

Example 4: Riser with Elevation

// Gas lift production
Stream production = new Stream("Production", wellFluid);
production.setFlowRate(10000.0, "kg/hr");
production.run();

// Riser (500m water depth)
PipeBeggsAndBrills riser = new PipeBeggsAndBrills("Riser", production);
riser.setLength(550.0, "m");  // Account for catenary
riser.setDiameter(0.2, "m");
riser.setElevationChange(500.0, "m");  // Rise from seabed
riser.run();

System.out.println("Bottom P: " + production.getPressure("bara") + " bara");
System.out.println("Top P: " + riser.getOutletStream().getPressure("bara") + " bara");
System.out.println("Flow regime: " + riser.getFlowRegime());

Beggs & Brill

PipeBeggsAndBrills - Multiphase Pipeline Simulation

Overview

The PipeBeggsAndBrills class implements the Beggs and Brill (1973) empirical correlations for pressure drop and liquid holdup prediction in multiphase pipeline flow. It supports single-phase (gas or liquid) and multiphase (gas-liquid, gas-oil-water) flow in horizontal, inclined, and vertical pipes.

Reference

Beggs, H.D. and Brill, J.P., "A Study of Two-Phase Flow in Inclined Pipes", Journal of Petroleum Technology, May 1973, pp. 607-617. SPE-4007-PA


Table of Contents

  1. Calculation Modes
  2. Flow Regime Determination
  3. Pressure Drop Calculation
  4. Heat Transfer Models
  5. Energy Equation Components
  6. Transient Simulation
  7. Usage Examples
  8. Typical Parameter Values
  9. API Reference

Calculation Modes

The pipeline supports two primary calculation modes:

Mode Description Use Case
CALCULATE_OUTLET_PRESSURE Given inlet conditions and flow rate, calculate outlet pressure Production pipelines with known flow
CALCULATE_FLOW_RATE Given inlet and outlet pressures, calculate flow rate Pipeline capacity analysis

Setting Calculation Mode

// Default: Calculate outlet pressure from known flow
pipe.setCalculationMode(CalculationMode.CALCULATE_OUTLET_PRESSURE);

// Alternative: Calculate flow from known pressures
pipe.setCalculationMode(CalculationMode.CALCULATE_FLOW_RATE);
pipe.setSpecifiedOutletPressure(40.0, "bara");
pipe.setMaxFlowIterations(50);
pipe.setFlowConvergenceTolerance(1e-4);

Flow Regime Determination

The Beggs and Brill correlation classifies two-phase flow into four regimes based on dimensionless parameters.

Dimensionless Parameters

Parameter Symbol Formula
Superficial liquid velocity v_SL Q_L / A
Superficial gas velocity v_SG Q_G / A
Mixture velocity v_m v_SL + v_SG
Input liquid fraction λ_L v_SL / v_m
Froude number Fr v_m² / (g × D)

Flow Regime Boundaries

The flow regime is determined by comparing the Froude number with boundary correlations L1-L4:

L1 = 316 × λL^0.302
L2 = 0.0009252 × λL^(-2.4684)
L3 = 0.1 × λL^(-1.4516)
L4 = 0.5 × λL^(-6.738)

Flow Regime Classification

Regime Conditions Description
SEGREGATED λL < 0.01 and Fr < L1, or λL ≥ 0.01 and Fr < L2 Stratified, wavy, or annular flow
INTERMITTENT λL < 0.4 and L3 < Fr ≤ L1, or λL ≥ 0.4 and L3 < Fr ≤ L4 Slug or plug flow
DISTRIBUTED λL < 0.4 and Fr ≥ L4, or λL ≥ 0.4 and Fr > L4 Bubble or mist flow
TRANSITION L2 < Fr < L3 Interpolation zone
SINGLE_PHASE Only gas or only liquid No two-phase effects

Flow Regime Map

                    Fr (Froude Number)
                    ↑
          1000 ─────┼─────────────────────────
                    │     DISTRIBUTED
                    │
           100 ─────┼───────────────┬─────────
                    │  INTERMITTENT │
            10 ─────┼───────────────┤
                    │   TRANSITION  │
             1 ─────┼───────────────┴─────────
                    │    SEGREGATED
           0.1 ─────┼─────────────────────────
                    └────┬────┬────┬────┬────→ λL
                       0.01  0.1  0.4  1.0

Pressure Drop Calculation

Total pressure drop consists of three components:

ΔP_total = ΔP_friction + ΔP_hydrostatic + ΔP_acceleration

Hydrostatic Pressure Drop

ΔP_hydrostatic = ρ_m × g × Δh

where:
  ρ_m = ρ_L × E_L + ρ_G × (1 - E_L)  [mixture density]
  E_L = liquid holdup (volume fraction)
  Δh = elevation change

Liquid Holdup Correlations

Flow Regime Horizontal Holdup (E_L0)
Segregated E_L0 = 0.98 × λL^0.4846 / Fr^0.0868
Intermittent E_L0 = 0.845 × λL^0.5351 / Fr^0.0173
Distributed E_L0 = 1.065 × λL^0.5824 / Fr^0.0609
Transition Weighted average of Segregated and Intermittent

Inclination Correction:

E_L = E_L0 × B_θ

B_θ = 1 + β × [sin(1.8θ) - (1/3)sin³(1.8θ)]

where β depends on flow regime and inclination direction (uphill vs downhill).

Friction Pressure Drop

ΔP_friction = f_tp × (L/D) × (ρ_ns × v_m²) / 2

where:
  f_tp = f × exp(S)     [two-phase friction factor]
  ρ_ns = no-slip density
  S = slip correction factor

Friction Factor Correlations:

Flow Regime Friction Factor
Laminar (Re < 2300) f = 64/Re
Transition (2300-4000) Linear interpolation
Turbulent (Re > 4000) Haaland: f = [1/(-1.8 log((ε/D/3.7)^1.11 + 6.9/Re))]²

Slip Correction Factor (S):

y = λL / E_L²

For 1 < y < 1.2:  S = ln(2.2y - 1.2)
Otherwise:       S = ln(y) / [-0.0523 + 3.18ln(y) - 0.872ln²(y) + 0.01853ln⁴(y)]

Heat Transfer Models

Heat Transfer Modes

Mode Description When to Use
ADIABATIC No heat transfer (Q=0) Well-insulated pipelines, short pipes
ISOTHERMAL Constant temperature Slow flow, thermal equilibrium
SPECIFIED_U User-specified U-value Known overall heat transfer coefficient
ESTIMATED_INNER_H Calculate inner h from flow Quick estimate, inner resistance dominant
DETAILED_U Full thermal resistance calculation Subsea pipelines, insulated pipes

NTU-Effectiveness Method

Heat transfer uses the analytical NTU (Number of Transfer Units) method:

NTU = U × A / (ṁ × Cp)

T_out = T_wall + (T_in - T_wall) × exp(-NTU)

This provides an exact solution for constant wall temperature boundary conditions.

Inner Heat Transfer Coefficient

Flow Regime Nusselt Number
Laminar (Re < 2300) Nu = 3.66
Transition (2300-3000) Linear interpolation
Turbulent (Re > 3000) Gnielinski: Nu = (f/8)(Re-1000)Pr / [1 + 12.7(f/8)^0.5(Pr^(2/3)-1)]
Two-phase Shah/Martinelli enhancement applied
h_inner = Nu × k / D

Overall U-Value (DETAILED_U Mode)

The overall heat transfer coefficient includes thermal resistances in series:

1/U = 1/h_inner + R_wall + R_insulation + 1/h_outer

where:
  R_wall = r_i × ln(r_o/r_i) / k_wall       [pipe wall]
  R_insulation = r_i × ln(r_ins/r_o) / k_ins [insulation layer]

Example calculation for 6" pipe with 50mm insulation:

Given:
  D_inner = 0.1524 m (6 inch)
  Wall thickness = 10 mm
  Insulation thickness = 50 mm
  k_steel = 45 W/(m·K)
  k_insulation = 0.04 W/(m·K)
  h_inner = 500 W/(m²·K)
  h_outer = 500 W/(m²·K) (seawater)

Calculate:
  r_i = 0.0762 m
  r_o = 0.0862 m  
  r_ins = 0.1362 m

  1/h_inner = 0.002 m²K/W
  R_wall = 0.0762 × ln(0.0862/0.0762) / 45 = 0.0002 m²K/W
  R_ins = 0.0762 × ln(0.1362/0.0862) / 0.04 = 0.87 m²K/W
  1/h_outer = 0.002 m²K/W

  1/U = 0.002 + 0.0002 + 0.87 + 0.002 = 0.874 m²K/W
  U = 1.14 W/(m²·K)

Energy Equation Components

The energy balance can include three optional components:

1. Wall Heat Transfer

Heat exchange with surroundings using the NTU-effectiveness method:

Q_wall = ṁ × Cp × (T_out - T_in)

2. Joule-Thomson Effect

Temperature change due to gas expansion:

ΔT_JT = -μ_JT × ΔP

where μ_JT is the Joule-Thomson coefficient

Typical Joule-Thomson Coefficients:

Fluid μ_JT [K/bar] μ_JT [K/Pa]
Methane ~0.4 ~4×10⁻⁶
Natural gas 0.3-0.5 3-5×10⁻⁶
CO₂ ~1.0 ~10⁻⁵
Nitrogen ~0.25 ~2.5×10⁻⁶

3. Friction Heating

Viscous dissipation adds energy to the fluid:

Q_friction = ΔP_friction × V̇

where V̇ is the volumetric flow rate

Note: Only the friction component of pressure drop is used (not hydrostatic), as hydrostatic pressure change is reversible work.

Enabling Energy Components

// Enable Joule-Thomson effect
pipe.setIncludeJouleThomsonEffect(true);

// Enable friction heating
pipe.setIncludeFrictionHeating(true);

Transient Simulation

The class supports time-dependent simulation using the runTransient() method.

Conservation Equations

The transient solver uses explicit finite difference for:

  1. Mass conservation:

    ∂ρ/∂t + ∂(ρv)/∂x = 0
    
  2. Momentum conservation:

    ∂(ρv)/∂t + ∂(ρv²)/∂x = -∂P/∂x - τ_wall - ρg sin(θ)
    
  3. Energy conservation:

    ∂(ρe)/∂t + ∂(ρvh)/∂x = Q_wall + Q_friction
    

Usage

// Initialize transient simulation
pipe.initTransientSimulation();

// Run time steps
double dt = 1.0;  // seconds
for (int step = 0; step < 1000; step++) {
    pipe.runTransient(dt);

    // Access profiles
    double outletT = pipe.getTransientTemperatureProfile().get(
        pipe.getTransientTemperatureProfile().size() - 1);
}

Stability (CFL Condition)

For numerical stability, the time step must satisfy:

Δt ≤ Δx / (v + c)

where:
  Δx = segment length
  v = flow velocity
  c = speed of sound

Usage Examples

Example 1: Basic Horizontal Pipeline

import neqsim.process.equipment.pipeline.PipeBeggsAndBrills;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create fluid system
SystemInterface fluid = new SystemSrkEos(303.15, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.setMixingRule("classic");

// Create inlet stream
Stream inlet = new Stream("inlet", fluid);
inlet.setFlowRate(50000, "kg/hr");
inlet.run();

// Create pipeline
PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("pipeline", inlet);
pipe.setDiameter(0.2032);       // 8 inch
pipe.setLength(10000.0);        // 10 km
pipe.setElevation(0.0);         // horizontal
pipe.setNumberOfIncrements(20);
pipe.setPipeWallRoughness(4.5e-5); // Commercial steel

pipe.run();

// Results
System.out.println("Inlet pressure:  " + inlet.getPressure("bara") + " bara");
System.out.println("Outlet pressure: " + pipe.getOutletStream().getPressure("bara") + " bara");
System.out.println("Pressure drop:   " + pipe.getPressureDrop() + " bar");
System.out.println("Flow regime:     " + pipe.getFlowRegime());
System.out.println("Liquid holdup:   " + pipe.getLiquidHoldup());

Example 2: Subsea Pipeline with Heat Loss

// Hot production fluid in cold seawater
PipeBeggsAndBrills subseaPipe = new PipeBeggsAndBrills("subsea", hotStream);
subseaPipe.setDiameter(0.1524);                    // 6 inch
subseaPipe.setLength(15000.0);                     // 15 km
subseaPipe.setElevation(0.0);                      // horizontal

// Heat transfer settings
subseaPipe.setConstantSurfaceTemperature(4.0, "C"); // Deep sea temperature
subseaPipe.setHeatTransferCoefficient(15.0);        // W/(m²·K) with insulation

subseaPipe.run();

System.out.println("Inlet temperature:  " + hotStream.getTemperature("C") + " °C");
System.out.println("Outlet temperature: " + subseaPipe.getOutletStream().getTemperature("C") + " °C");

Example 3: Detailed Heat Transfer with Insulation

PipeBeggsAndBrills insulatedPipe = new PipeBeggsAndBrills("insulated", feedStream);
insulatedPipe.setDiameter(0.2032);                  // 8 inch
insulatedPipe.setThickness(0.0127);                 // 0.5 inch wall
insulatedPipe.setLength(20000.0);                   // 20 km

// Detailed thermal model
insulatedPipe.setConstantSurfaceTemperature(5.0, "C");
insulatedPipe.setOuterHeatTransferCoefficient(500.0);   // Seawater
insulatedPipe.setPipeWallThermalConductivity(45.0);     // Carbon steel
insulatedPipe.setInsulation(0.075, 0.04);               // 75mm PU foam
insulatedPipe.setHeatTransferMode(HeatTransferMode.DETAILED_U);

insulatedPipe.run();

Example 4: Vertical Riser

// Production riser from seabed to platform
PipeBeggsAndBrills riser = new PipeBeggsAndBrills("riser", subseaStream);
riser.setDiameter(0.1524);                          // 6 inch
riser.setLength(500.0);                             // 500 m length
riser.setElevation(500.0);                          // 500 m rise
riser.setAngle(90.0);                               // Vertical
riser.setNumberOfIncrements(50);

riser.run();

// Hydrostatic pressure difference
double hydrostaticHead = riser.getSegmentPressure(0) - 
                         riser.getSegmentPressure(riser.getNumberOfIncrements());
System.out.println("Hydrostatic head: " + hydrostaticHead + " bar");

Example 5: Adiabatic Pipeline with JT Effect

// High-pressure gas letdown - significant JT cooling expected
PipeBeggsAndBrills gasLine = new PipeBeggsAndBrills("gas_line", hpGasStream);
gasLine.setDiameter(0.1016);                        // 4 inch
gasLine.setLength(5000.0);                          // 5 km
gasLine.setElevation(0.0);

// Adiabatic with JT effect
gasLine.setHeatTransferMode(HeatTransferMode.ADIABATIC);
gasLine.setIncludeJouleThomsonEffect(true);

gasLine.run();

// For 20 bar pressure drop in natural gas:
// Expected JT cooling: ~8-10 K
double tempDrop = hpGasStream.getTemperature("C") - 
                  gasLine.getOutletStream().getTemperature("C");
System.out.println("Temperature drop from JT: " + tempDrop + " °C");

Example 6: Flow Rate Calculation

// Determine pipeline capacity for given inlet/outlet pressures
PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("flowline", feedStream);
pipe.setDiameter(0.2032);
pipe.setLength(25000.0);
pipe.setElevation(-50.0);                           // Slight descent

// Calculate flow rate mode
pipe.setCalculationMode(CalculationMode.CALCULATE_FLOW_RATE);
pipe.setSpecifiedOutletPressure(35.0, "bara");
pipe.setMaxFlowIterations(100);
pipe.setFlowConvergenceTolerance(1e-5);

pipe.run();

System.out.println("Calculated flow rate: " + 
    pipe.getOutletStream().getFlowRate("kg/hr") + " kg/hr");

Example 7: Multiphase Three-Phase Flow

// Gas-oil-water flow
SystemInterface fluid = new SystemSrkEos(323.15, 50.0);
fluid.addComponent("methane", 0.70);
fluid.addComponent("n-heptane", 0.20);
fluid.addComponent("water", 0.10);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

Stream threePhase = new Stream("three_phase", fluid);
threePhase.setFlowRate(100000, "kg/hr");
threePhase.run();

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("flowline", threePhase);
pipe.setDiameter(0.3048);                           // 12 inch
pipe.setLength(10000.0);
pipe.setElevation(0.0);
pipe.setNumberOfIncrements(50);

pipe.run();

// Check water accumulation
System.out.println("Average liquid holdup: " + pipe.getLiquidHoldup());

Typical Parameter Values

Heat Transfer Coefficients

Environment h [W/(m²·K)]
Still air (natural convection) 5-25
Forced air (5 m/s) 25-50
Forced air (30 m/s) 100-250
Still water 100-500
Flowing seawater (1 m/s) 500-1000
Buried in wet soil 1-5
Buried in dry soil 0.5-2

Thermal Conductivities

Material k [W/(m·K)]
Carbon steel 45-50
Stainless steel 15-20
Copper 380-400
Mineral wool insulation 0.03-0.05
Polyurethane foam 0.02-0.03
Polypropylene 0.1-0.2
Concrete coating 1.0-1.5

Pipe Roughness

Material ε [mm]
Commercial steel 0.045
Stainless steel 0.015
Cast iron 0.25
Galvanized steel 0.15
PVC/Plastic 0.0015
Concrete 0.3-3.0

API Reference

Key Methods

Method Description
setDiameter(double d) Set inner diameter [m]
setLength(double L) Set pipe length [m]
setElevation(double h) Set elevation change [m]
setAngle(double θ) Set pipe inclination [degrees]
setNumberOfIncrements(int n) Set number of calculation segments
setPipeWallRoughness(double ε) Set wall roughness [m]
setHeatTransferMode(HeatTransferMode mode) Set heat transfer calculation mode
setHeatTransferCoefficient(double U) Set overall U-value [W/(m²·K)]
setConstantSurfaceTemperature(double T, String unit) Set ambient/wall temperature
setIncludeJouleThomsonEffect(boolean) Enable/disable JT cooling
setIncludeFrictionHeating(boolean) Enable/disable friction heating
run() Execute steady-state simulation
runTransient(double dt) Execute one transient time step

Output Methods

Method Returns
getPressureDrop() Total pressure drop [bar]
getFlowRegime() Current flow regime
getLiquidHoldup() Average liquid holdup [-]
getOutletStream() Outlet stream with results
getPressureProfile() List of pressures along pipe
getTemperatureProfile() List of temperatures along pipe
getLiquidHoldupProfile() List of holdups along pipe

Validation

The Beggs and Brill correlation has been validated against:

Expected accuracy:


See Also

Networks

Pipeline Networks

Documentation for pipeline network modeling in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.network

The network package provides classes for modeling interconnected pipeline systems where multiple pipelines converge at manifolds. This is essential for:

Class Description
PipeFlowNetwork Compositional network with TDMA solver
WellFlowlineNetwork Network using Beggs-Brill correlations

PipeFlowNetwork

The PipeFlowNetwork class models pipeline networks where multiple pipelines converge to manifolds, using compositional OnePhasePipeLine with TDMA (Tri-Diagonal Matrix Algorithm) solvers.

Class Hierarchy

ProcessEquipmentBaseClass
└── PipeFlowNetwork
    ├── contains: ManifoldNode[]
    └── contains: PipelineSegment[]

Key Features

Architecture

The network is modeled as a directed graph:

        [Feed 1]             [Feed 2]
           │                    │
           ▼                    ▼
      ┌─────────┐         ┌─────────┐
      │ Pipe 1  │         │ Pipe 2  │
      └────┬────┘         └────┬────┘
           │                   │
           ▼                   ▼
        ┌─────────────────────────┐
        │    Manifold A (Mixer)   │
        └───────────┬─────────────┘
                    │
                    ▼
             ┌─────────────┐
             │ Export Pipe │
             └──────┬──────┘
                    │
                    ▼
        ┌─────────────────────────┐
        │   End Manifold (Mixer)  │
        └───────────┬─────────────┘
                    │
                    ▼
              [Outlet Stream]

Inner Classes

PipelineSegment

Represents a pipeline in the network:

public static class PipelineSegment {
    String name;
    OnePhasePipeLine pipeline;
    String fromManifold;  // null for inlet pipes
    String toManifold;

    boolean isInletPipeline();  // true if fromManifold is null
}

ManifoldNode

Represents a junction/manifold in the network:

public static class ManifoldNode {
    String name;
    Mixer mixer;
    List<PipelineSegment> inboundPipelines;
    PipelineSegment outboundPipeline;

    boolean isTerminal();  // true if no outbound pipeline
}

Constructors

import neqsim.process.equipment.network.PipeFlowNetwork;

// Basic constructor
PipeFlowNetwork network = new PipeFlowNetwork("gathering network");

Key Methods

Method Description
createManifold(String name) Create a new manifold node
addInletPipeline(name, feed, toManifold, length, diameter, nodes) Add inlet pipeline
connectManifolds(from, to, name, length, diameter, nodes) Connect two manifolds
run() Execute network simulation
getOutletStream() Get the network outlet stream
getPipeline(String name) Get a specific pipeline by name
getManifold(String name) Get a manifold node by name

Usage Examples

Gas Gathering Network

import neqsim.process.equipment.network.PipeFlowNetwork;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create feed streams from wells
SystemInterface gas1 = new SystemSrkEos(320.0, 150.0);
gas1.addComponent("methane", 0.92);
gas1.addComponent("ethane", 0.05);
gas1.addComponent("propane", 0.03);
gas1.setMixingRule("classic");

Stream feed1 = new Stream("well-1", gas1);
feed1.setFlowRate(5.0, "MSm3/day");
feed1.run();

SystemInterface gas2 = new SystemSrkEos(315.0, 145.0);
gas2.addComponent("methane", 0.90);
gas2.addComponent("ethane", 0.06);
gas2.addComponent("propane", 0.04);
gas2.setMixingRule("classic");

Stream feed2 = new Stream("well-2", gas2);
feed2.setFlowRate(3.0, "MSm3/day");
feed2.run();

// Create network
PipeFlowNetwork network = new PipeFlowNetwork("gathering system");

// Create manifolds
String manifoldA = network.createManifold("manifold A");
String endManifold = network.createManifold("end manifold");

// Add inlet pipelines to manifold A
// Parameters: name, feedStream, toManifold, length(m), diameter(m), nodes
network.addInletPipeline("pipe1", feed1, manifoldA, 5000.0, 0.3, 50);
network.addInletPipeline("pipe2", feed2, manifoldA, 4500.0, 0.25, 45);

// Connect manifold A to end manifold with export pipeline
network.connectManifolds(manifoldA, endManifold, "export", 15000.0, 0.5, 100);

// Run steady-state simulation
network.run();

// Access results
StreamInterface outlet = network.getOutletStream();
System.out.println("Outlet pressure: " + outlet.getPressure("bara") + " bara");
System.out.println("Outlet temperature: " + outlet.getTemperature("C") + " °C");
System.out.println("Total flow: " + outlet.getFlowRate("MSm3/day") + " MSm3/day");

Multi-Tier Network

// Create complex gathering network
PipeFlowNetwork network = new PipeFlowNetwork("field gathering");

// Create manifold hierarchy
String tier1ManifoldA = network.createManifold("tier1-A");
String tier1ManifoldB = network.createManifold("tier1-B");
String tier2Manifold = network.createManifold("tier2");
String centralManifold = network.createManifold("central");

// Tier 1: Wells to first-level manifolds
network.addInletPipeline("well1", well1Stream, tier1ManifoldA, 2000.0, 0.2, 30);
network.addInletPipeline("well2", well2Stream, tier1ManifoldA, 2500.0, 0.2, 30);
network.addInletPipeline("well3", well3Stream, tier1ManifoldB, 1800.0, 0.2, 30);
network.addInletPipeline("well4", well4Stream, tier1ManifoldB, 3000.0, 0.2, 30);

// Tier 2: First-level to second-level
network.connectManifolds(tier1ManifoldA, tier2Manifold, "line-A", 5000.0, 0.35, 50);
network.connectManifolds(tier1ManifoldB, tier2Manifold, "line-B", 4500.0, 0.35, 50);

// Tier 3: Second-level to central
network.connectManifolds(tier2Manifold, centralManifold, "export", 10000.0, 0.5, 100);

// Run
network.run();

Accessing Pipeline Results

// Run network
network.run();

// Access specific pipeline
PipeFlowNetwork.PipelineSegment segment = network.getPipelineSegment("pipe1");
OnePhasePipeLine pipeline = segment.getPipeline();

// Get pressure profile
double[] pressures = pipeline.getPressureProfile();
double[] positions = pipeline.getPositionProfile();

for (int i = 0; i < positions.length; i++) {
    System.out.printf("Position: %.1f m, Pressure: %.2f bara%n", 
                      positions[i], pressures[i]);
}

WellFlowlineNetwork

A simplified network model using Beggs-Brill correlations instead of the full TDMA solver.

Key Differences from PipeFlowNetwork

Aspect PipeFlowNetwork WellFlowlineNetwork
Flow Model TDMA solver Beggs-Brill correlation
Compositional Full Simplified
Speed Slower Faster
Accuracy Higher Lower
Use Case Detailed analysis Quick screening

Advanced Features

Transient Simulation

PipeFlowNetwork network = new PipeFlowNetwork("transient network");
// ... setup network ...

// Configure transient parameters
network.setTransientMode(true);
network.setTimeStep(1.0);  // seconds

// Run transient for 1 hour
for (double t = 0; t < 3600; t += 1.0) {
    // Update boundary conditions if needed
    feed1.setFlowRate(5.0 + 0.5 * Math.sin(t / 600), "MSm3/day");
    feed1.run();

    network.run();

    if (t % 60 == 0) {
        System.out.printf("t=%.0f s, P_outlet=%.2f bara%n", 
                          t, network.getOutletStream().getPressure("bara"));
    }
}

Heat Transfer

// Configure pipeline heat transfer
OnePhasePipeLine pipeline = network.getPipelineSegment("export").getPipeline();
pipeline.setOuterTemperature(278.15);  // Ambient temperature (K)
pipeline.setOverallHeatTransferCoefficient(5.0);  // W/m²/K

Design Considerations

Network Topology

Pressure Matching

Computational Performance


Reservoirs

Reservoir Modeling

Documentation for reservoir modeling equipment in NeqSim, enabling coupled reservoir-process simulations.

Table of Contents


Overview

Location: neqsim.process.equipment.reservoir

The reservoir package provides classes for simplified reservoir modeling that can be integrated with process simulations. This enables:

Class Description
SimpleReservoir Material balance reservoir model
Well Well connection to reservoir
WellFlow Well flow calculations
WellSystem System of connected wells
ReservoirCVDsim Constant volume depletion simulation

SimpleReservoir

The SimpleReservoir class provides a material balance approach to reservoir modeling with support for multiple producers and injectors.

Class Hierarchy

ProcessEquipmentBaseClass
└── SimpleReservoir
    ├── contains: Well[] gasProducers
    ├── contains: Well[] oilProducers
    ├── contains: Well[] waterProducers
    ├── contains: Well[] gasInjectors
    └── contains: Well[] waterInjectors

Key Features

Constructor

import neqsim.process.equipment.reservoir.SimpleReservoir;
import neqsim.thermo.system.SystemSrkEos;

// Create reservoir fluid
SystemInterface reservoirFluid = new SystemSrkEos(373.0, 250.0);  // 100°C, 250 bar
reservoirFluid.addComponent("methane", 50.0);
reservoirFluid.addComponent("ethane", 5.0);
reservoirFluid.addComponent("propane", 3.0);
reservoirFluid.addComponent("n-heptane", 40.0);
reservoirFluid.addComponent("water", 2.0);
reservoirFluid.setMixingRule("classic");

// Create reservoir
SimpleReservoir reservoir = new SimpleReservoir("North Sea Field");
reservoir.setReservoirFluid(reservoirFluid);
reservoir.setGasVolume(1.0e9, "Sm3");      // Initial gas volume
reservoir.setOilVolume(100.0e6, "Sm3");     // Initial oil volume
reservoir.setWaterVolume(50.0e6, "m3");     // Initial water volume

Key Methods

Method Description
setReservoirFluid(SystemInterface) Set reservoir fluid composition
setGasVolume(double, String) Set initial gas volume
setOilVolume(double, String) Set initial oil volume
setWaterVolume(double, String) Set initial water volume
addGasProducer(String) Add gas production well
addOilProducer(String) Add oil production well
addWaterProducer(String) Add water production well
addGasInjector(String) Add gas injection well
addWaterInjector(String) Add water injection well
getGasInPlace(String) Get remaining gas in place
getOilInPlace(String) Get remaining oil in place
getPressure() Get current reservoir pressure
run() Execute material balance update

Adding Wells

// Add production wells
StreamInterface gasStream = reservoir.addGasProducer("Well-G1");
StreamInterface oilStream = reservoir.addOilProducer("Well-O1");
StreamInterface waterStream = reservoir.addWaterProducer("Well-W1");

// Add injection wells
StreamInterface gasInjStream = reservoir.addGasInjector("Well-GI1");
StreamInterface waterInjStream = reservoir.addWaterInjector("Well-WI1");

// Set production rates
reservoir.getGasProducer(0).getStream().setFlowRate(1.0, "MSm3/day");
reservoir.getOilProducer(0).getStream().setFlowRate(1000, "m3/day");

Retrieving Wells

// By index
Well gasWell = reservoir.getGasProducer(0);
Well oilWell = reservoir.getOilProducer(0);

// By name
Well namedWell = reservoir.getOilProducer("Well-O1");

// Access well stream
StreamInterface wellStream = gasWell.getStream();

Well Classes

Well

Represents a well connection to the reservoir.

import neqsim.process.equipment.reservoir.Well;

Well well = new Well("Production Well 1");
well.setStream(productionStream);

WellFlow

Provides well inflow performance relationship (IPR) calculations.

import neqsim.process.equipment.reservoir.WellFlow;

WellFlow wellFlow = new WellFlow("Well Inflow");
wellFlow.setReservoirPressure(250.0, "bara");
wellFlow.setWellheadPressure(80.0, "bara");
wellFlow.setProductivityIndex(10.0);  // m³/day/bar

Usage Examples

Basic Depletion Study

import neqsim.process.equipment.reservoir.SimpleReservoir;
import neqsim.thermo.system.SystemSrkEos;

// Create reservoir
SystemInterface fluid = new SystemSrkEos(373.0, 250.0);
fluid.addComponent("methane", 70.0);
fluid.addComponent("n-heptane", 30.0);
fluid.setMixingRule("classic");

SimpleReservoir reservoir = new SimpleReservoir("Test Reservoir");
reservoir.setReservoirFluid(fluid);
reservoir.setGasVolume(5.0e9, "Sm3");   // 5 GSm³
reservoir.setOilVolume(50.0e6, "Sm3");  // 50 MSm³

// Add producer
StreamInterface gasOut = reservoir.addGasProducer("GP-1");
reservoir.getGasProducer(0).getStream().setFlowRate(5.0, "MSm3/day");

// Simulate 10 years of production
double dt = 1.0;  // day
for (int day = 0; day < 3650; day++) {
    reservoir.run();

    // Log every month
    if (day % 30 == 0) {
        double year = day / 365.0;
        double gasInPlace = reservoir.getGasInPlace("GSm3");
        double pressure = reservoir.getPressure();

        System.out.printf("Year %.1f: GIP=%.2f GSm³, P=%.1f bara%n", 
                          year, gasInPlace, pressure);
    }
}

Oil Field with Water Injection

// Create oil reservoir
SimpleReservoir oilField = new SimpleReservoir("Oil Field");
oilField.setReservoirFluid(oilFluid);
oilField.setOilVolume(200.0e6, "Sm3");
oilField.setGasVolume(20.0e9, "Sm3");
oilField.setWaterVolume(100.0e6, "m3");
oilField.setLowPressureLimit(100.0);  // Minimum reservoir pressure

// Production wells
StreamInterface oil1 = oilField.addOilProducer("OP-1");
StreamInterface oil2 = oilField.addOilProducer("OP-2");
StreamInterface gas1 = oilField.addGasProducer("GP-1");

// Injection well for pressure maintenance
StreamInterface waterInj = oilField.addWaterInjector("WI-1");

// Set rates
oilField.getOilProducer(0).getStream().setFlowRate(5000, "bbl/day");
oilField.getOilProducer(1).getStream().setFlowRate(4000, "bbl/day");
oilField.getGasProducer(0).getStream().setFlowRate(1.0, "MSm3/day");

// Water injection for voidage replacement
oilField.getWaterInjector(0).getStream().setFlowRate(6000, "bbl/day");

// Run simulation
oilField.run();

Integrated Reservoir-Process System

import neqsim.process.processmodel.ProcessSystem;

// Create reservoir
SimpleReservoir reservoir = new SimpleReservoir("Field A");
reservoir.setReservoirFluid(reservoirFluid);
reservoir.setOilVolume(100.0e6, "Sm3");

// Add producer
StreamInterface wellStream = reservoir.addOilProducer("OP-1");

// Create process system
ProcessSystem facility = new ProcessSystem("FPSO");

// Reservoir production
reservoir.getOilProducer(0).getStream().setFlowRate(10000, "bbl/day");
facility.add(reservoir);

// Production separator
ThreePhaseSeparator separator = new ThreePhaseSeparator("HP Separator");
separator.setInletStream(wellStream);
facility.add(separator);

// Gas processing
Stream gasStream = separator.getGasOutStream();
Compressor compressor = new Compressor("Export Compressor", gasStream);
compressor.setOutletPressure(200.0, "bara");
facility.add(compressor);

// Run integrated simulation
facility.run();

// Check OOIP recovery
double initialOOIP = 100.0e6;
double remainingOil = reservoir.getOilInPlace("MSm3") * 1e6;
double recovery = (initialOOIP - remainingOil) / initialOOIP * 100;
System.out.println("Oil recovery: " + recovery + " %");

Material Balance

The reservoir uses a simplified material balance approach:

Gas Reservoir

G_p = G_i × (1 - P/P_i × Z_i/Z)

Where:

Oil Reservoir with Gas Cap

Includes gas cap expansion, oil zone compressibility, and water influx terms.

Volume Tracking

// Get original volumes
double OOIP = reservoir.getOOIP();  // Original oil in place
double OGIP = reservoir.getOGIP();  // Original gas in place

// Get current in-place volumes
double currentGIP = reservoir.getGasInPlace("GSm3");
double currentOIP = reservoir.getOilInPlace("MSm3");

// Get cumulative production
double cumGas = reservoir.getGasProductionTotal();
double cumOil = reservoir.getOilProductionTotal();

Integration with Process Systems

The reservoir can be integrated with surface facilities:

┌─────────────────┐
│   RESERVOIR     │
│                 │
│  ┌───────────┐  │
│  │  Gas Cap  │  │
│  └─────┬─────┘  │
│        │        │
│  ┌─────┴─────┐  │      ┌─────────────┐      ┌──────────┐
│  │ Oil Zone  │──┼──────│  Separator  │──────│ Pipeline │
│  └─────┬─────┘  │      └─────────────┘      └──────────┘
│        │        │
│  ┌─────┴─────┐  │
│  │   Water   │◄─┼───── Water Injection
│  └───────────┘  │
│                 │
└─────────────────┘

Subsea Systems

Subsea Production Systems

Documentation for subsea production equipment in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.subsea

The subsea package provides equipment for modeling subsea production systems, including:

Class Description
SubseaWell Combined well and tubing model
SimpleFlowLine Subsea flowline/riser model

SubseaWell

The SubseaWell class models a subsea production well including the wellbore/tubing flow using an adiabatic two-phase pipe model.

Class Hierarchy

TwoPortEquipment
└── SubseaWell
    └── contains: AdiabaticTwoPhasePipe (tubing)

Key Features

Constructor

import neqsim.process.equipment.subsea.SubseaWell;
import neqsim.process.equipment.stream.StreamInterface;

// Create subsea well with inlet stream from reservoir
SubseaWell well = new SubseaWell("Well-1", reservoirStream);

Properties

Property Description Default
height Vertical depth of well 1000.0 m
length Measured depth of well 1200.0 m

Key Methods

Method Description
getPipeline() Access internal tubing model
getOutletStream() Get wellhead stream
run() Execute well flow calculation

Pipeline Configuration

SubseaWell well = new SubseaWell("OP-1", reservoirStream);

// Configure tubing
AdiabaticTwoPhasePipe tubing = well.getPipeline();
tubing.setDiameter(0.15);           // 6" tubing ID
tubing.setLength(3000.0);           // 3000m MD
tubing.setInletElevation(-2500.0);  // Reservoir depth TVD
tubing.setOutletElevation(-200.0);  // Mudline depth

SimpleFlowLine

The SimpleFlowLine class models a subsea flowline or riser from the wellhead to the platform.

Class Hierarchy

TwoPortEquipment
└── SimpleFlowLine
    └── contains: AdiabaticTwoPhasePipe (flowline)

Constructor

import neqsim.process.equipment.subsea.SimpleFlowLine;

// Create flowline from choke outlet
SimpleFlowLine flowline = new SimpleFlowLine("FL-1", chokeOutletStream);

Configuration

SimpleFlowLine flowline = new SimpleFlowLine("Flowline", wellheadStream);

// Configure flowline
flowline.getPipeline().setDiameter(0.4);          // 16" flowline
flowline.getPipeline().setLength(5000.0);         // 5 km tieback
flowline.getPipeline().setInletElevation(-200.0); // Mudline
flowline.getPipeline().setOutletElevation(0.0);   // Platform

System Integration

Typical Subsea System Architecture

┌─────────────┐
│  RESERVOIR  │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ SUBSEA WELL │ ← Tubing flow model
│  (tubing)   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│SUBSEA CHOKE │ ← Pressure control
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  FLOWLINE   │ ← Multiphase transport
│   (riser)   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│TOPSIDE CHOKE│ ← Final pressure control
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  SEPARATOR  │ ← First-stage separation
└─────────────┘

Usage Examples

Complete Subsea Production System

import neqsim.process.equipment.reservoir.SimpleReservoir;
import neqsim.process.equipment.subsea.SubseaWell;
import neqsim.process.equipment.subsea.SimpleFlowLine;
import neqsim.process.equipment.valve.ThrottlingValve;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;

// Create reservoir fluid
SystemInterface reservoirFluid = new SystemSrkEos(373.15, 250.0);  // 100°C, 250 bar
reservoirFluid.addComponent("nitrogen", 0.1);
reservoirFluid.addComponent("methane", 70.0);
reservoirFluid.addComponent("ethane", 5.0);
reservoirFluid.addComponent("propane", 3.0);
reservoirFluid.addComponent("n-butane", 2.0);
reservoirFluid.addComponent("n-heptane", 10.0);
reservoirFluid.addComponent("nC10", 5.0);
reservoirFluid.addComponent("water", 10.0);
reservoirFluid.setMixingRule(2);
reservoirFluid.setMultiPhaseCheck(true);

// Create reservoir
SimpleReservoir reservoir = new SimpleReservoir("Deepwater Field");
reservoir.setReservoirFluid(reservoirFluid, 5.0e7, 550.0e6, 10.0e6);

// Add oil producer
StreamInterface producedStream = reservoir.addOilProducer("OP-1");
producedStream.setFlowRate(10000.0 * 24.0, "kg/day");

// Run reservoir
reservoir.run();

// Create subsea well
SubseaWell well = new SubseaWell("OP-1 Well", producedStream);
well.getPipeline().setDiameter(0.15);           // 6" tubing
well.getPipeline().setLength(3500.0);           // 3500m MD
well.getPipeline().setInletElevation(-2000.0);  // Reservoir depth
well.getPipeline().setOutletElevation(-500.0);  // Mudline

// Subsea choke
ThrottlingValve subseaChoke = new ThrottlingValve("Subsea Choke", well.getOutletStream());
subseaChoke.setOutletPressure(120.0, "bara");
subseaChoke.setAcceptNegativeDP(false);

// Flowline to platform
SimpleFlowLine flowline = new SimpleFlowLine("Flowline", subseaChoke.getOutletStream());
flowline.getPipeline().setDiameter(0.25);       // 10" flowline
flowline.getPipeline().setLength(8000.0);       // 8 km tieback
flowline.getPipeline().setInletElevation(-500.0);
flowline.getPipeline().setOutletElevation(0.0);

// Topside choke
ThrottlingValve topsideChoke = new ThrottlingValve("Topside Choke", flowline.getOutletStream());
topsideChoke.setOutletPressure(50.0, "bara");

// Create process system
ProcessSystem subsea = new ProcessSystem("Subsea System");
subsea.add(well);
subsea.add(subseaChoke);
subsea.add(flowline);
subsea.add(topsideChoke);

// Run
subsea.run();

// Results
System.out.println("Wellhead pressure: " + well.getOutletStream().getPressure("bara") + " bara");
System.out.println("Subsea choke DP: " + subseaChoke.getDeltaPressure("bar") + " bar");
System.out.println("Arrival pressure: " + flowline.getOutletStream().getPressure("bara") + " bara");
System.out.println("Topside pressure: " + topsideChoke.getOutletStream().getPressure("bara") + " bara");

Transient Reservoir Depletion

import java.util.ArrayList;

// Setup as above...

// Production rate control with adjuster
Adjuster rateControl = new Adjuster("Rate Adjuster");
rateControl.setActivateWhenLess(true);
rateControl.setTargetVariable(flowline.getOutletStream(), "pressure", 80.0, "bara");
rateControl.setAdjustedVariable(producedStream, "flow rate");

// Add adjuster to process
subsea.add(rateControl);

// Run transient simulation
ArrayList<double[]> productionHistory = new ArrayList<double[]>();

for (int day = 0; day < 365; day++) {
    // Run reservoir for one day
    reservoir.runTransient(60 * 60 * 24);  // seconds in day

    // Run subsea system
    subsea.run();

    // Record data
    productionHistory.add(new double[] {
        day,
        producedStream.getFlowRate("kg/hr"),
        reservoir.getOilProductionTotal("MSm3"),
        reservoir.getPressure()
    });

    // Monthly output
    if (day % 30 == 0) {
        System.out.printf("Day %d: Rate=%.0f kg/hr, Cum=%.2f MSm3, P_res=%.1f bara%n",
            day, 
            producedStream.getFlowRate("kg/hr"),
            reservoir.getOilProductionTotal("MSm3"),
            reservoir.getPressure());
    }
}

Multiple Wells with Manifold

// Create three subsea wells
SubseaWell well1 = new SubseaWell("OP-1", reservoir.getOilProducer("OP-1").getStream());
SubseaWell well2 = new SubseaWell("OP-2", reservoir.getOilProducer("OP-2").getStream());
SubseaWell well3 = new SubseaWell("OP-3", reservoir.getOilProducer("OP-3").getStream());

// Configure wells
for (SubseaWell well : new SubseaWell[] {well1, well2, well3}) {
    well.getPipeline().setDiameter(0.15);
    well.getPipeline().setLength(3000.0);
    well.getPipeline().setInletElevation(-2000.0);
    well.getPipeline().setOutletElevation(-400.0);
}

// Create subsea manifold
Manifold subseaManifold = new Manifold("Subsea Manifold");
subseaManifold.addStream(well1.getOutletStream());
subseaManifold.addStream(well2.getOutletStream());
subseaManifold.addStream(well3.getOutletStream());

// Export flowline from manifold
SimpleFlowLine exportLine = new SimpleFlowLine("Export", subseaManifold.getOutletStream());
exportLine.getPipeline().setDiameter(0.4);
exportLine.getPipeline().setLength(15000.0);

Design Considerations

Well Deliverability

Flowline Sizing

Choke Valve Placement

Thermal Management


Chapter 20: Utility Equipment

Utility Overview

Utility Equipment

This folder contains documentation for utility and control equipment in NeqSim.

Equipment Categories

Process Control

Equipment File Description
Adjusters adjusters.md Parameter adjustment to meet specifications
Recycles recycles.md Recycle stream handling
Setters setters.md Variable setters
Calculators calculators.md Custom calculations

Flow Control

Equipment File Description
Set Points setpoints.md Process set points
Flow Rate Adjusters flow_adjusters.md Flow rate control

Quick Reference

Adjuster Pattern

Adjuster adjuster = new Adjuster("Controller");
adjuster.setAdjustedVariable(equipment, "parameter");
adjuster.setTargetVariable(stream, "property", targetValue, unit);
process.add(adjuster);

Recycle Pattern

Recycle recycle = new Recycle("RecycleName");
recycle.addStream(recycleStream);
recycle.setOutletStream(targetMixer);
recycle.setTolerance(1e-6);
process.add(recycle);

Adjusters

Adjusters

Documentation for adjuster equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.util

Class: Adjuster

Adjusters are iterative solvers that modify one process variable to achieve a target specification. They are essential for solving design problems where:

NeqSim supports two configuration modes:

  1. Standard Mode: Using predefined variable names (setAdjustedVariable, setTargetVariable)
  2. Functional Interface Mode: Using lambda expressions for complete flexibility

Adjuster Class

Basic Usage

import neqsim.process.equipment.util.Adjuster;

// Create adjuster
Adjuster adjuster = new Adjuster("Temperature Controller");

// Set the variable to adjust
adjuster.setAdjustedVariable(heater, "outTemperature");

// Set the target specification
adjuster.setTargetVariable(stream, "temperature", 80.0, "C");

// Add to process
process.add(adjuster);
process.run();

// Get the adjusted value
double adjustedTemp = heater.getOutTemperature("C");

Configuration

Adjusted Variables

The variable that the adjuster will modify:

Equipment Variable Description
Heater/Cooler "duty" Heat duty (W)
Heater/Cooler "outTemperature" Outlet temperature
Compressor "outletPressure" Discharge pressure
Valve "outletPressure" Outlet pressure
Valve "percentValveOpening" Valve position
Splitter "splitFactor" Split ratio
Stream "flowRate" Flow rate
// Examples
adjuster.setAdjustedVariable(heater, "duty");
adjuster.setAdjustedVariable(compressor, "outletPressure");
adjuster.setAdjustedVariable(valve, "percentValveOpening");
adjuster.setAdjustedVariable(splitter, "splitFactor", 0);  // First split

Target Variables

The specification to be achieved:

Equipment Variable Description
Stream "temperature" Stream temperature
Stream "pressure" Stream pressure
Stream "flowRate" Stream flow rate
Stream "moleFraction" Component mole fraction
Separator "liquidLevel" Liquid level fraction
Any Custom User-defined property
// Examples
adjuster.setTargetVariable(stream, "temperature", 80.0, "C");
adjuster.setTargetVariable(separator, "liquidLevel", 0.5);
adjuster.setTargetVariable(stream, "moleFraction", 0.02, "CO2");

Solver Settings

// Maximum iterations
adjuster.setMaximumIterations(100);

// Convergence tolerance
adjuster.setTolerance(1e-6);

// Bounds on adjusted variable
adjuster.setMinimumValue(-1e7);  // Lower bound
adjuster.setMaximumValue(1e7);   // Upper bound

// Step size for numerical derivatives
adjuster.setStepSize(0.001);

Functional Interface Mode

For complex scenarios where standard variable names are insufficient, the Adjuster supports functional interfaces (lambda expressions) for complete flexibility. This allows you to:

Functional Interface Methods

Method Signature Description
setAdjustedValueSetter Consumer<Double> Lambda to set the adjusted value
setAdjustedValueGetter Supplier<Double> Lambda to get the current adjusted value
setTargetValueCalculator Supplier<Double> Lambda to calculate the target (measured) value
setTargetValue double The setpoint that the target should reach

Basic Structure

Adjuster adjuster = new Adjuster("adjuster");

// Set the setpoint (what targetValueCalculator should return)
adjuster.setTargetValue(desiredValue);

// Set bounds for the adjusted variable
adjuster.setMinAdjustedValue(minValue);
adjuster.setMaxAdjustedValue(maxValue);

// Lambda: How to SET the adjusted variable
adjuster.setAdjustedValueSetter((val) -> {
    // Your logic to set the value
    equipment.setSomeProperty(val);
});

// Lambda: How to GET the current adjusted variable value
adjuster.setAdjustedValueGetter(() -> {
    // Your logic to get the current value
    return equipment.getSomeProperty();
});

// Lambda: How to CALCULATE the measured variable (compared to setpoint)
adjuster.setTargetValueCalculator(() -> {
    // Your logic to calculate current target value
    return someCalculation();
});

Example: Splitter Flow Control

Adjust a splitter's second outlet flow rate to achieve a target flow in the first outlet:

// Create process equipment
Stream feed = new Stream("feed", fluid);
feed.setFlowRate(1000.0, "kg/hr");

Splitter splitter = new Splitter("splitter", feed);
splitter.setSplitNumber(2);
splitter.setSplitFactors(new double[] {0.5, 0.5});

Stream stream1 = new Stream("stream1", splitter.getSplitStream(0));
Stream stream2 = new Stream("stream2", splitter.getSplitStream(1));

// Create adjuster with functional interfaces
Adjuster adjuster = new Adjuster("Flow Adjuster");

// Setpoint: we want stream1 to have 800 kg/hr
adjuster.setTargetValue(800.0);

// Bounds: stream2 flow must be between 0 and 1000 kg/hr
adjuster.setMinAdjustedValue(0.0);
adjuster.setMaxAdjustedValue(1000.0);

// Setter: Adjust the flow rate of stream2 via splitter
// Note: Splitter.setFlowRates uses -1 for "calculate this one"
adjuster.setAdjustedValueSetter((val) -> {
    splitter.setFlowRates(new double[] {-1, val}, "kg/hr");
});

// Getter: Get current flow rate of stream2
adjuster.setAdjustedValueGetter(() -> {
    return splitter.getSplitStream(1).getFlowRate("kg/hr");
});

// Target Calculator: Get flow rate of stream1 (what we're controlling)
adjuster.setTargetValueCalculator(() -> {
    return stream1.getFlowRate("kg/hr");
});

// Add to process
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(splitter);
process.add(stream1);
process.add(stream2);
process.add(adjuster);
process.run();

// Result: stream1 = 800 kg/hr, stream2 = 200 kg/hr

Example: Custom Temperature Control

Adjust temperature to achieve a target product of flow × temperature:

Stream inletStream = new Stream("inlet", fluid);
inletStream.setFlowRate(100.0, "kg/hr");
inletStream.setTemperature(200.0, "K");

Adjuster adjuster = new Adjuster("Custom Adjuster");

// Setpoint: flow × temperature = 30000
adjuster.setTargetValue(30000.0);
adjuster.setMinAdjustedValue(100.0);   // Min temp 100 K
adjuster.setMaxAdjustedValue(500.0);   // Max temp 500 K

// Setter: Adjust temperature
adjuster.setAdjustedValueSetter((val) -> {
    inletStream.setTemperature(val, "K");
});

// Getter: Get current temperature
adjuster.setAdjustedValueGetter(() -> {
    return inletStream.getTemperature("K");
});

// Target Calculator: flow × temperature
adjuster.setTargetValueCalculator(() -> {
    return inletStream.getFlowRate("kg/hr") * inletStream.getTemperature("K");
});

ProcessSystem process = new ProcessSystem();
process.add(inletStream);
process.add(adjuster);
process.run();

// Result: temperature adjusts to 300 K (100 × 300 = 30000)

Equipment-Based Functional Interfaces

For cleaner code when working with specific equipment, use the equipment-aware signatures:

// Set the equipment references
adjuster.setAdjustedVariable(inletStream);
adjuster.setTargetVariable(inletStream);

// Setter with equipment reference
adjuster.setAdjustedValueSetter((equipment, val) -> {
    Stream s = (Stream) equipment;
    s.setTemperature(val, "K");
});

// Getter with equipment reference
adjuster.setAdjustedValueGetter((equipment) -> {
    Stream s = (Stream) equipment;
    return s.getTemperature("K");
});

// Target calculator with equipment reference
adjuster.setTargetValueCalculator((equipment) -> {
    Stream s = (Stream) equipment;
    return s.getFlowRate("kg/hr") * s.getTemperature("K");
});

When to Use Functional Interfaces

Scenario Recommended Approach
Standard properties (temperature, pressure, flow) Standard mode with setAdjustedVariable
Properties not in predefined list Functional interface mode
Complex calculations for target Use setTargetValueCalculator
Adjusting one equipment to affect another Functional interface mode
Multiple variables combined in target Functional interface mode
Conditional logic in getting/setting Functional interface mode

Usage Examples

Temperature Control

// Adjust heater duty to achieve target outlet temperature
Adjuster tempControl = new Adjuster("TC-100");
tempControl.setAdjustedVariable(heater, "duty");
tempControl.setTargetVariable(heater.getOutletStream(), "temperature", 100.0, "C");
process.add(tempControl);

Dew Point Control

// Adjust cooler to achieve hydrocarbon dew point
Adjuster dewPointControl = new Adjuster("HCDP Controller");
dewPointControl.setAdjustedVariable(cooler, "outTemperature");
dewPointControl.setTargetPhaseCondition(stream, "cricondenbar", 50.0, "bara");
process.add(dewPointControl);

Product Purity

// Adjust column reflux to achieve product purity
Adjuster purityControl = new Adjuster("Purity Controller");
purityControl.setAdjustedVariable(column, "refluxRatio");
purityControl.setTargetVariable(overhead, "moleFraction", 0.99, "methane");
process.add(purityControl);

Separator Level Control

// Adjust outlet valve to maintain liquid level
Adjuster levelControl = new Adjuster("LC-100");
levelControl.setAdjustedVariable(outletValve, "percentValveOpening");
levelControl.setTargetVariable(separator, "liquidLevel", 0.5);
process.add(levelControl);

Flow Split Optimization

// Adjust split ratio to achieve target flow in branch
Adjuster flowControl = new Adjuster("FC-100");
flowControl.setAdjustedVariable(splitter, "splitFactor", 0);
flowControl.setTargetVariable(branchStream, "flowRate", 5000.0, "kg/hr");
process.add(flowControl);

Multiple Adjusters

When using multiple adjusters, add them in order of priority:

// First adjuster (higher priority)
Adjuster adj1 = new Adjuster("Primary");
adj1.setAdjustedVariable(heater, "duty");
adj1.setTargetVariable(stream1, "temperature", 80.0, "C");
process.add(adj1);

// Second adjuster (solved after first converges)
Adjuster adj2 = new Adjuster("Secondary");
adj2.setAdjustedVariable(cooler, "duty");
adj2.setTargetVariable(stream2, "temperature", 30.0, "C");
process.add(adj2);

Troubleshooting

Convergence Issues

// Increase iterations
adjuster.setMaximumIterations(200);

// Widen bounds
adjuster.setMinimumValue(-1e8);
adjuster.setMaximumValue(1e8);

// Check if converged
if (!adjuster.isConverged()) {
    System.out.println("Adjuster did not converge");
    System.out.println("Current error: " + adjuster.getError());
}

Infeasible Specifications

Some specifications may be physically impossible:

Check that specifications are achievable before troubleshooting solver settings.


Recycles

Recycles

Documentation for recycle handling in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.util

Classes:

Class Description
Recycle Main recycle handler
RecycleController Advanced recycle control
AccelerationMethod Convergence acceleration
BroydenAccelerator Broyden's method acceleration

Recycles handle iterative loops in process flowsheets where a downstream stream feeds back into an upstream unit. Common examples:


Recycle Class

Basic Usage

import neqsim.process.equipment.util.Recycle;

// Create recycle
Recycle recycle = new Recycle("Solvent Recycle");

// Add the stream coming from downstream
recycle.addStream(returnStream);

// Set where the recycle feeds into
recycle.setOutletStream(feedMixer);

// Add to process
process.add(recycle);
process.run();

How Recycles Work

  1. Initial Estimate: Process runs with assumed recycle composition
  2. Calculate Downstream: Equipment processes the estimate
  3. Update Recycle: New recycle stream values are calculated
  4. Iterate: Repeat until convergence

Configuration

Tolerance

// Set convergence tolerance
recycle.setTolerance(1e-6);

// Separate tolerances for flow and composition
recycle.setFlowTolerance(1e-4);
recycle.setCompositionTolerance(1e-6);
recycle.setTemperatureTolerance(0.1);  // K
recycle.setPressureTolerance(0.01);    // bar

Maximum Iterations

// Limit iterations
recycle.setMaximumIterations(50);

Damping

Damping helps prevent oscillation:

// Set damping factor (0-1, lower = more damping)
recycle.setDampingFactor(0.5);  // 50% of new value, 50% of old

Acceleration Methods

For faster convergence, acceleration methods can be used:

Wegstein Acceleration

recycle.setAccelerationMethod("wegstein");

Broyden Acceleration

import neqsim.process.equipment.util.BroydenAccelerator;

BroydenAccelerator accelerator = new BroydenAccelerator();
recycle.setAccelerationMethod(accelerator);

Direct Substitution

Simple successive substitution (default):

recycle.setAccelerationMethod("direct");

Usage Examples

Simple Solvent Recycle

ProcessSystem process = new ProcessSystem();

// Feed stream
Stream feed = new Stream("Feed", feedFluid);
process.add(feed);

// Mixer for feed and recycle
Mixer mixer = new Mixer("Feed Mixer");
mixer.addStream(feed);
process.add(mixer);

// Process unit (e.g., absorber)
Absorber absorber = new Absorber("TEG Contactor", mixer.getOutletStream());
process.add(absorber);

// Regeneration
Heater regenerator = new Heater("TEG Regenerator", absorber.getLiquidOutStream());
regenerator.setOutTemperature(200.0, "C");
process.add(regenerator);

// Cooler
Cooler cooler = new Cooler("TEG Cooler", regenerator.getOutletStream());
cooler.setOutTemperature(40.0, "C");
process.add(cooler);

// Recycle lean solvent back to mixer
Recycle solventRecycle = new Recycle("TEG Recycle");
solventRecycle.addStream(cooler.getOutletStream());
solventRecycle.setOutletStream(mixer);
solventRecycle.setTolerance(1e-5);
process.add(solventRecycle);

// Connect mixer to absorber with recycle
mixer.addStream(solventRecycle.getOutletStream());

// Run
process.run();

// Check convergence
if (solventRecycle.isConverged()) {
    System.out.println("Recycle converged in " + 
        solventRecycle.getIterations() + " iterations");
}

Reactor Recycle

// Fresh feed
Stream freshFeed = new Stream("Fresh Feed", freshFeedFluid);
process.add(freshFeed);

// Mix fresh feed with recycle
Mixer reactorFeed = new Mixer("Reactor Feed");
reactorFeed.addStream(freshFeed);
process.add(reactorFeed);

// Reactor
GibbsReactor reactor = new GibbsReactor("Synthesis Reactor");
reactor.setInletStream(reactorFeed.getOutletStream());
process.add(reactor);

// Separator
Separator productSep = new Separator("Product Separator", reactor.getOutletStream());
process.add(productSep);

// Recycle unreacted gas
Recycle gasRecycle = new Recycle("Unreacted Gas Recycle");
gasRecycle.addStream(productSep.getGasOutStream());
gasRecycle.setOutletStream(reactorFeed);
gasRecycle.setTolerance(1e-5);
gasRecycle.setDampingFactor(0.7);
process.add(gasRecycle);

reactorFeed.addStream(gasRecycle.getOutletStream());

process.run();

Nested Recycles

For processes with multiple recycle loops:

// Outer recycle (converges first)
Recycle outerRecycle = new Recycle("Outer Recycle");
outerRecycle.addStream(outerStream);
outerRecycle.setOutletStream(outerMixer);
outerRecycle.setPriority(1);  // Lower priority converges first
process.add(outerRecycle);

// Inner recycle (converges second)
Recycle innerRecycle = new Recycle("Inner Recycle");
innerRecycle.addStream(innerStream);
innerRecycle.setOutletStream(innerMixer);
innerRecycle.setPriority(2);  // Higher priority
process.add(innerRecycle);

Convergence Monitoring

Check Status

// Check if converged
boolean converged = recycle.isConverged();

// Get number of iterations
int iterations = recycle.getIterations();

// Get current error
double error = recycle.getError();

System.out.println("Recycle status:");
System.out.println("  Converged: " + converged);
System.out.println("  Iterations: " + iterations);
System.out.println("  Error: " + error);

Convergence History

// Get convergence history for debugging
double[] errorHistory = recycle.getErrorHistory();
for (int i = 0; i < errorHistory.length; i++) {
    System.out.println("Iteration " + i + ": error = " + errorHistory[i]);
}

Troubleshooting

Slow Convergence

  1. Reduce damping factor
  2. Use acceleration method
  3. Check for conflicting specifications
  4. Improve initial estimate
// Try Wegstein acceleration
recycle.setAccelerationMethod("wegstein");
recycle.setDampingFactor(0.8);

Oscillation

  1. Increase damping
  2. Reduce step size
  3. Check for multiple solutions
// Heavy damping for oscillating systems
recycle.setDampingFactor(0.3);
recycle.setMaximumIterations(100);

Non-Convergence

  1. Check physical feasibility
  2. Verify mass balance closure
  3. Start with simpler configuration
  4. Check stream specifications
// Debug mode
recycle.setVerbose(true);
process.run();

Best Practices

  1. Tear Stream Selection: Choose streams with least impact on downstream
  2. Good Initial Estimate: Provide reasonable starting values
  3. Appropriate Tolerance: Balance accuracy vs. computation time
  4. Monitor Convergence: Check iteration count and error trends
  5. Sequential Solution: For nested loops, converge inner loops first

Calculators

Calculators and Setters

Documentation for calculator and setter equipment in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.util

Classes:

Class Description
Calculator Custom calculation unit
CalculatorLibrary Pre-built calculation functions
Setter Variable setter
FlowSetter Flow rate setter
MoleFractionControllerUtil Composition control

Calculator Class

Performs custom calculations based on process variables. The Calculator supports two configuration modes:

  1. Standard Mode - Uses expression strings (limited support)
  2. Functional Interface Mode - Uses Java lambdas for full flexibility

Basic Usage

import neqsim.process.equipment.util.Calculator;

// Create calculator
Calculator calc = new Calculator("Duty Calculator");

// Add input variables
calc.addInputVariable(stream1);
calc.addInputVariable(stream2);

// Set output variable
calc.setOutputVariable(heater);

// Add to process
process.add(calc);

Adding Multiple Inputs

// Add streams individually
calc.addInputVariable(stream1);
calc.addInputVariable(stream2);

// Or add multiple at once using varargs
calc.addInputVariable(stream1, stream2, stream3);

Functional Interface Mode

The Calculator class supports lambda expressions for defining custom calculation logic. This provides full flexibility to implement any calculation without expression parsing limitations.

Available Methods

Method Signature Description
setCalculationMethod BiConsumer<ArrayList<ProcessEquipmentInterface>, ProcessEquipmentInterface> Full access to registered inputs and output
setCalculationMethod Runnable Simple lambda that captures variables from enclosing scope

Use this pattern when you want to work with formally registered input/output variables:

Calculator calculator = new Calculator("Energy Calculator");
calculator.addInputVariable(inletStream);
calculator.setOutputVariable(outletStream);

calculator.setCalculationMethod((inputs, output) -> {
    Stream in = (Stream) inputs.get(0);
    Stream out = (Stream) output;
    double energy = in.LCV() * in.getFlowRate("Sm3/hr");
    out.setTemperature(350.0, "K");
});

calculator.run();

Multiple Inputs Example

Calculator calculator = new Calculator("Total Flow Calculator");

// Add multiple inputs using varargs
calculator.addInputVariable(inletStream1, inletStream2);
calculator.setOutputVariable(outletStream);

calculator.setCalculationMethod((inputs, output) -> {
    double totalFlow = 0.0;
    for (ProcessEquipmentInterface input : inputs) {
        totalFlow += ((Stream) input).getFlowRate("kg/hr");
    }
    ((Stream) output).setFlowRate(totalFlow, "kg/hr");
});

calculator.run();

Runnable Pattern (Simple)

Use this pattern when you want to capture equipment directly in the lambda closure:

Calculator calculator = new Calculator("Energy Calculator");
// No need to register inputs/outputs - capture them directly

calculator.setCalculationMethod(() -> {
    double energy = inletStream.LCV() * inletStream.getFlowRate("Sm3/hr");
    outletStream.setTemperature(350.0, "K");
});

calculator.run();

When to Use Each Pattern

Pattern Use When
BiConsumer Building reusable calculations, working with variable number of inputs
Runnable Quick calculations, capturing specific equipment from scope

Calculator Library

Pre-built calculation presets for common thermodynamic operations. These presets provide declarative building blocks that encourage consistent logic across simulations.

Available Presets

Preset Description
ENERGY_BALANCE Flashes output stream to match summed input enthalpy
DEW_POINT_TARGETING Sets output temperature to hydrocarbon dew point

Using Presets

import neqsim.process.equipment.util.CalculatorLibrary;

// Using the preset directly
Calculator calculator = new Calculator("Energy Balance");
calculator.addInputVariable(inlet);
calculator.setOutputVariable(outlet);
calculator.setCalculationMethod(CalculatorLibrary.energyBalance());
calculator.run();

Energy Balance Preset

Performs an enthalpy-based energy balance. The output stream is flashed at its current pressure to match the summed input enthalpies:

SystemSrkEos fluid = new SystemSrkEos(280.0, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.1);
fluid.createDatabase(true);
fluid.setMixingRule(2);

Stream inlet = new Stream("inlet", fluid);
inlet.setTemperature(280.0, "K");
inlet.setPressure(50.0, "bara");
inlet.run();

Stream outlet = new Stream("outlet", fluid.clone());
outlet.setTemperature(320.0, "K");
outlet.setPressure(50.0, "bara");
outlet.run();

Calculator calculator = new Calculator("Energy Balance");
calculator.addInputVariable(inlet);
calculator.setOutputVariable(outlet);
calculator.setCalculationMethod(CalculatorLibrary.energyBalance());
calculator.run();

// Outlet enthalpy now matches inlet enthalpy

Dew Point Targeting Preset

Sets the output stream temperature to the hydrocarbon dew point of the first input stream at the output stream's pressure:

Stream source = new Stream("source", fluid);
source.setPressure(15.0, "bara");
source.run();

Stream target = new Stream("target", fluid.clone());
target.setPressure(12.0, "bara");
target.run();

Calculator calculator = new Calculator("Dew Point Targeter");
calculator.addInputVariable(source);
calculator.setOutputVariable(target);
calculator.setCalculationMethod(CalculatorLibrary.byName("dewPointTargeting"));
calculator.run();

// Target temperature now equals dew point at 12 bara

Dew Point with Margin

Add a safety margin above the dew point:

// Add 5 K margin above dew point
calculator.setCalculationMethod(CalculatorLibrary.dewPointTargeting(5.0));

Resolving Presets by Name

Useful for declarative configuration or AI-generated instructions:

// Case-insensitive, supports various formats
CalculatorLibrary.byName("energyBalance");
CalculatorLibrary.byName("ENERGY_BALANCE");
CalculatorLibrary.byName("energy-balance");
CalculatorLibrary.byName("dewPointTargeting");

Setter Class

Sets process variables to specific values.

Basic Usage

import neqsim.process.equipment.util.Setter;

// Create setter
Setter setter = new Setter("Temperature Setter");

// Set target equipment and property
setter.setEquipment(heater);
setter.setProperty("outTemperature");
setter.setValue(80.0);
setter.setUnit("C");

// Add to process
process.add(setter);

Use Cases

// Set valve position
Setter valveSetter = new Setter("Valve Opener");
valveSetter.setEquipment(valve);
valveSetter.setProperty("percentValveOpening");
valveSetter.setValue(75.0);

// Set compressor speed
Setter speedSetter = new Setter("Speed Setter");
speedSetter.setEquipment(compressor);
speedSetter.setProperty("speed");
speedSetter.setValue(5000.0);

Flow Setter

Specifically for setting flow rates.

Basic Usage

import neqsim.process.equipment.util.FlowSetter;

// Create flow setter
FlowSetter flowSetter = new FlowSetter("Production Rate", stream);
flowSetter.setFlowRate(10000.0, "kg/hr");

// Add to process
process.add(flowSetter);

With Ramping

// Ramp flow rate over time
flowSetter.setRampRate(1000.0, "kg/hr/min");
flowSetter.setTargetFlowRate(20000.0, "kg/hr");

Mole Fraction Controller

Control stream composition.

Basic Usage

import neqsim.process.equipment.util.MoleFractionControllerUtil;

// Control CO2 content
MoleFractionControllerUtil co2Control = 
    new MoleFractionControllerUtil("CO2 Spec", stream);
co2Control.setTargetMoleFraction("CO2", 0.02);  // 2 mol%

// Add to process
process.add(co2Control);

Usage Examples

Production Optimizer

// Calculator to maximize production
Calculator optimizer = new Calculator("Production Optimizer");
optimizer.addInputVariable(separator, "pressure");
optimizer.addInputVariable(feedStream, "flowRate");
optimizer.setOutputVariable(exportValve, "percentValveOpening");
optimizer.setExpression("calculateOptimalOpening(pressure, flowRate)");
process.add(optimizer);

Cascade Control

// Primary controller output sets secondary setpoint
Calculator cascade = new Calculator("Cascade");
cascade.addInputVariable(temperatureController, "output");
cascade.setOutputVariable(flowController, "setpoint");
cascade.setExpression("output * flowGain + flowBias");
process.add(cascade);

Ratio Control

// Maintain fuel/air ratio
Calculator ratioCalc = new Calculator("F/A Ratio");
ratioCalc.addInputVariable(fuelStream, "flowRate");
ratioCalc.setOutputVariable(airDamper, "position");
ratioCalc.setExpression("fuelFlow * stoichRatio * excessAir");
process.add(ratioCalc);

Duty Calculation

// Calculate required cooling duty
Calculator dutyCalc = new Calculator("Cooling Duty");
dutyCalc.addInputVariable(inletStream, "temperature");
dutyCalc.addInputVariable(inletStream, "flowRate");
dutyCalc.addInputVariable(inletStream, "heatCapacity");
dutyCalc.setOutputVariable(cooler, "duty");
dutyCalc.setExpression("-flowRate * heatCapacity * (targetTemp - inletTemp)");
process.add(dutyCalc);

Set Point Class

Sets the value of a variable in a target equipment based on a source equipment. Used for feed-forward control or copying values between equipment.

Basic Usage

import neqsim.process.equipment.util.SetPoint;

// Create set point to copy pressure
SetPoint setPoint = new SetPoint("Pressure Copy");
setPoint.setSourceVariable(sourceStream, "pressure");
setPoint.setTargetVariable(targetStream, "pressure");

// Add to process
process.add(setPoint);

Supported Target Variables

Equipment Type Supported Variables
Stream pressure, temperature
ThrottlingValve pressure (outlet)
Compressor pressure (outlet)
Pump pressure (outlet)
Heater/Cooler pressure, temperature

Functional Interface Mode

Use setSourceValueCalculator to define a custom function that calculates the value to set on the target equipment:

SetPoint setPoint = new SetPoint("Custom SetPoint");
setPoint.setSourceVariable(sourceStream);
setPoint.setTargetVariable(targetStream, "pressure");

// Set target pressure based on source temperature: P = T / 10.0
setPoint.setSourceValueCalculator((equipment) -> {
    Stream s = (Stream) equipment;
    return s.getTemperature("K") / 10.0;
});

setPoint.run();
// Target pressure is now 30.0 bara (if source temp = 300 K)

Method Signature

Method Type Description
setSourceValueCalculator Function<ProcessEquipmentInterface, Double> Custom function to compute the value to set

When to Use Functional Mode

Use Case Example
Non-linear relationships Pressure = f(temperature, flow)
Unit conversions Convert from source units to target units
Computed ratios Set valve to percentage of max flow
Conditional logic Different values based on operating mode

Stream Transition

Smooth transition between operating states.

import neqsim.process.equipment.util.StreamTransition;

// Create transition
StreamTransition transition = new StreamTransition("Startup Ramp");
transition.setStream(feedStream);
transition.setInitialFlowRate(0.0, "kg/hr");
transition.setFinalFlowRate(10000.0, "kg/hr");
transition.setTransitionTime(3600.0);  // 1 hour ramp

// Run transition
for (double t = 0; t < 3600; t += 60) {
    transition.setTime(t);
    process.run();
}

Chapter 21: Process Control

Controllers

Process Controllers and Logic

Documentation for controllers, adjusters, recycles, and process logic in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.util, neqsim.process.controllerdevice, neqsim.process.logic

Classes:


Adjusters

Adjusters modify one variable to achieve a target specification.

Basic Usage

import neqsim.process.equipment.util.Adjuster;

// Adjust heater duty to achieve target outlet temperature
Adjuster tempControl = new Adjuster("TC-100");
tempControl.setAdjustedVariable(heater, "outTemperature");
tempControl.setTargetVariable(stream, "temperature", 80.0, "C");
process.add(tempControl);

Adjustable Variables

Equipment Variable Description
Heater/Cooler "duty" Heat duty
Heater/Cooler "outTemperature" Outlet temperature
Compressor "outletPressure" Discharge pressure
Valve "outletPressure" Outlet pressure
Splitter "splitFactor" Split ratio
Stream "flowRate" Flow rate

Target Variables

Equipment Variable Description
Stream "temperature" Temperature
Stream "pressure" Pressure
Stream "flowRate" Flow rate
Stream "moleFraction" Component mole fraction
Separator "liquidLevel" Liquid level

Example: Dew Point Control

// Adjust cooler to achieve hydrocarbon dew point
Adjuster dewPointControl = new Adjuster("Dew Point Controller");
dewPointControl.setAdjustedVariable(cooler, "outTemperature");
dewPointControl.setTargetPhaseCondition(stream, "dewpoint", 50.0, "bara");
process.add(dewPointControl);

Solver Settings

adjuster.setMaximumIterations(50);
adjuster.setTolerance(1e-6);
adjuster.setMinimumValue(-1e6);  // Duty lower bound
adjuster.setMaximumValue(1e6);   // Duty upper bound

Recycles

Handle recycle streams in process flowsheets.

Basic Usage

import neqsim.process.equipment.util.Recycle;

// Define recycle
Recycle recycle = new Recycle("Solvent Recycle");
recycle.addStream(recycleStream);
recycle.setOutletStream(inletMixer);
recycle.setTolerance(1e-6);
process.add(recycle);

Recycle Placement

ProcessSystem process = new ProcessSystem();

// Feed
process.add(feed);

// Mixer (combines feed and recycle)
Mixer mixer = new Mixer("M-100");
mixer.addStream(feed);
process.add(mixer);

// Process equipment
process.add(reactor);
process.add(separator);

// Splitter for recycle
Splitter splitter = new Splitter("Splitter", separator.getLiquidOutStream());
splitter.setSplitFactors(new double[]{0.9, 0.1});  // 10% recycle
process.add(splitter);

// Recycle stream
Recycle recycle = new Recycle("Recycle");
recycle.addStream(splitter.getSplitStream(1));
recycle.setOutletStream(mixer);
process.add(recycle);

// Connect mixer to recycle
mixer.addStream(recycle.getOutletStream());

process.run();

Convergence Settings

recycle.setTolerance(1e-6);
recycle.setMaximumIterations(100);

// Acceleration methods
recycle.setAccelerationMethod("wegstein");
// Options: "direct", "wegstein", "broyden"

Setters

Set variable values directly.

Basic Usage

import neqsim.process.equipment.util.Setter;

// Set flow rate
Setter flowSetter = new Setter("Flow Setter", stream);
flowSetter.setVariable("flowRate", 1000.0, "kg/hr");
process.add(flowSetter);

Mole Fraction Setter

import neqsim.process.equipment.util.MoleFractionSetter;

// Set component mole fraction
MoleFractionSetter compSetter = new MoleFractionSetter("CO2 Setter", stream);
compSetter.setMoleFraction("CO2", 0.02);
process.add(compSetter);

Calculators

Perform custom calculations.

Basic Usage

import neqsim.process.equipment.util.Calculator;

Calculator calc = new Calculator("Energy Balance");
calc.addInputVariable(stream1);
calc.addInputVariable(stream2);
calc.setOutputVariable(heater, "duty");

// Custom calculation (override in subclass or use expression)
calc.setExpression("stream1.enthalpy - stream2.enthalpy");
process.add(calc);

PID Controllers

For dynamic simulation with feedback control.

Basic Usage

import neqsim.process.controllerdevice.PIDController;

PIDController levelControl = new PIDController("LC-100");
levelControl.setMeasuredVariable(separator, "liquidLevel");
levelControl.setControlledVariable(valve, "opening");
levelControl.setSetPoint(0.5);  // 50% level

// Tuning parameters
levelControl.setKp(2.0);    // Proportional gain
levelControl.setKi(0.1);    // Integral gain (1/s)
levelControl.setKd(0.0);    // Derivative gain (s)

process.add(levelControl);

Tuning

// Action
levelControl.setReverseAction(true);  // Increase output decreases PV

// Output limits
levelControl.setOutputMin(0.0);
levelControl.setOutputMax(100.0);

// Anti-windup
levelControl.setAntiWindup(true);

Dynamic Execution

// Run transient with controllers
for (double t = 0; t < 3600; t += 1.0) {
    process.runTransient();

    double pv = levelControl.getProcessVariable();
    double sp = levelControl.getSetPoint();
    double out = levelControl.getOutput();

    System.out.printf("%.1f, %.3f, %.3f, %.1f%n", t, pv, sp, out);
}

Process Logic

Conditional logic for process decisions.

Basic Usage

import neqsim.process.logic.ProcessLogicController;

ProcessLogicController logic = new ProcessLogicController("Emergency Logic");

// Define condition
logic.setCondition(pressure, ">", 100.0, "bara");

// Define action
logic.setAction(shutoffValve, "close");

process.add(logic);

Complex Conditions

// AND condition
logic.addCondition(pressure, ">", 100.0, "bara", "AND");
logic.addCondition(temperature, ">", 150.0, "C", "AND");

// OR condition
logic.addCondition(level, "<", 0.1, "ratio", "OR");
logic.addCondition(level, ">", 0.9, "ratio", "OR");

Alarm Integration

import neqsim.process.alarm.ProcessAlarmManager;

ProcessAlarmManager alarms = process.getAlarmManager();

// High pressure alarm
alarms.addAlarm(separator, "pressure", 95.0, "high", "bara");
alarms.addAlarm(separator, "pressure", 100.0, "highHigh", "bara");

// Low level alarm
alarms.addAlarm(separator, "liquidLevel", 0.2, "low", "ratio");

Example: Complete Control System

ProcessSystem process = new ProcessSystem();

// Feed stream
Stream feed = new Stream("Feed", feedFluid);
feed.setFlowRate(1000.0, "kg/hr");
process.add(feed);

// Heater with temperature control
Heater heater = new Heater("E-100", feed);
process.add(heater);

Adjuster tempControl = new Adjuster("TC-100");
tempControl.setAdjustedVariable(heater, "duty");
tempControl.setTargetVariable(heater.getOutletStream(), "temperature", 80.0, "C");
process.add(tempControl);

// Separator with level control
Separator separator = new Separator("V-100", heater.getOutletStream());
process.add(separator);

ThrottlingValve liquidValve = new ThrottlingValve("LV-100", separator.getLiquidOutStream());
liquidValve.setOutletPressure(5.0, "bara");
process.add(liquidValve);

// Level controller (for dynamic)
PIDController levelControl = new PIDController("LC-100");
levelControl.setMeasuredVariable(separator, "liquidLevel");
levelControl.setControlledVariable(liquidValve, "opening");
levelControl.setSetPoint(0.5);
levelControl.setKp(5.0);
levelControl.setKi(0.5);
process.add(levelControl);

// Pressure control
ThrottlingValve gasValve = new ThrottlingValve("PV-100", separator.getGasOutStream());
process.add(gasValve);

Adjuster pressControl = new Adjuster("PC-100");
pressControl.setAdjustedVariable(gasValve, "outletPressure");
pressControl.setTargetVariable(separator, "pressure", 20.0, "bara");
process.add(pressControl);

// Run steady state
process.run();

// Run dynamic
for (double t = 0; t < 3600; t += 1.0) {
    // Disturbance at t=600
    if (Math.abs(t - 600) < 0.5) {
        feed.setFlowRate(1200.0, "kg/hr");
    }

    process.runTransient();
}

Process Control

Process control framework

NeqSim contains a flexible process control framework for dynamic simulations. The framework provides:

See the unit tests in src/test/java/neqsim/process/controllerdevice for examples of how the controllers and control structures are used in simulations.

Model predictive control

The ModelPredictiveController class adds multivariable model predictive control (MPC) to the framework. The controller uses a first-order process model with configurable gain, time constant, bias and prediction horizon to calculate an optimal control move that balances tracking accuracy, absolute energy usage and aggressive movement. MPC integrates with the rest of the process-control package through the common ControllerDeviceInterface, allowing it to replace or work alongside traditional PID loops.

Single-loop quick start

  1. Provide a measurement – connect a MeasurementDeviceInterface (for example a temperature or pressure transmitter) via setTransmitter. The MPC will read samples from the device whenever runTransient is invoked.
  2. Instantiate and parameterise – create the controller, call setControllerSetPoint, describe the internal process model with setProcessModel and setProcessBias, then choose a prediction horizon and tuning weights with setPredictionHorizon and setWeights.
  3. Apply limits and preferences – use setOutputLimits to cap the actuator and setPreferredControlValue to encode an economic target such as minimum heater duty.
  4. Execute in the simulation – call runTransient(previousControl, dt) every control interval. The return value from getResponse() is the new manipulated-variable value to apply to the process.
ModelPredictiveController controller = new ModelPredictiveController("heaterMpc");
controller.setTransmitter(temperatureSensor);
controller.setControllerSetPoint(328.15, "K");
controller.setProcessModel(0.18, 45.0);   // gain, time constant [s]
controller.setProcessBias(298.15);
controller.setPredictionHorizon(20);
controller.setWeights(1.0, 0.03, 0.2);    // tracking, energy, move penalties
controller.setPreferredControlValue(20.0);
controller.setOutputLimits(0.0, 100.0);

double manipulated = controller.getResponse();
for (double t = 0.0; t < 1800.0; t += 5.0) {
  controller.runTransient(manipulated, 5.0);
  manipulated = controller.getResponse();
  heater.setDuty(manipulated, "kW");
}

The single-input mode automatically handles reverse-acting processes via setReverseActing(true) and can be paused/resumed with setActive(false).

Multivariable optimisation

For flowsheets with several manipulated variables the MPC is configured with an ordered control vector:

controller.configureControls("dewPointCooler", "stabiliserHeater", "compressorSpeed");
controller.setInitialControlValues(6.0, 65.0, 0.78);
controller.setControlLimits("dewPointCooler", -10.0, 25.0);
controller.setControlLimits("stabiliserHeater", 40.0, 90.0);
controller.setControlLimits("compressorSpeed", 0.5, 1.05);
controller.setControlWeights(0.6, 0.2, 0.05);   // energy usage penalty
controller.setMoveWeights(0.2, 0.05, 0.02);     // movement smoothing
controller.setPreferredControlVector(0.0, 55.0, 0.8);

getControlVector() returns the most recent actuation proposal for all manipulated variables, while setPrimaryControlIndex determines which entry is exposed via getResponse() for backwards compatibility with controller structures expecting a single output.

Quality constraints and product specifications

MPC quality constraints describe how key product indicators respond to each manipulated variable and to feed composition/rate changes. Limits are handled as soft constraints, letting the optimiser trade off specification margin and energy usage.

ModelPredictiveController.QualityConstraint wobbeConstraint =
    ModelPredictiveController.QualityConstraint.builder("WobbeIndex")
        .measurement(wobbeTransmitter)
        .unit("MJ/Sm3")
        .limit(51.7)
        .margin(0.2)
        .controlSensitivity(0.04, -0.01, 0.03)
        .compositionSensitivity("nitrogen", -2.8)
        .rateSensitivity(0.005)
        .build();

controller.addQualityConstraint(wobbeConstraint);

The controller stores predicted specification values for diagnostics via getPredictedQuality. Call clearQualityConstraints() when the control structure changes or before reconfiguring sensitivities.

Feedforward updates

updateFeedConditions injects the expected upstream composition and rate into the next optimisation. Supplying these predictions enables proactive responses to known feed changes and improves constraint tracking on multivariate systems.

controller.updateFeedConditions(Map.of(
    "methane", 0.82,
    "ethane", 0.08,
    "propane", 0.03),
    12.4);    // kmol/hr

Moving horizon estimation

When the underlying process characteristics drift, enable the embedded moving horizon estimator so the internal model follows the plant:

controller.enableMovingHorizonEstimation(60);   // keep the last 60 samples

After the estimator has gathered enough samples getLastMovingHorizonEstimate() returns identified gain, time constant, bias and prediction error. Call clearMovingHorizonHistory() to restart the identification window, or disableMovingHorizonEstimation() to lock the controller to its current model.

Using plant measurements alongside the model

Digital twins are most valuable when they continuously reconcile with plant data. The MPC supports blending measured values from a facility with simulated predictions:

This approach allows existing plant instrumentation to update the MPC while the NeqSim model still contributes predictive behaviour for future disturbances.

Diagnostics and best practices

Dynamic Simulation Guide

Dynamic Simulation Guide

Comprehensive guide to transient and dynamic simulation in NeqSim.

Table of Contents


Overview

NeqSim supports both steady-state and transient (dynamic) simulation modes. Dynamic simulation enables modeling of:

When to Use Dynamic Simulation

Scenario Mode
Design calculations Steady-state
Equipment sizing Steady-state
Controller tuning Transient
Startup/shutdown analysis Transient
Safety studies (blowdown) Transient
Slug catcher sizing Transient
Training simulators Transient

Steady-State vs Transient Mode

Steady-State Mode (Default)

In steady-state mode, each run() call calculates the equilibrium solution assuming constant boundary conditions:

ProcessSystem process = new ProcessSystem();
process.add(stream);
process.add(separator);
process.add(compressor);

// Steady-state - each run() finds equilibrium
process.run();

Transient Mode

In transient mode, equipment remembers state between time steps. Enable by setting setCalculateSteadyState(false):

// Enable transient mode on equipment
separator.setCalculateSteadyState(false);
pipeline.setCalculateSteadyState(false);

// Time step through simulation
UUID id = UUID.randomUUID();
for (int step = 0; step < 100; step++) {
    process.runTransient(1.0, id);  // 1 second time step
}

Key Differences

Aspect Steady-State Transient
State memory None Equipment retains state
Method run() runTransient(dt, id)
Holdup Ignored Tracked (level, pressure)
Controllers Converged Time-stepping response
Initialization Automatic Requires steady-state first

Basic Transient Setup

Step-by-Step Setup

import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;
import java.util.UUID;

// 1. Create thermodynamic system
SystemInterface fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.1);
fluid.setMixingRule("classic");

// 2. Build process flowsheet
Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(1000.0, "kg/hr");

Separator separator = new Separator("V-100", feed);
separator.setInternalDiameter(2.0);
separator.setSeparatorLength(5.0);

ThrottlingValve gasValve = new ThrottlingValve("Gas Valve", separator.getGasOutStream());
gasValve.setOutletPressure(20.0, "bara");

ThrottlingValve liquidValve = new ThrottlingValve("Liq Valve", separator.getLiquidOutStream());
liquidValve.setOutletPressure(20.0, "bara");

// 3. Create process system
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(gasValve);
process.add(liquidValve);

// 4. Initialize with steady-state
process.run();
System.out.println("Initial level: " + separator.getLiquidLevel());

// 5. Switch to transient mode
separator.setCalculateSteadyState(false);

// 6. Run transient simulation
UUID calcId = UUID.randomUUID();
double dt = 1.0;  // 1 second time step

for (int step = 0; step < 60; step++) {
    process.runTransient(dt, calcId);

    if (step % 10 == 0) {
        System.out.printf("t=%d s, Level=%.3f m, P=%.2f bara%n",
            step, separator.getLiquidLevel(), separator.getPressure());
    }
}

Time Stepping

Choosing Time Step Size

The time step should be small enough to capture dynamics but large enough for efficiency:

Application Typical Time Step
Fast control loops 0.1 - 1.0 s
Separator level control 1.0 - 10.0 s
Pipeline transients 0.5 - 5.0 s
Reservoir depletion 3600 s (1 hour) to 86400 s (1 day)
Blowdown studies 0.1 - 1.0 s

CFL Condition for Pipelines

For pipeline transients, the time step should satisfy the CFL (Courant-Friedrichs-Lewy) condition:

$$\Delta t \leq \frac{\Delta x}{v + c}$$

Where:

Variable Time Stepping

double t = 0;
double tEnd = 3600;  // 1 hour
double dt = 1.0;

while (t < tEnd) {
    process.runTransient(dt, calcId);

    // Adjust time step based on rate of change
    double rateOfChange = Math.abs(separator.getLiquidLevel() - previousLevel) / dt;
    if (rateOfChange > 0.1) {
        dt = Math.max(0.1, dt / 2);  // Decrease time step
    } else if (rateOfChange < 0.01) {
        dt = Math.min(10.0, dt * 1.5);  // Increase time step
    }

    t += dt;
    previousLevel = separator.getLiquidLevel();
}

Equipment with Transient Support

Separators

Separator separator = new Separator("V-100", feed);
separator.setInternalDiameter(2.0);
separator.setSeparatorLength(5.0);
separator.setLiquidLevel(0.5);  // Initial level (m)
separator.setCalculateSteadyState(false);

// During transient, level changes based on in/out flow imbalance
separator.runTransient(dt, id);
double newLevel = separator.getLiquidLevel();

Pipelines (PipeBeggsAndBrills)

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("Pipeline", feed);
pipe.setLength(10000);  // 10 km
pipe.setDiameter(0.3);  // 0.3 m
pipe.setNumberOfIncrements(50);
pipe.setCalculateSteadyState(false);

// Transient tracks pressure waves and liquid holdup
pipe.runTransient(dt, id);

Tanks

Tank tank = new Tank("T-100", feed);
tank.setVolume(100.0);  // m³
tank.setCalculateSteadyState(false);

// Tank pressure/composition evolves based on in/out flows
tank.runTransient(dt, id);

Compressors

Compressor comp = new Compressor("K-100", feed);
comp.setOutletPressure(100.0, "bara");

// Enable dynamic features (state machine, events)
comp.setDynamicSimulationEnabled(true);
comp.setRotorInertia(100.0);  // kg·m²

// Compressor responds to speed changes, surge, etc.
comp.runTransient(dt, id);

Reservoirs

SimpleReservoir reservoir = new SimpleReservoir("Field");
reservoir.setReservoirFluid(fluid);
reservoir.setGasVolume(1e9, "Sm3");

StreamInterface producer = reservoir.addGasProducer("GP-1");
producer.setFlowRate(5.0, "MSm3/day");

// Run for one day
reservoir.runTransient(86400, id);
double remainingGas = reservoir.getGasInPlace("Sm3");

Control Systems

Adding Controllers for Dynamic Simulation

Controllers require transmitters to measure process variables:

// Level transmitter
LevelTransmitter levelTransmitter = new LevelTransmitter("LT-100", separator);
levelTransmitter.setMinimumValue(0.0);
levelTransmitter.setMaximumValue(2.0);

// Level controller
ControllerDeviceBaseClass levelController = new ControllerDeviceBaseClass("LC-100");
levelController.setTransmitter(levelTransmitter);
levelController.setControllerSetPoint(0.5);  // 0.5 m target level
levelController.setControllerParameters(0.5, 100.0, 0.0);  // Kp, Ti, Td
levelController.setReverseActing(true);  // Open valve to lower level

// Connect controller to valve
liquidValve.setController(levelController);

Pressure Controller

// Pressure transmitter
PressureTransmitter pressureTransmitter = new PressureTransmitter("PT-100", separator);
pressureTransmitter.setMinimumValue(0.0);
pressureTransmitter.setMaximumValue(100.0);

// Pressure controller
ControllerDeviceBaseClass pressureController = new ControllerDeviceBaseClass("PC-100");
pressureController.setTransmitter(pressureTransmitter);
pressureController.setControllerSetPoint(50.0);  // 50 bara target
pressureController.setControllerParameters(1.0, 50.0, 0.0);

gasValve.setController(pressureController);

Flow Controller

// Volume flow transmitter
VolumeFlowTransmitter flowTransmitter = new VolumeFlowTransmitter("FT-100", feed);
flowTransmitter.setMeasuredPhase("gas");
flowTransmitter.setMinimumValue(0.0);
flowTransmitter.setMaximumValue(10000.0);

ControllerDeviceBaseClass flowController = new ControllerDeviceBaseClass("FC-100");
flowController.setTransmitter(flowTransmitter);
flowController.setControllerSetPoint(5000.0);  // Target flow
flowController.setControllerParameters(0.1, 200.0, 0.0);

inletValve.setController(flowController);

Controller Tuning

// PID parameters
double Kp = 1.0;    // Proportional gain
double Ti = 100.0;  // Integral time (seconds)
double Td = 0.0;    // Derivative time (seconds)

controller.setControllerParameters(Kp, Ti, Td);

// Action direction
controller.setReverseActing(true);   // Increase output to decrease PV
controller.setReverseActing(false);  // Increase output to increase PV

Pipeline Dynamics

Transient Multiphase Flow

The TransientPipe class provides drift-flux based transient simulation:

import neqsim.fluidmechanics.flowsystem.twophaseflowsystem.twophasepipeflowsystem.TwoPhasePipeFlowSystem;

// Create transient pipeline
TransientPipe pipeline = new TransientPipe("FL-100", feed);
pipeline.setLength(5000.0);  // 5 km
pipeline.setDiameter(0.25);
pipeline.setNumberOfNodes(50);
pipeline.setElevationProfile(elevations);  // Terrain effects

// Configure flow regime detection
pipeline.setFlowRegimeDetectionMethod("mechanistic");

// Run transient
pipeline.setCalculateSteadyState(false);
for (int step = 0; step < 600; step++) {
    pipeline.runTransient(1.0, id);

    // Track slugs
    SlugTracker slugs = pipeline.getSlugTracker();
    System.out.println("Active slugs: " + slugs.getSlugCount());
}

Slug Tracking

// Get slug statistics
SlugTracker tracker = pipeline.getSlugTracker();

int slugCount = tracker.getSlugCount();
double avgSlugLength = tracker.getAverageSlugLength();
double maxSlugVolume = tracker.getMaxSlugVolume();
double slugFrequency = tracker.getSlugFrequency();

System.out.printf("Slugs: %d, Avg length: %.1f m, Frequency: %.2f /min%n",
    slugCount, avgSlugLength, slugFrequency * 60);

Vessel Depressurization

Blowdown Simulation

// Create vessel with initial conditions
SystemInterface vesselFluid = new SystemSrkEos(350.0, 100.0);
vesselFluid.addComponent("methane", 0.8);
vesselFluid.addComponent("ethane", 0.15);
vesselFluid.addComponent("propane", 0.05);
vesselFluid.setMixingRule("classic");

Tank vessel = new Tank("V-100", vesselFluid);
vessel.setVolume(50.0);  // 50 m³

// Blowdown valve to atmosphere
ThrottlingValve blowdownValve = new ThrottlingValve("BDV", vessel.getOutletStream());
blowdownValve.setOutletPressure(1.5, "bara");
blowdownValve.setCv(50.0);

ProcessSystem blowdown = new ProcessSystem();
blowdown.add(vessel);
blowdown.add(blowdownValve);

// Initialize
blowdown.run();
vessel.setCalculateSteadyState(false);

// Run blowdown for 15 minutes
UUID id = UUID.randomUUID();
double dt = 0.5;  // 0.5 second steps

ArrayList<double[]> results = new ArrayList<>();
for (double t = 0; t <= 900; t += dt) {
    blowdown.runTransient(dt, id);

    results.add(new double[] {
        t,
        vessel.getPressure(),
        vessel.getTemperature() - 273.15,  // °C
        blowdownValve.getFlowRate("kg/hr")
    });
}

// Report minimum temperature (for MDMT assessment)
double minTemp = results.stream()
    .mapToDouble(r -> r[2])
    .min()
    .orElse(Double.NaN);

System.out.println("Minimum temperature: " + minTemp + " °C");

Fire Case Depressurization

// Add heat input for fire case
vessel.setHeatInput(1000000.0);  // 1 MW fire load

for (double t = 0; t <= 900; t += dt) {
    blowdown.runTransient(dt, id);

    // Check for two-phase relief (wetted surface)
    double liquidFraction = vessel.getLiquidVolumeFraction();
    if (liquidFraction > 0) {
        System.out.println("Two-phase relief at t=" + t);
    }
}

Calculation Identifiers

Why IDs Matter

The calculation identifier (UUID) ensures all equipment in a process system is synchronized during transient runs:

UUID calcId = UUID.randomUUID();

// All equipment should have same ID after runTransient
process.runTransient(dt, calcId);

// Verify synchronization
for (ProcessEquipmentInterface eq : process.getUnitOperations()) {
    if (!eq.getCalculationIdentifier().equals(calcId)) {
        System.err.println("Equipment out of sync: " + eq.getName());
    }
}

Detecting Stale States

// After transient step, check all equipment updated
UUID expectedId = process.getCalculationIdentifier();

boolean allSynced = true;
for (ProcessEquipmentInterface eq : process.getUnitOperations()) {
    if (!eq.getCalculationIdentifier().equals(expectedId)) {
        allSynced = false;
        System.err.println("Stale: " + eq.getName());
    }
}

if (!allSynced) {
    // Reinitialize process
    process.run();
}

Best Practices

1. Always Initialize with Steady-State

// CORRECT: Initialize first
process.run();  // Steady-state initialization
separator.setCalculateSteadyState(false);
process.runTransient(dt, id);

// WRONG: Skip initialization
separator.setCalculateSteadyState(false);
process.runTransient(dt, id);  // May fail or give wrong results

2. Set Transient Mode on Dynamic Equipment Only

// Equipment that needs transient mode:
separator.setCalculateSteadyState(false);  // Has holdup
tank.setCalculateSteadyState(false);       // Has volume
pipeline.setCalculateSteadyState(false);   // Has segments

// Equipment that typically stays steady-state:
// - Streams (boundary conditions)
// - Heat exchangers (fast thermal equilibrium)
// - Compressors (unless modeling inertia)

3. Match Controller Time Constants to Time Step

double dt = 1.0;  // 1 second time step

// Controller integral time should be >> dt
double Ti = 100.0;  // 100 seconds >> 1 second

// Avoid Ti close to dt (causes oscillation)
// Ti = 1.0 would be problematic with dt = 1.0

4. Log Key Variables for Debugging

try (PrintWriter log = new PrintWriter("transient.csv")) {
    log.println("time,pressure,temperature,level,flow");

    for (double t = 0; t < tEnd; t += dt) {
        process.runTransient(dt, id);

        log.printf("%.1f,%.2f,%.2f,%.3f,%.1f%n",
            t,
            separator.getPressure(),
            separator.getTemperature("C"),
            separator.getLiquidLevel(),
            gasValve.getFlowRate("kg/hr"));
    }
}

5. Use try-finally for Clean Shutdown

try {
    process.run();
    separator.setCalculateSteadyState(false);

    for (int step = 0; step < 100; step++) {
        process.runTransient(dt, id);
    }
} finally {
    // Reset to steady-state for future runs
    separator.setCalculateSteadyState(true);
}

Examples

Example 1: Feed Rate Step Change

// Initialize at 1000 kg/hr
feed.setFlowRate(1000.0, "kg/hr");
process.run();
separator.setCalculateSteadyState(false);

// Step change to 1500 kg/hr
feed.setFlowRate(1500.0, "kg/hr");

// Observe response
for (int step = 0; step < 300; step++) {
    process.runTransient(1.0, id);
    System.out.printf("t=%d s, Level=%.3f m%n", step, separator.getLiquidLevel());
}

Example 2: Slug Arrival at Separator

See transient_slug_separator_control_example.md

Example 3: Compressor Startup

Compressor comp = new Compressor("K-100", feed);
comp.setDynamicSimulationEnabled(true);
comp.setRotorInertia(150.0);

// Start from standby
comp.setState(CompressorState.STANDBY);
process.run();

// Initiate startup
comp.startStartupSequence();

for (int step = 0; step < 600; step++) {
    process.runTransient(0.5, id);

    System.out.printf("t=%.1f s, Speed=%.0f RPM, State=%s%n",
        step * 0.5,
        comp.getSpeed(),
        comp.getState());

    if (comp.getState() == CompressorState.RUNNING) {
        System.out.println("Startup complete at t=" + step * 0.5 + " s");
        break;
    }
}

Core Transient Documentation

Equipment-Specific

Interactive Notebooks (Google Colab)

These Colab notebooks provide hands-on dynamic simulation examples:

Notebook Description
Dynamic Simulation Basics Introduction to transient simulation
Dynamic Compressor Compressor dynamics and control
Single Component Dynamics Single component transient behavior
Dynamic Separator Separator level dynamics and control

Local Examples

Process Logic

Transient Simulation

Transient process simulation patterns from tests

Dynamic process behavior in NeqSim is validated through ProcessSystemRunTransientTest, which assembles streams, valves, separators, transmitters, and controllers before stepping a transient solver. Reusing the tested scaffolding ensures that custom flowsheets converge and maintain synchronized calculation identifiers across unit operations.

Minimal transient loop with flow control

The first scenario creates a single feed stream, lets it down through a valve into a separator, and attaches a flow controller to the inlet valve based on a volume-flow transmitter.【F:src/test/java/neqsim/process/processmodel/ProcessSystemRunTransientTest.java†L58-L120】 Key steps reproduced below:

  1. Build a thermodynamic system with SRK EOS and set a mixing rule.
  2. Define a Stream, set mass flow and pressure, and connect it to a ThrottlingValve with a target outlet pressure.
  3. Route the valve outlet to a Separator, configure geometry (diameter, length) and initial liquid level.
  4. Add downstream valves for gas and liquid outlets to define back-pressure targets.
  5. Attach a VolumeFlowTransmitter to the inlet stream, wire it to a ControllerDeviceBaseClass, and assign the controller to the inlet valve.
  6. Run a steady-state initialization (p.run()), choose a transient timestep, and iterate runTransient() to observe controller action converging toward the setpoint (73.5 kg/hr in the test).

The assertions in the test check that every unit operation shares the same calculation identifier during the loop and that the transmitter stabilizes near the requested flow, confirming correct coupling of controller logic and transport equations.【F:src/test/java/neqsim/process/processmodel/ProcessSystemRunTransientTest.java†L106-L120】 Use this template when debugging control loops or valve responses in your own cases.

Level- and pressure-controlled separator case

A second scenario adds a purge stream to the separator, introduces level and pressure transmitters, and binds each to a dedicated controller that manipulates the liquid and gas outlet valves respectively.【F:src/test/java/neqsim/process/processmodel/ProcessSystemRunTransientTest.java†L122-L232】 After a steady-state start, the process is marched forward with a 10-second timestep and the level transmitter reading is checked against the 0.45 m setpoint, demonstrating how controller gains drive the separator toward balanced holdup.

When replicating this pattern:

Why calculation identifiers matter

Throughout the transient loops the test asserts that each unit operation's getCalculationIdentifier() matches the process system's identifier.【F:src/test/java/neqsim/process/processmodel/ProcessSystemRunTransientTest.java†L114-L118】【F:src/test/java/neqsim/process/processmodel/ProcessSystemRunTransientTest.java†L221-L229】 This guards against stale states or partial updates when complex equipment is added. If you see divergence, reinitialize the process system or inspect units for disabled steady-state flags.

Chapter 22: Mechanical Design

Mechanical Design

Mechanical Design Framework

NeqSim provides a comprehensive mechanical design framework for sizing and specifying process equipment according to industry standards. This document describes the architecture, usage patterns, and JSON export capabilities.

Overview

The mechanical design system calculates:

Architecture

Class Hierarchy

MechanicalDesign (base class)
├── SeparatorMechanicalDesign      → ASME VIII / API 12J
├── GasScrubberMechanicalDesign    → ASME VIII / API 12J
├── CompressorMechanicalDesign     → API 617
├── PumpMechanicalDesign           → API 610
├── ValveMechanicalDesign          → IEC 60534 / ANSI/ISA-75
├── ExpanderMechanicalDesign       → API 617
├── TankMechanicalDesign           → API 650/620
├── HeatExchangerMechanicalDesign  → TEMA
├── PipelineMechanicalDesign       → ASME B31.3
├── AdsorberMechanicalDesign       → ASME VIII
├── AbsorberMechanicalDesign       → ASME VIII
├── EjectorMechanicalDesign        → HEI
└── SafetyValveMechanicalDesign    → API 520/521

Response Classes for JSON Export

MechanicalDesignResponse (base class)
├── CompressorMechanicalDesignResponse
├── PumpMechanicalDesignResponse
├── ValveMechanicalDesignResponse
├── SeparatorMechanicalDesignResponse
└── HeatExchangerMechanicalDesignResponse

System-Level Aggregation

SystemMechanicalDesign
└── Aggregates all equipment in a ProcessSystem
    ├── Total weights and volumes
    ├── Weight breakdown by equipment type
    ├── Weight breakdown by discipline
    ├── Utility requirements summary
    └── Equipment list with design parameters

Usage Patterns

Individual Equipment Design

// Create and run process equipment
SystemInterface fluid = new SystemSrkEos(298.0, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.1);
fluid.setMixingRule("classic");

Stream inlet = new Stream("feed", fluid);
inlet.setFlowRate(10000.0, "kg/hr");
inlet.run();

Separator separator = new Separator("V-100", inlet);
separator.run();

// Access mechanical design
MechanicalDesign mecDesign = separator.getMechanicalDesign();

// Set design standards (optional - uses defaults if not specified)
mecDesign.setCompanySpecificDesignStandards("Equinor");

// Calculate design
mecDesign.calcDesign();

// Access results
double weight = mecDesign.getWeightTotal();           // kg
double wallThickness = mecDesign.getWallThickness();  // mm
double innerDiameter = mecDesign.getInnerDiameter();  // m
double length = mecDesign.getTantanLength();          // m
double designPressure = mecDesign.getMaxDesignPressure(); // bara

// Display results in GUI
mecDesign.displayResults();

System-Wide Mechanical Design

// Build a process system
ProcessSystem process = new ProcessSystem();
process.add(inlet);
process.add(separator);
process.add(gasStream);
process.add(compressor);
process.add(cooler);
process.add(outlet);
process.run();

// Create system mechanical design
SystemMechanicalDesign sysMecDesign = new SystemMechanicalDesign(process);

// Set company standards for all equipment
sysMecDesign.setCompanySpecificDesignStandards("Equinor");

// Run design calculations for all equipment
sysMecDesign.runDesignCalculation();

// Access aggregated results
double totalWeight = sysMecDesign.getTotalWeight();           // kg
double totalVolume = sysMecDesign.getTotalVolume();           // m³
double plotSpace = sysMecDesign.getTotalPlotSpace();          // m²
double powerRequired = sysMecDesign.getTotalPowerRequired();  // kW
double heatingDuty = sysMecDesign.getTotalHeatingDuty();      // kW
double coolingDuty = sysMecDesign.getTotalCoolingDuty();      // kW

// Get breakdowns
Map<String, Double> weightByType = sysMecDesign.getWeightByEquipmentType();
Map<String, Double> weightByDiscipline = sysMecDesign.getWeightByDiscipline();
Map<String, Integer> countByType = sysMecDesign.getEquipmentCountByType();

// Print summary report
System.out.println(sysMecDesign.generateSummaryReport());

JSON Export

Exporting Individual Equipment

// Calculate design
separator.getMechanicalDesign().calcDesign();

// Export to JSON
String json = separator.getMechanicalDesign().toJson();

// Example output:
/*
{
  "name": "V-100",
  "equipmentType": "Separator",
  "equipmentClass": "Separator",
  "designStandard": "ASME VIII / API 12J",
  "isSystemLevel": false,
  "totalWeight": 15420.5,
  "vesselWeight": 8500.0,
  "internalsWeight": 1200.0,
  "pipingWeight": 2100.0,
  "eiWeight": 1500.0,
  "structuralWeight": 2120.5,
  "maxDesignPressure": 55.0,
  "maxDesignTemperature": 80.0,
  "innerDiameter": 2.4,
  "tangentLength": 7.2,
  "wallThickness": 28.5,
  "moduleLength": 10.0,
  "moduleWidth": 5.0,
  "moduleHeight": 4.5,
  ...
}
*/

Exporting System-Wide Design

SystemMechanicalDesign sysMecDesign = new SystemMechanicalDesign(process);
sysMecDesign.runDesignCalculation();

String json = sysMecDesign.toJson();

// Example output:
/*
{
  "isSystemLevel": true,
  "processName": "Gas Processing Unit",
  "equipmentCount": 12,
  "totalWeight": 185000.0,
  "totalVolume": 450.5,
  "totalPlotSpace": 1200.0,
  "totalPowerRequired": 2500.0,
  "totalPowerRecovered": 150.0,
  "netPower": 2350.0,
  "totalHeatingDuty": 500.0,
  "totalCoolingDuty": 1800.0,
  "footprintLength": 40.0,
  "footprintWidth": 30.0,
  "maxHeight": 15.0,
  "weightByType": {
    "Separator": 45000.0,
    "Compressor": 85000.0,
    "HeatExchanger": 25000.0,
    "Valve": 5000.0,
    "Pump": 12000.0,
    "Other": 13000.0
  },
  "weightByDiscipline": {
    "Mechanical": 120000.0,
    "Piping": 35000.0,
    "E&I": 18000.0,
    "Structural": 12000.0
  },
  "equipmentList": [
    {
      "name": "V-100",
      "type": "Separator",
      "weight": 15420.5,
      "designPressure": 55.0,
      "designTemperature": 80.0,
      "power": 0.0,
      "duty": 0.0,
      "dimensions": "ID 2.4m x TT 7.2m"
    },
    ...
  ]
}
*/

Using Specialized Response Classes

For equipment-specific data, use the typed response:

// Compressor-specific response
CompressorMechanicalDesignResponse response = 
    (CompressorMechanicalDesignResponse) compressor.getMechanicalDesign().getResponse();

int stages = response.getNumberOfStages();
double impellerDiameter = response.getImpellerDiameter();  // mm
double tipSpeed = response.getTipSpeed();                   // m/s
double driverPower = response.getDriverPower();             // kW
double tripSpeed = response.getTripSpeed();                 // rpm

// Valve-specific response
ValveMechanicalDesignResponse valveResponse = 
    (ValveMechanicalDesignResponse) valve.getMechanicalDesign().getResponse();

int ansiClass = valveResponse.getAnsiPressureClass();
double cvMax = valveResponse.getCvMax();
double faceToFace = valveResponse.getFaceToFace();  // mm
String valveType = valveResponse.getValveType();

Round-Trip Parsing

// Export to JSON
String json = sysMecDesign.toJson();

// Parse back to object
MechanicalDesignResponse parsed = MechanicalDesignResponse.fromJson(json);

// Access parsed data
double weight = parsed.getTotalWeight();
boolean isSystem = parsed.isSystemLevel();

Merging with Process Data

// Get mechanical design response
MechanicalDesignResponse mecResponse = sysMecDesign.getResponse();

// Get process simulation JSON
String processJson = process.toJson();

// Merge into combined document
String combined = mecResponse.mergeWithEquipmentJson(processJson);

// Result has both "processData" and "mechanicalDesign" sections

Equipment-Specific Design Standards

Separators (API 12J / ASME VIII)

SeparatorMechanicalDesign sepDesign = 
    (SeparatorMechanicalDesign) separator.getMechanicalDesign();

// Key parameters
double gasLoadFactor = sepDesign.getGasLoadFactor();      // K-factor
double retentionTime = sepDesign.getRetentionTime();      // seconds
double liquidLevelFraction = sepDesign.getFg();           // Fg factor

Design calculations include:

Compressors (API 617)

CompressorMechanicalDesign compDesign = 
    (CompressorMechanicalDesign) compressor.getMechanicalDesign();

// Key parameters
int stages = compDesign.getNumberOfStages();
double headPerStage = compDesign.getHeadPerStage();       // kJ/kg
double impellerDia = compDesign.getImpellerDiameter();    // mm
double tipSpeed = compDesign.getTipSpeed();                // m/s
double driverPower = compDesign.getDriverPower();          // kW

Design calculations include:

Pumps (API 610)

PumpMechanicalDesign pumpDesign = 
    (PumpMechanicalDesign) pump.getMechanicalDesign();

// Key parameters
double specificSpeed = pumpDesign.getSpecificSpeed();
double npshRequired = pumpDesign.getNpshRequired();        // m
double impellerDia = pumpDesign.getImpellerDiameter();     // mm
double driverPower = pumpDesign.getDriverPower();          // kW

Design calculations include:

Valves (IEC 60534)

ValveMechanicalDesign valveDesign = 
    (ValveMechanicalDesign) valve.getMechanicalDesign();

// Key parameters
double cvMax = valveDesign.getValveCvMax();
int ansiClass = valveDesign.getAnsiPressureClass();
double faceToFace = valveDesign.getFaceToFace();           // mm
double actuatorThrust = valveDesign.getRequiredActuatorThrust(); // N

Design calculations include:

Heat Exchangers (TEMA)

Design calculations include:

Tanks (API 650/620)

Design calculations include:

Weight Breakdown Categories

By Equipment Type

By Discipline

Design Margins

The framework applies industry-standard margins:

Parameter Margin Standard
Design Pressure +10% above max operating ASME VIII
Design Temperature +30°C above max operating ASME VIII
Driver Power (small) +25% for < 22 kW API 610/617
Driver Power (medium) +15% for 22-75 kW API 610/617
Driver Power (large) +10% for > 75 kW API 610/617
Wall Thickness +CA (corrosion allowance) ASME VIII

Integration with Cost Estimation

Each mechanical design class has an associated cost estimation class:

// Access cost estimate
UnitCostEstimateBaseClass costEstimate = mecDesign.getCostEstimate();
double equipmentCost = costEstimate.getEquipmentCost();    // USD
double installedCost = costEstimate.getInstalledCost();    // USD

Best Practices

  1. Always run equipment before calculating design - The mechanical design uses process conditions from the simulation.

  2. Set design standards early - Call setCompanySpecificDesignStandards() before calcDesign().

  3. Use system-level design for complete estimates - SystemMechanicalDesign handles all equipment consistently.

  4. Export JSON for documentation - The toJson() method provides comprehensive, structured output.

  5. Verify critical parameters - Check that design pressure/temperature exceed operating conditions.

Example: Complete Workflow

// 1. Create fluid system
SystemInterface fluid = new SystemSrkEos(298.0, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.setMixingRule("classic");

// 2. Build process
Stream feed = new Stream("feed", fluid);
feed.setFlowRate(50000.0, "kg/hr");

Separator separator = new Separator("V-100", feed);

Stream gas = new Stream("gas", separator.getGasOutStream());

Compressor compressor = new Compressor("K-100", gas);
compressor.setOutletPressure(80.0, "bara");

Cooler cooler = new Cooler("E-100", compressor.getOutletStream());
cooler.setOutTemperature(40.0, "C");

ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(gas);
process.add(compressor);
process.add(cooler);
process.run();

// 3. Calculate mechanical design
SystemMechanicalDesign sysMecDesign = new SystemMechanicalDesign(process);
sysMecDesign.setCompanySpecificDesignStandards("Equinor");
sysMecDesign.runDesignCalculation();

// 4. Generate reports
System.out.println(sysMecDesign.generateSummaryReport());

// 5. Export JSON for documentation/integration
String json = sysMecDesign.toJson();
Files.write(Paths.get("mechanical_design.json"), json.getBytes());

// 6. Access specific equipment details
CompressorMechanicalDesignResponse compResponse = 
    (CompressorMechanicalDesignResponse) compressor.getMechanicalDesign().getResponse();
System.out.println("Compressor stages: " + compResponse.getNumberOfStages());
System.out.println("Driver power: " + compResponse.getDriverPower() + " kW");

See Also

Design Standards

Mechanical Design Standards in NeqSim

Overview

NeqSim provides comprehensive support for international design standards used in process equipment mechanical design. The framework enables engineers to apply company-specific and international standards consistently across all equipment in a process simulation.

Supported Design Standards

StandardType Enumeration

The StandardType enum catalogs 30+ international design standards organized by category:

Category Standards
Pressure Vessel Codes ASME Section VIII Div.1/2, EN 13445, PD 5500, DNV-OS-F101
Piping Codes ASME B31.3, ASME B31.4, ASME B31.8, EN 13480, NORSOK L-002
Process Design NORSOK P-001, NORSOK P-002, API RP 14E, API RP 521
Material Standards ASTM A516, ASTM A106, EN 10028, NORSOK M-001
Safety Standards API RP 520, API RP 521, ISO 23251

Using StandardType

import neqsim.process.mechanicaldesign.designstandards.StandardType;

// Get standard by code
StandardType standard = StandardType.fromCode("ASME-VIII-1");

// Get standard properties
String code = standard.getCode();           // "ASME-VIII-1"
String name = standard.getName();           // "ASME Section VIII Division 1"
String version = standard.getDefaultVersion(); // "2023"
String category = standard.getDesignStandardCategory(); // "pressure vessel design code"

// Check equipment applicability
boolean applies = standard.appliesTo("separator"); // true
boolean applies2 = standard.appliesTo("pump");     // false

// Get all standards for an equipment type
List<StandardType> applicable = StandardType.getApplicableStandards("compressor");

Standard Categories

NeqSim uses category-based standard assignment to ensure appropriate standards are applied to each equipment type:

Category Key Description Example Standards
pressure vessel design code Pressure containment design ASME VIII, EN 13445
separator process design Separator sizing rules NORSOK P-002, API 12J
compressor design Compressor design requirements API 617, API 618
pipeline design codes Pipeline design DNV-OS-F101, ASME B31.4
valve design Valve sizing and selection API 6D, EN ISO 10497
material plate design Plate material selection ASTM A516, EN 10028
material pipe design Pipe material selection ASTM A106, API 5L

StandardRegistry

The StandardRegistry class provides factory methods for creating DesignStandard instances:

import neqsim.process.mechanicaldesign.designstandards.StandardRegistry;
import neqsim.process.mechanicaldesign.designstandards.DesignStandard;

// Create a design standard from StandardType
DesignStandard standard = StandardRegistry.createStandard(StandardType.ASME_VIII_DIV1);

// Create with specific version
DesignStandard standard2 = StandardRegistry.createStandard(StandardType.NORSOK_P002, "Rev 3");

// Get recommended standards for equipment
List<StandardType> recommended = StandardRegistry.getRecommendedStandards("separator", "Equinor");

Applying Standards to Equipment

Single Equipment

import neqsim.process.equipment.separator.Separator;
import neqsim.process.mechanicaldesign.MechanicalDesign;
import neqsim.process.mechanicaldesign.designstandards.StandardType;

// Create equipment
Separator separator = new Separator("HP Separator", feedStream);

// Get mechanical design
MechanicalDesign mechDesign = separator.getMechanicalDesign();

// Apply single standard
mechDesign.setDesignStandard(StandardType.ASME_VIII_DIV1);

// Apply standard with version
mechDesign.setDesignStandard(StandardType.NORSOK_P002, "Rev 3");

// Apply multiple standards
List<StandardType> standards = Arrays.asList(
    StandardType.ASME_VIII_DIV1,
    StandardType.NORSOK_P002,
    StandardType.ASTM_A516
);
mechDesign.setDesignStandards(standards);

System-Wide Standards

import neqsim.process.mechanicaldesign.SystemMechanicalDesign;
import neqsim.process.processmodel.ProcessSystem;

// Create process system
ProcessSystem process = new ProcessSystem();
process.add(separator);
process.add(compressor);
process.add(heatExchanger);

// Apply company standards to all equipment
SystemMechanicalDesign sysMechDesign = new SystemMechanicalDesign(process);
sysMechDesign.setCompanySpecificDesignStandards("Equinor");

// Run design calculations
sysMechDesign.runDesignCalculation();

Design Standard Hierarchy

Standards are applied hierarchically based on specificity:

1. Equipment-specific standard (highest priority)
   ↓
2. TORG project standards
   ↓
3. Company default standards
   ↓
4. NeqSim default standards (lowest priority)

Available Design Standard Classes

NeqSim includes specialized design standard implementations:

Class Purpose
PressureVesselDesignStandard ASME/EN pressure vessel calculations
SeparatorDesignStandard Separator sizing per NORSOK/API
CompressorDesignStandard Compressor design per API 617/618
PipelineDesignStandard Pipeline wall thickness per DNV/ASME
MaterialPlateDesignStandard Plate material properties
MaterialPipeDesignStandard Pipe material properties
JointEfficiencyPlateStandard Weld joint efficiency factors
GasScrubberDesignStandard Gas scrubber sizing rules
AdsorptionDehydrationDesignStandard Dehydration unit design

Example: Complete Standard Application

import neqsim.process.equipment.separator.Separator;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.mechanicaldesign.SystemMechanicalDesign;
import neqsim.process.mechanicaldesign.designstandards.StandardType;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;

// Create fluid
SystemInterface fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.setMixingRule("classic");

// Create feed stream
Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(10000, "kg/hr");
feed.setTemperature(25, "C");
feed.setPressure(50, "bara");

// Create equipment with standards
Separator separator = new Separator("HP Separator", feed);
separator.getMechanicalDesign().setDesignStandard(StandardType.ASME_VIII_DIV1);
separator.getMechanicalDesign().setDesignStandard(StandardType.NORSOK_P002);

Compressor compressor = new Compressor("Export Compressor", separator.getGasOutStream());
compressor.setOutletPressure(150, "bara");
compressor.getMechanicalDesign().setDesignStandard(StandardType.API_617);

// Build process
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(compressor);
process.run();

// Run mechanical design
SystemMechanicalDesign sysMechDesign = new SystemMechanicalDesign(process);
sysMechDesign.runDesignCalculation();

// Get results
System.out.println("Total Weight: " + sysMechDesign.getTotalWeight() + " kg");
System.out.println("Total Volume: " + sysMechDesign.getTotalVolume() + " m³");

See Also

Design Database

Mechanical Design Database and Data Sources

Overview

NeqSim supports loading mechanical design parameters from various data sources including databases and CSV files. This allows organizations to maintain centralized repositories of design data, material properties, and company-specific standards.

Data Source Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    MechanicalDesignDataSource                    │
│                         (Interface)                              │
├─────────────────────────────────────────────────────────────────┤
│                              │                                   │
│         ┌────────────────────┼────────────────────┐             │
│         ▼                    ▼                    ▼             │
│ ┌───────────────┐  ┌─────────────────┐  ┌──────────────────┐   │
│ │ Database      │  │ CSV Data        │  │ Standard-Based   │   │
│ │ DataSource    │  │ Source          │  │ CSV DataSource   │   │
│ └───────────────┘  └─────────────────┘  └──────────────────┘   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

MechanicalDesignDataSource Interface

The core interface for all design data sources:

public interface MechanicalDesignDataSource {

    /**
     * Get a design parameter value.
     * @param category Parameter category (e.g., "material", "safety_factor")
     * @param parameterName Parameter name (e.g., "tensile_strength")
     * @param equipmentType Equipment type filter
     * @return Parameter value as double
     */
    double getParameter(String category, String parameterName, String equipmentType);

    /**
     * Get a string property.
     */
    String getProperty(String category, String propertyName, String equipmentType);

    /**
     * Check if data source is available.
     */
    boolean isAvailable();

    /**
     * Get the standard type this data source provides data for.
     */
    StandardType getStandardType();
}

Database Data Source

Configuration

The DatabaseMechanicalDesignDataSource connects to the NeqSim database:

import neqsim.process.mechanicaldesign.data.DatabaseMechanicalDesignDataSource;

// Create database source (uses default neqsim database)
DatabaseMechanicalDesignDataSource dbSource = new DatabaseMechanicalDesignDataSource();

// Or specify connection
DatabaseMechanicalDesignDataSource dbSource = new DatabaseMechanicalDesignDataSource(
    "jdbc:derby:neqsimthermodatabase",
    "TechnicalRequirements_Process"
);

Database Schema

The primary table TechnicalRequirements_Process stores design parameters:

CREATE TABLE TechnicalRequirements_Process (
    ID              INTEGER PRIMARY KEY,
    COMPANY         VARCHAR(50),
    CATEGORY        VARCHAR(100),
    PARAMETER_NAME  VARCHAR(100),
    EQUIPMENT_TYPE  VARCHAR(50),
    VALUE_NUMERIC   DOUBLE,
    VALUE_TEXT      VARCHAR(255),
    UNIT            VARCHAR(20),
    STANDARD_CODE   VARCHAR(20),
    VERSION         VARCHAR(10),
    NOTES           VARCHAR(500)
);

-- Example data
INSERT INTO TechnicalRequirements_Process VALUES
(1, 'Equinor', 'safety_factor', 'pressure_margin', 'separator', 1.10, NULL, '-', 'NORSOK-P002', 'Rev3', NULL),
(2, 'Equinor', 'material', 'min_wall_thickness', 'pressure_vessel', 6.0, NULL, 'mm', 'ASME-VIII-1', '2023', NULL),
(3, 'Equinor', 'sizing', 'liquid_retention_time', 'separator', 180, NULL, 's', 'NORSOK-P002', 'Rev3', 'Minimum 3 minutes');

Querying the Database

// Get numeric parameter
double pressureMargin = dbSource.getParameter("safety_factor", "pressure_margin", "separator");

// Get text property
String material = dbSource.getProperty("material", "default_plate_grade", "pressure_vessel");

// Check availability
if (dbSource.isAvailable()) {
    // Use database source
}

CSV Data Source

Basic CSV Format

Create CSV files for design parameters:

File: designdata/company_standards.csv

category,parameter_name,equipment_type,value_numeric,value_text,unit,standard_code
safety_factor,pressure_margin,separator,1.10,,−,NORSOK-P002
safety_factor,temperature_margin,all,25.0,,C,NORSOK-P001
material,default_plate_grade,pressure_vessel,,SA-516-70,,ASTM-A516
sizing,liquid_retention_time,separator,180.0,,s,NORSOK-P002
sizing,gas_velocity_factor,scrubber,0.07,,-,API-12J

Using CSV Data Source

import neqsim.process.mechanicaldesign.data.StandardBasedCsvDataSource;
import neqsim.process.mechanicaldesign.designstandards.StandardType;

// Load from file path
StandardBasedCsvDataSource csvSource = new StandardBasedCsvDataSource(
    "path/to/company_standards.csv",
    StandardType.NORSOK_P002
);

// Load from classpath resource
StandardBasedCsvDataSource csvSource = new StandardBasedCsvDataSource(
    "designdata/equinor_standards.csv",
    StandardType.NORSOK_P002
);

// Get parameters
double retentionTime = csvSource.getParameter("sizing", "liquid_retention_time", "separator");

Standard-Based CSV Structure

For standards-specific data, use the enhanced format:

File: designdata/asme_viii_parameters.csv

standard_code,category,parameter_name,equipment_type,value,unit,version,notes
ASME-VIII-1,joint_efficiency,full_radiograph,all,1.0,-,2023,Category A and B joints
ASME-VIII-1,joint_efficiency,spot_radiograph,all,0.85,-,2023,Category A and B joints
ASME-VIII-1,joint_efficiency,no_radiograph,all,0.70,-,2023,Category A and B joints
ASME-VIII-1,material,min_tensile_strength,SA-516-70,485,MPa,2023,Grade 70
ASME-VIII-1,material,allowable_stress,SA-516-70,138,MPa,2023,At 100°C

Registering Data Sources

With MechanicalDesign

import neqsim.process.mechanicaldesign.MechanicalDesign;

MechanicalDesign mechDesign = separator.getMechanicalDesign();

// Add database source
mechDesign.addDataSource(new DatabaseMechanicalDesignDataSource());

// Add CSV source
mechDesign.addDataSource(new StandardBasedCsvDataSource(
    "designdata/norsok_p002.csv",
    StandardType.NORSOK_P002
));

// Data sources are queried in order added (first match wins)

With SystemMechanicalDesign

import neqsim.process.mechanicaldesign.SystemMechanicalDesign;

SystemMechanicalDesign sysMech = new SystemMechanicalDesign(process);

// Configure data sources for entire system
sysMech.addDataSource(new DatabaseMechanicalDesignDataSource());
sysMech.addDataSource(new StandardBasedCsvDataSource("company_stds.csv", StandardType.NORSOK_P001));

Default Data Location

NeqSim looks for design data in these locations:

  1. Classpath resources: src/main/resources/designdata/
  2. Working directory: ./designdata/
  3. User home: ~/.neqsim/designdata/

Provided Default Files

File Description
asme_viii_materials.csv ASME Section VIII material allowables
norsok_p002_sizing.csv NORSOK P-002 sizing parameters
api_617_compressors.csv API 617 compressor requirements
dnv_os_f101_pipeline.csv DNV pipeline design factors

Creating Custom Data Sources

Implement the MechanicalDesignDataSource interface:

public class MyCompanyDataSource implements MechanicalDesignDataSource {

    private Map<String, Double> parameters = new HashMap<>();

    @Override
    public double getParameter(String category, String parameterName, String equipmentType) {
        String key = category + ":" + parameterName + ":" + equipmentType;
        return parameters.getOrDefault(key, Double.NaN);
    }

    @Override
    public String getProperty(String category, String propertyName, String equipmentType) {
        // Implementation
        return null;
    }

    @Override
    public boolean isAvailable() {
        return true;
    }

    @Override
    public StandardType getStandardType() {
        return StandardType.NORSOK_P001;
    }
}

Data Validation

NeqSim validates data source values:

import neqsim.process.mechanicaldesign.data.DataSourceValidator;

// Validate a data source
DataSourceValidator validator = new DataSourceValidator();
List<String> errors = validator.validate(csvSource);

if (!errors.isEmpty()) {
    for (String error : errors) {
        System.err.println("Validation error: " + error);
    }
}

Best Practices

1. Version Control Your Data

Keep CSV files in version control alongside your simulations:

project/
├── simulations/
│   └── hp_separator_sizing.java
├── designdata/
│   ├── project_standards.csv
│   └── material_data.csv
└── README.md

2. Use Standard Codes Consistently

Always reference standards by their StandardType code:

# Good
standard_code,category,parameter
NORSOK-P002,sizing,liquid_retention

# Avoid
standard_code,category,parameter  
NORSOK P-002,sizing,liquid_retention
Norsok-P002,sizing,liquid_retention

3. Document Units

Always include units in your data:

parameter_name,value,unit
min_wall_thickness,6.0,mm
design_pressure,50.0,barg
temperature_margin,25.0,C

4. Layer Data Sources

Use multiple sources with appropriate priority:

// Priority order: company-specific → project-specific → defaults
mechDesign.addDataSource(new CsvDataSource("company_standards.csv"));  // 1st priority
mechDesign.addDataSource(new CsvDataSource("project_overrides.csv"));  // 2nd priority
mechDesign.addDataSource(new DatabaseMechanicalDesignDataSource());    // 3rd priority (fallback)

See Also

TORG Integration

TORG (Technical Requirements Document) Integration

Overview

A TORG (Technical Requirements Document, also known as TR or Technical Requirements Governing Document) defines the standards, methods, and requirements to be used in process design for a specific project. NeqSim provides comprehensive support for loading, managing, and applying TORG requirements across process simulations.

TORG Framework Architecture

┌──────────────────────────────────────────────────────────────────────────────┐
│                              TORG Framework                                   │
├──────────────────────────────────────────────────────────────────────────────┤
│                                                                               │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                    TechnicalRequirementsDocument                         │ │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌─────────────┐  │ │
│  │  │ Project Info │  │ Standards    │  │ Environmental│  │ Safety      │  │ │
│  │  │ - projectId  │  │ - pressure   │  │ Conditions   │  │ Factors     │  │ │
│  │  │ - company    │  │ - separator  │  │ - minTemp    │  │ - pressure  │  │ │
│  │  │ - revision   │  │ - pipeline   │  │ - maxTemp    │  │ - corrosion │  │ │
│  │  └──────────────┘  └──────────────┘  │ - seismic    │  └─────────────┘  │ │
│  │                                      └──────────────┘                    │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                     │                                         │
│                                     ▼                                         │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                           TorgManager                                    │ │
│  │  - load(projectId)           - apply(torg, processSystem)               │ │
│  │  - loadAndApply(id, system)  - applyToEquipment(torg, equipment)        │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                     │                                         │
│          ┌──────────────────────────┼──────────────────────────┐             │
│          ▼                          ▼                          ▼             │
│  ┌───────────────┐         ┌───────────────┐         ┌───────────────┐      │
│  │ CsvTorgData   │         │ DatabaseTorg  │         │ Custom        │      │
│  │ Source        │         │ DataSource    │         │ DataSource    │      │
│  └───────────────┘         └───────────────┘         └───────────────┘      │
│                                                                               │
└──────────────────────────────────────────────────────────────────────────────┘

TechnicalRequirementsDocument Class

The TechnicalRequirementsDocument class represents a complete TORG with all project-specific requirements.

Core Properties

Property Description
projectId Unique project identifier (e.g., "JOHAN-SVERDRUP-01")
projectName Human-readable project name
companyIdentifier Company code (e.g., "EQUINOR")
revision Document revision (e.g., "Rev 3")
issueDate Document issue date
designLifeYears Design life in years

Nested Classes

EnvironmentalConditions

TechnicalRequirementsDocument.EnvironmentalConditions env = torg.getEnvironmentalConditions();

double minAmbient = env.getMinAmbientTemperature();  // °C
double maxAmbient = env.getMaxAmbientTemperature();  // °C
double minSeawater = env.getMinSeawaterTemperature(); // °C
double maxSeawater = env.getMaxSeawaterTemperature(); // °C
String seismicZone = env.getSeismicZone();
String location = env.getLocation();

SafetyFactors

TechnicalRequirementsDocument.SafetyFactors safety = torg.getSafetyFactors();

double pressureSF = safety.getPressureSafetyFactor();      // e.g., 1.10
double tempMargin = safety.getTemperatureSafetyMargin();   // °C
double corrosion = safety.getCorrosionAllowance();         // mm
double wallTol = safety.getWallThicknessTolerance();       // fraction
double loadFactor = safety.getLoadFactor();                // multiplier

MaterialSpecifications

TechnicalRequirementsDocument.MaterialSpecifications mats = torg.getMaterialSpecifications();

String plateMaterial = mats.getDefaultPlateMaterial();     // e.g., "SA-516-70"
String pipeMaterial = mats.getDefaultPipeMaterial();       // e.g., "API-5L-X65"
double minDesignTemp = mats.getMinDesignTemperature();     // °C
double maxDesignTemp = mats.getMaxDesignTemperature();     // °C
boolean impactTest = mats.isRequireImpactTesting();
String materialStd = mats.getMaterialStandard();           // e.g., "ASTM"

Creating a TORG Programmatically

Use the Builder pattern for flexible TORG creation:

import neqsim.process.mechanicaldesign.torg.TechnicalRequirementsDocument;
import neqsim.process.mechanicaldesign.designstandards.StandardType;

TechnicalRequirementsDocument torg = new TechnicalRequirementsDocument.Builder()
    // Project identification
    .projectId("TROLL-WEST-2025")
    .projectName("Troll West Field Development")
    .companyIdentifier("EQUINOR")
    .revision("Rev 2")
    .issueDate("2025-01-15")
    .designLifeYears(25)

    // Add design standards
    .addStandard("pressure_vessel", StandardType.ASME_VIII_DIV1)
    .addStandard("separator_process", StandardType.NORSOK_P002)
    .addStandard("pipeline", StandardType.DNV_OS_F101)
    .addStandard("compressor", StandardType.API_617)

    // Environmental conditions
    .environmentalConditions(new TechnicalRequirementsDocument.EnvironmentalConditions(
        -30.0,    // minAmbientTemp °C
        35.0,     // maxAmbientTemp °C
        2.0,      // minSeawaterTemp °C
        20.0,     // maxSeawaterTemp °C
        "Zone 1", // seismicZone
        "Norwegian Sea" // location
    ))

    // Safety factors
    .safetyFactors(new TechnicalRequirementsDocument.SafetyFactors(
        1.10,    // pressureSafetyFactor
        25.0,    // temperatureSafetyMargin °C
        3.0,     // corrosionAllowance mm
        0.125,   // wallThicknessTolerance (12.5%)
        1.0      // loadFactor
    ))

    // Material specifications
    .materialSpecifications(new TechnicalRequirementsDocument.MaterialSpecifications(
        "SA-516-70",  // defaultPlateMaterial
        "API-5L-X65", // defaultPipeMaterial
        -46.0,        // minDesignTemp °C (for impact testing)
        150.0,        // maxDesignTemp °C
        true,         // requireImpactTesting
        "ASTM"        // materialStandard
    ))

    .build();

Loading TORG from Data Sources

CSV Data Source

Create a CSV file for TORG data:

File: torg_projects.csv

project_id,project_name,company,revision,issue_date,design_life_years
TROLL-WEST-2025,Troll West Development,EQUINOR,Rev 2,2025-01-15,25
SNORRE-EXPANSION,Snorre Expansion Project,EQUINOR,Rev 1,2024-06-01,30

File: torg_standards.csv

project_id,category,standard_code,version,notes
TROLL-WEST-2025,pressure_vessel,ASME-VIII-1,2023,Primary code
TROLL-WEST-2025,separator_process,NORSOK-P002,Rev 3,Process sizing
TROLL-WEST-2025,pipeline,DNV-OS-F101,2021,Subsea pipelines

Loading from CSV:

import neqsim.process.mechanicaldesign.torg.CsvTorgDataSource;
import neqsim.process.mechanicaldesign.torg.TorgManager;

// Create CSV data source
CsvTorgDataSource csvSource = new CsvTorgDataSource("path/to/torg_projects.csv");

// Create manager and add source
TorgManager manager = new TorgManager();
manager.addDataSource(csvSource);

// Load TORG
Optional<TechnicalRequirementsDocument> optTorg = manager.load("TROLL-WEST-2025");
if (optTorg.isPresent()) {
    TechnicalRequirementsDocument torg = optTorg.get();
    System.out.println("Loaded: " + torg.getProjectName());
}

Database Data Source

Load TORG from the NeqSim database:

import neqsim.process.mechanicaldesign.torg.DatabaseTorgDataSource;

// Create database source
DatabaseTorgDataSource dbSource = new DatabaseTorgDataSource();

// Or with custom connection
DatabaseTorgDataSource dbSource = new DatabaseTorgDataSource(
    "jdbc:derby:neqsimthermodatabase"
);

// Add to manager
TorgManager manager = new TorgManager();
manager.addDataSource(dbSource);

// Load by company and project
Optional<TechnicalRequirementsDocument> torg = 
    manager.load("EQUINOR", "TROLL-WEST-2025");

Database Schema for TORG

-- Main TORG projects table
CREATE TABLE TORG_Projects (
    PROJECT_ID      VARCHAR(50) PRIMARY KEY,
    PROJECT_NAME    VARCHAR(200),
    COMPANY         VARCHAR(50),
    REVISION        VARCHAR(20),
    ISSUE_DATE      DATE,
    DESIGN_LIFE     INTEGER,
    STATUS          VARCHAR(20)
);

-- Standards mapping table
CREATE TABLE TORG_Standards (
    ID              INTEGER PRIMARY KEY,
    PROJECT_ID      VARCHAR(50) REFERENCES TORG_Projects(PROJECT_ID),
    CATEGORY        VARCHAR(50),
    STANDARD_CODE   VARCHAR(20),
    VERSION         VARCHAR(20),
    NOTES           VARCHAR(500)
);

-- Environmental conditions
CREATE TABLE TORG_Environment (
    PROJECT_ID          VARCHAR(50) PRIMARY KEY REFERENCES TORG_Projects(PROJECT_ID),
    MIN_AMBIENT_TEMP    DOUBLE,
    MAX_AMBIENT_TEMP    DOUBLE,
    MIN_SEAWATER_TEMP   DOUBLE,
    MAX_SEAWATER_TEMP   DOUBLE,
    SEISMIC_ZONE        VARCHAR(20),
    LOCATION            VARCHAR(100)
);

TorgManager

The TorgManager orchestrates TORG loading and application:

import neqsim.process.mechanicaldesign.torg.TorgManager;

TorgManager manager = new TorgManager();

// Add multiple data sources (checked in order)
manager.addDataSource(new CsvTorgDataSource("project_torg.csv"));
manager.addDataSource(new DatabaseTorgDataSource());

// Load TORG
Optional<TechnicalRequirementsDocument> optTorg = manager.load("PROJECT-001");

// Get active TORG (most recently loaded)
TechnicalRequirementsDocument activeTorg = manager.getActiveTorg();

// Load and apply in one step
boolean success = manager.loadAndApply("PROJECT-001", processSystem);

// Apply to specific equipment
manager.applyToEquipment(torg, separator);

Applying TORG to Process Systems

Automatic Application

import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.mechanicaldesign.torg.TorgManager;

// Build process system
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(compressor);
process.run();

// Load and apply TORG
TorgManager manager = new TorgManager();
manager.addDataSource(new CsvTorgDataSource("project_torg.csv"));

// This applies standards and parameters to all equipment
boolean applied = manager.loadAndApply("TROLL-WEST-2025", process);

if (applied) {
    System.out.println("TORG applied successfully");
}

Manual Application

// Load TORG first
Optional<TechnicalRequirementsDocument> optTorg = manager.load("TROLL-WEST-2025");

if (optTorg.isPresent()) {
    TechnicalRequirementsDocument torg = optTorg.get();

    // Apply to entire system
    manager.apply(torg, process);

    // Or apply to specific equipment
    manager.applyToEquipment(torg, separator);
    manager.applyToEquipment(torg, compressor);
}

What Gets Applied

When a TORG is applied to equipment, the following are configured:

Setting Source Applied To
Design standards torg.getStandard(category) mechDesign.setDesignStandard()
Pressure safety factor safetyFactors.getPressureSafetyFactor() mechDesign.setPressureMarginFactor()
Temperature margin safetyFactors.getTemperatureSafetyMargin() Design temperature calculation
Corrosion allowance safetyFactors.getCorrosionAllowance() mechDesign.setCorrosionAllowance()
Material grade materialSpecs.getDefaultPlateMaterial() mechDesign.setMaterialDesignStandard()
Design life torg.getDesignLifeYears() Fatigue and corrosion calculations

Generating TORG Summary

// Generate summary of applied TORG
String summary = manager.generateSummary(torg, process);
System.out.println(summary);

Output:

TORG Summary: TROLL-WEST-2025
=============================
Project: Troll West Development
Company: EQUINOR
Revision: Rev 2
Design Life: 25 years

Applied Standards:
  - pressure_vessel: ASME-VIII-1 (2023)
  - separator_process: NORSOK-P002 (Rev 3)
  - pipeline: DNV-OS-F101 (2021)

Equipment Coverage:
  - HP Separator: Standards applied
  - Export Compressor: Standards applied
  - Subsea Pipeline: Standards applied

Environmental Conditions:
  - Ambient Temperature: -30°C to 35°C
  - Seawater Temperature: 2°C to 20°C
  - Location: Norwegian Sea

Safety Factors:
  - Pressure: 1.10
  - Temperature Margin: 25°C
  - Corrosion Allowance: 3.0 mm

TORG Validation

Validate TORG completeness before applying:

import neqsim.process.mechanicaldesign.torg.TorgValidator;

TorgValidator validator = new TorgValidator();
List<String> issues = validator.validate(torg);

if (!issues.isEmpty()) {
    System.out.println("TORG validation issues:");
    for (String issue : issues) {
        System.out.println("  - " + issue);
    }
}

Best Practices

1. One TORG Per Project

Each project should have exactly one TORG that governs all design:

// Good - single source of truth
TorgManager manager = new TorgManager();
manager.loadAndApply("PROJECT-001", process);

// Avoid - multiple conflicting TORGs
// manager.loadAndApply("PROJECT-001-TOPSIDES", process);
// manager.loadAndApply("PROJECT-001-SUBSEA", process);

2. Version Control TORGs

Track TORG changes with revision numbers:

TechnicalRequirementsDocument torg = new TechnicalRequirementsDocument.Builder()
    .projectId("PROJECT-001")
    .revision("Rev 3")  // Increment for changes
    .issueDate("2025-01-06")
    .build();

3. Validate Before Production

Always validate TORG against equipment:

// Run validation before final design
boolean isComplete = manager.validateTorgCoverage(torg, process);
if (!isComplete) {
    throw new IllegalStateException("TORG does not cover all equipment types");
}

4. Document Deviations

Log any deviations from TORG requirements:

// If equipment requires non-standard settings
mechDesign.setDesignStandard(StandardType.ASME_VIII_DIV2);
logger.warn("Deviation from TORG: Using ASME VIII Div 2 instead of Div 1 for {}", 
    equipment.getName());

See Also

Field Development

Field Development Design Orchestration

Overview

The FieldDevelopmentDesignOrchestrator provides a unified workflow for coordinating process simulation, mechanical design, and design validation throughout a field development project lifecycle. It integrates TORG requirements, design standards, and design cases into a structured workflow.

Orchestrator Architecture

┌──────────────────────────────────────────────────────────────────────────────┐
│                    FieldDevelopmentDesignOrchestrator                         │
├──────────────────────────────────────────────────────────────────────────────┤
│                                                                               │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌────────────┐ │
│  │ Design Phase │───▶│ Design Cases │───▶│    TORG      │───▶│  Workflow  │ │
│  │  (Lifecycle) │    │ (Scenarios)  │    │ (Standards)  │    │  Execute   │ │
│  └──────────────┘    └──────────────┘    └──────────────┘    └────────────┘ │
│         │                   │                   │                   │        │
│         ▼                   ▼                   ▼                   ▼        │
│  ┌──────────────────────────────────────────────────────────────────────────┐│
│  │                         Workflow Steps                                    ││
│  │  1. Initialize    2. Run Process   3. Apply   4. Mechanical  5. Validate ││
│  │     Environment      Simulation       TORG       Design         Results  ││
│  └──────────────────────────────────────────────────────────────────────────┘│
│         │                                                           │        │
│         ▼                                                           ▼        │
│  ┌──────────────┐                                          ┌────────────────┐│
│  │ Design Case  │                                          │  Validation    ││
│  │ Results      │                                          │  Results       ││
│  └──────────────┘                                          └────────────────┘│
│                                                                               │
└──────────────────────────────────────────────────────────────────────────────┘

Design Phases

The DesignPhase enum represents project lifecycle stages with associated accuracy requirements:

Phase Description Accuracy Range Requires Full Design
SCREENING Early opportunity screening ±40-50% No
CONCEPT_SELECT Concept selection study ±30% No
PRE_FEED Pre-FEED study ±25% No
FEED Front-End Engineering Design ±15-20% Yes
DETAIL_DESIGN Detailed engineering ±10% Yes
AS_BUILT As-built verification ±5% Yes

Using Design Phases

import neqsim.process.mechanicaldesign.designstandards.DesignPhase;

// Get phase properties
DesignPhase phase = DesignPhase.FEED;

String accuracy = phase.getAccuracyRange();           // "±15-20%"
boolean compliance = phase.requiresDetailedCompliance(); // true
boolean fullDesign = phase.requiresFullMechanicalDesign(); // true

// Phase comparisons
boolean isLate = phase.isLaterThan(DesignPhase.CONCEPT_SELECT); // true
boolean isEarly = phase.isEarlierThan(DesignPhase.DETAIL_DESIGN); // true

Design Cases

The DesignCase enum defines operating scenarios for equipment sizing:

Case Load Factor Sizing Critical Relief Required
NORMAL 1.0 Yes No
MAXIMUM 1.1 Yes Yes
MINIMUM 0.3 No (turndown) No
STARTUP 0.1 No No
SHUTDOWN 0.1 No No
UPSET 1.2 Yes Yes
EMERGENCY 1.0 No Yes
WINTER 1.0 Yes No
SUMMER 1.0 Yes No
EARLY_LIFE 1.0 Yes No
LATE_LIFE 0.8 Yes No

Using Design Cases

import neqsim.process.mechanicaldesign.designstandards.DesignCase;

// Get case properties
DesignCase designCase = DesignCase.MAXIMUM;

double loadFactor = designCase.getTypicalLoadFactor();  // 1.1
boolean sizing = designCase.isSizingCritical();         // true
boolean turndown = designCase.isTurndownCase();         // false
boolean relief = designCase.requiresReliefSizing();     // true

// Get relevant cases for different purposes
List<DesignCase> sizingCases = DesignCase.getSizingCriticalCases();
List<DesignCase> reliefCases = DesignCase.getReliefSizingCases();
List<DesignCase> turndownCases = DesignCase.getTurndownCases();

Complete Workflow Example

Step 1: Create Orchestrator

import neqsim.process.mechanicaldesign.designstandards.FieldDevelopmentDesignOrchestrator;
import neqsim.process.mechanicaldesign.designstandards.DesignPhase;
import neqsim.process.mechanicaldesign.designstandards.DesignCase;
import neqsim.process.processmodel.ProcessSystem;

// Build process system
ProcessSystem process = new ProcessSystem();
Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(100.0, "kg/hr");
process.add(feed);

Separator separator = new Separator("HP Separator", feed);
process.add(separator);

Compressor compressor = new Compressor("Export Compressor", separator.getGasOutStream());
compressor.setOutletPressure(80.0, "bara");
process.add(compressor);

// Create orchestrator
FieldDevelopmentDesignOrchestrator orchestrator = 
    new FieldDevelopmentDesignOrchestrator(process);

Step 2: Configure Design Phase and Cases

// Set design phase
orchestrator.setDesignPhase(DesignPhase.FEED);

// Add design cases to evaluate
orchestrator.addDesignCase(DesignCase.NORMAL);
orchestrator.addDesignCase(DesignCase.MAXIMUM);
orchestrator.addDesignCase(DesignCase.MINIMUM);
orchestrator.addDesignCase(DesignCase.UPSET);

Step 3: Load and Apply TORG

import neqsim.process.mechanicaldesign.torg.TorgManager;
import neqsim.process.mechanicaldesign.torg.CsvTorgDataSource;

// Configure TORG source
TorgManager torgManager = new TorgManager();
torgManager.addDataSource(new CsvTorgDataSource("project_torg.csv"));

// Load TORG for project
boolean loaded = orchestrator.loadTorg(torgManager, "TROLL-WEST-2025");
if (!loaded) {
    throw new IllegalStateException("Failed to load TORG");
}

Step 4: Run Complete Workflow

// Run complete design workflow
orchestrator.runCompleteDesignWorkflow();

This executes the following steps:

  1. Initialize - Set up environment and validate configuration
  2. Run Process Simulation - Execute process calculations for all design cases
  3. Apply TORG - Apply standards and requirements from TORG
  4. Run Mechanical Design - Calculate equipment sizing and material selection
  5. Validate - Check compliance with standards and requirements

Step 5: Get Results

// Get validation results
DesignValidationResult results = orchestrator.validateDesign();

if (results.isValid()) {
    System.out.println("Design validation passed!");
} else {
    System.out.println("Design validation failed:");
    for (DesignValidationResult.ValidationMessage msg : results.getMessages()) {
        System.out.println("  " + msg.getSeverity() + ": " + msg.getMessage());
    }
}

// Get results for each design case
Map<DesignCase, DesignCaseResult> caseResults = orchestrator.getDesignCaseResults();
for (Map.Entry<DesignCase, DesignCaseResult> entry : caseResults.entrySet()) {
    DesignCase dc = entry.getKey();
    DesignCaseResult result = entry.getValue();
    System.out.println(dc.name() + ": " + (result.isConverged() ? "Converged" : "Failed"));
}

Design Validation Results

The DesignValidationResult class provides structured validation feedback:

Severity Levels

Level Description Blocks Design
INFO Informational messages No
WARNING Potential issues, review recommended No
ERROR Design problems, must be addressed Yes
CRITICAL Severe issues, safety implications Yes

Using Validation Results

import neqsim.process.mechanicaldesign.designstandards.DesignValidationResult;

DesignValidationResult result = orchestrator.validateDesign();

// Check overall status
boolean isValid = result.isValid();          // true if no ERROR/CRITICAL
boolean hasWarnings = result.hasWarnings();  // true if any WARNING
boolean hasCritical = result.hasCriticalIssues(); // true if any CRITICAL

// Get counts by severity
int errorCount = result.getErrorCount();
int warningCount = result.getWarningCount();

// Get all messages
List<ValidationMessage> allMessages = result.getMessages();

// Filter by severity
List<ValidationMessage> errors = result.getMessagesBySeverity(Severity.ERROR);
List<ValidationMessage> warnings = result.getMessagesBySeverity(Severity.WARNING);

// Print formatted summary
System.out.println(result.getSummary());

Example output:

Design Validation Summary
=========================
Status: PASSED WITH WARNINGS

Messages:
  [INFO] HP Separator design completed successfully
  [INFO] Export Compressor design completed successfully
  [WARNING] HP Separator corrosion allowance (2.0 mm) is below TORG requirement (3.0 mm)
  [WARNING] Minimum case shows separator efficiency at 85% (target 90%)

Statistics:
  - Info: 2
  - Warnings: 2
  - Errors: 0
  - Critical: 0

Design Report Generation

Generate comprehensive design reports:

// Generate design report
String report = orchestrator.generateDesignReport();
System.out.println(report);

// Save to file
Files.write(Paths.get("design_report.txt"), report.getBytes());

Example report:

Field Development Design Report
================================
Project: TROLL-WEST-2025
Phase: FEED (±15-20% accuracy)
Generated: 2025-01-06 14:30:00

TORG Information
----------------
Revision: Rev 2
Company: EQUINOR
Design Life: 25 years

Design Cases Evaluated
----------------------
1. NORMAL (Load Factor: 1.0)
   Status: Converged
   Iterations: 5

2. MAXIMUM (Load Factor: 1.1)
   Status: Converged
   Iterations: 7

3. MINIMUM (Load Factor: 0.3)
   Status: Converged
   Iterations: 4

4. UPSET (Load Factor: 1.2)
   Status: Converged
   Iterations: 9

Equipment Summary
-----------------
HP Separator:
  - Design Pressure: 55.0 barg
  - Design Temperature: 150°C
  - Material: SA-516-70
  - Wall Thickness: 25.4 mm
  - Weight: 12,500 kg
  - Standards: ASME VIII Div 1, NORSOK P-002

Export Compressor:
  - Stages: 2
  - Power: 2.5 MW
  - Discharge Pressure: 80 bara
  - Material: API 617 compliant
  - Standards: API 617

Validation Summary
------------------
Overall Status: PASSED WITH WARNINGS
- 0 Critical issues
- 0 Errors
- 2 Warnings
- 4 Info messages

See detailed validation report for warning details.

Workflow Customization

Custom Workflow Steps

// Add custom pre-processing step
orchestrator.addPreProcessStep("Custom Pre-Check", () -> {
    // Custom validation logic
    if (!checkCustomRequirements()) {
        throw new IllegalStateException("Custom requirements not met");
    }
});

// Add custom post-processing step
orchestrator.addPostProcessStep("Export Results", () -> {
    // Export to external system
    exportToExternalDatabase(orchestrator.getResults());
});

Selective Case Execution

// Run only sizing-critical cases
orchestrator.clearDesignCases();
for (DesignCase dc : DesignCase.getSizingCriticalCases()) {
    orchestrator.addDesignCase(dc);
}
orchestrator.runCompleteDesignWorkflow();

Phase-Specific Behavior

DesignPhase phase = orchestrator.getDesignPhase();

if (phase.requiresFullMechanicalDesign()) {
    // Full mechanical design with detailed calculations
    orchestrator.setDetailedCalculations(true);
} else {
    // Simplified calculations for early phases
    orchestrator.setDetailedCalculations(false);
}

Integration with Process Simulation

Updating Process Conditions

// For each design case, update process conditions
for (DesignCase designCase : orchestrator.getDesignCases()) {
    // Adjust feed rate based on case
    double loadFactor = designCase.getTypicalLoadFactor();
    feed.setFlowRate(baseFlowRate * loadFactor, "kg/hr");

    // Adjust temperature for seasonal cases
    if (designCase == DesignCase.WINTER) {
        feed.setTemperature(-20.0, "C");
    } else if (designCase == DesignCase.SUMMER) {
        feed.setTemperature(35.0, "C");
    }

    // Run simulation
    process.run();

    // Store results
    orchestrator.storeDesignCaseResult(designCase, process);
}

Equipment Sizing Envelope

// Get sizing envelope across all cases
SizingEnvelope envelope = orchestrator.getSizingEnvelope();

double maxPressure = envelope.getMaxDesignPressure();
double maxTemperature = envelope.getMaxDesignTemperature();
double maxFlow = envelope.getMaxFlowRate();

System.out.println("Sizing Envelope:");
System.out.println("  Max Pressure: " + maxPressure + " barg");
System.out.println("  Max Temperature: " + maxTemperature + " °C");
System.out.println("  Max Flow: " + maxFlow + " kg/hr");

Error Handling

try {
    orchestrator.runCompleteDesignWorkflow();
} catch (TorgNotFoundException e) {
    System.err.println("TORG not found: " + e.getMessage());
    // Fall back to default standards
    orchestrator.applyDefaultStandards();
    orchestrator.runCompleteDesignWorkflow();
} catch (DesignConvergenceException e) {
    System.err.println("Design did not converge: " + e.getMessage());
    // Get partial results
    DesignValidationResult partial = e.getPartialResults();
    System.out.println(partial.getSummary());
} catch (StandardNotSupportedException e) {
    System.err.println("Standard not supported: " + e.getMessage());
    String remediation = e.getRemediation();
    System.out.println("Suggested action: " + remediation);
}

Best Practices

1. Progressive Refinement

Start with coarse phases and refine:

// Screening phase - quick estimates
orchestrator.setDesignPhase(DesignPhase.SCREENING);
orchestrator.addDesignCase(DesignCase.NORMAL);
orchestrator.addDesignCase(DesignCase.MAXIMUM);
orchestrator.runCompleteDesignWorkflow();

// If viable, move to FEED
if (orchestrator.validateDesign().isValid()) {
    orchestrator.setDesignPhase(DesignPhase.FEED);
    // Add more cases for detailed analysis
    orchestrator.addDesignCase(DesignCase.MINIMUM);
    orchestrator.addDesignCase(DesignCase.UPSET);
    orchestrator.addDesignCase(DesignCase.WINTER);
    orchestrator.addDesignCase(DesignCase.SUMMER);
    orchestrator.runCompleteDesignWorkflow();
}

2. Document All Assumptions

// Add assumptions to report
orchestrator.addAssumption("Feed composition based on 2024 well test data");
orchestrator.addAssumption("Ambient temperature range from met-ocean study");
orchestrator.addAssumption("Design life 25 years per TORG Rev 2");

3. Version Control Integration

// Tag design run with version info
orchestrator.setRunMetadata("git_commit", getGitCommitHash());
orchestrator.setRunMetadata("torg_revision", torg.getRevision());
orchestrator.setRunMetadata("analyst", System.getProperty("user.name"));

4. Reproducibility

// Save complete configuration for reproducibility
orchestrator.saveConfiguration("design_config_2025-01-06.json");

// Later, reload and re-run
FieldDevelopmentDesignOrchestrator restored = 
    FieldDevelopmentDesignOrchestrator.loadConfiguration("design_config_2025-01-06.json");
restored.runCompleteDesignWorkflow();

Complete Example

import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.mechanicaldesign.designstandards.*;
import neqsim.process.mechanicaldesign.torg.*;
import neqsim.thermo.system.SystemSrkEos;

public class FieldDevelopmentDesignExample {

    public static void main(String[] args) {
        // 1. Create fluid and process
        SystemSrkEos fluid = new SystemSrkEos(280.0, 50.0);
        fluid.addComponent("methane", 0.85);
        fluid.addComponent("ethane", 0.08);
        fluid.addComponent("propane", 0.04);
        fluid.addComponent("n-butane", 0.03);
        fluid.setMixingRule("classic");

        ProcessSystem process = new ProcessSystem();

        Stream feed = new Stream("Well Feed", fluid);
        feed.setFlowRate(50000.0, "kg/hr");
        process.add(feed);

        Separator hpSep = new Separator("HP Separator", feed);
        process.add(hpSep);

        Compressor exportComp = new Compressor("Export Compressor", hpSep.getGasOutStream());
        exportComp.setOutletPressure(150.0, "bara");
        process.add(exportComp);

        // 2. Create orchestrator
        FieldDevelopmentDesignOrchestrator orchestrator = 
            new FieldDevelopmentDesignOrchestrator(process);

        // 3. Configure for FEED phase
        orchestrator.setDesignPhase(DesignPhase.FEED);

        // 4. Add design cases
        orchestrator.addDesignCase(DesignCase.NORMAL);
        orchestrator.addDesignCase(DesignCase.MAXIMUM);
        orchestrator.addDesignCase(DesignCase.MINIMUM);
        orchestrator.addDesignCase(DesignCase.UPSET);
        orchestrator.addDesignCase(DesignCase.EARLY_LIFE);
        orchestrator.addDesignCase(DesignCase.LATE_LIFE);

        // 5. Load TORG
        TorgManager torgManager = new TorgManager();
        torgManager.addDataSource(new CsvTorgDataSource("project_torg.csv"));
        orchestrator.loadTorg(torgManager, "TROLL-WEST-2025");

        // 6. Run complete workflow
        orchestrator.runCompleteDesignWorkflow();

        // 7. Validate and report
        DesignValidationResult validation = orchestrator.validateDesign();
        System.out.println(validation.getSummary());

        if (validation.isValid()) {
            String report = orchestrator.generateDesignReport();
            System.out.println(report);
        } else {
            System.err.println("Design validation failed!");
            for (ValidationMessage msg : validation.getMessagesBySeverity(Severity.ERROR)) {
                System.err.println("  ERROR: " + msg.getMessage());
            }
        }
    }
}

See Also

Chapter 23: Serialization & Persistence

Process Serialization

Saving and Loading Process Simulations in NeqSim

This document describes how to save and load process simulations in NeqSim using JSON/XML serialization. NeqSim uses the XStream library for object serialization, supporting both uncompressed XML and compressed .neqsim file formats.

Table of Contents


Overview

NeqSim provides multiple ways to persist and restore process simulations:

Method Format Compression Use Case
ProcessSystem.saveToNeqsim() XML in ZIP ✅ Yes Recommended - compact storage
ProcessSystem.saveAuto() Auto-detect ✅ Auto Convenience - format by extension
NeqSimXtream.saveNeqsim() XML in ZIP ✅ Yes Low-level API
ProcessSystemState JSON ✅ Optional Version control - Git-friendly
save_xml() / open_xml() Plain XML ❌ No Debugging - human-readable

The underlying serialization technology is XStream, a powerful Java library that converts objects to XML and back without requiring any modifications to the objects.


Serialization Options

The .neqsim file format stores the XML serialization inside a ZIP archive, significantly reducing file size for large process models. This is the recommended format for production use.

Advantages:

2. Plain XML Files

Uncompressed XML files are useful for debugging and manual inspection but can become very large for complex simulations.

3. JSON State Files

JSON-based state export provides a human-readable, Git-friendly format for version control and lifecycle management.


Java API

Quick Start - ProcessSystem Methods

The simplest way to save and load process models is using the convenience methods directly on ProcessSystem:

import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create and configure a process
SystemInterface fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 10.0);
fluid.addComponent("ethane", 5.0);
fluid.setMixingRule("classic");

ProcessSystem process = new ProcessSystem("My Process");
Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(1000.0, "kg/hr");
process.add(feed);
process.run();

// Save to compressed .neqsim file (recommended)
process.saveToNeqsim("my_process.neqsim");

// Load from .neqsim file (auto-runs after loading)
ProcessSystem loaded = ProcessSystem.loadFromNeqsim("my_process.neqsim");
System.out.println("Loaded: " + loaded.getName());

Auto-Format Detection

Use saveAuto() and loadAuto() to automatically detect format based on file extension:

// Format detected by extension
process.saveAuto("my_process.neqsim");  // → Compressed XStream XML
process.saveAuto("my_process.json");    // → JSON state export
process.saveAuto("my_process.ser");     // → Java binary serialization

// Load with auto-detection
ProcessSystem loaded = ProcessSystem.loadAuto("my_process.neqsim");

Saving Process Models

Using NeqSimXtream (Compressed Format)

import neqsim.util.serialization.NeqSimXtream;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create and configure a process
SystemInterface fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 10.0);
fluid.addComponent("ethane", 5.0);
fluid.setMixingRule(2);

ProcessSystem process = new ProcessSystem("My Process");
Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(1000.0, "kg/hr");
process.add(feed);
process.run();

// Save to compressed .neqsim file
boolean success = NeqSimXtream.saveNeqsim(process, "my_process.neqsim");
if (success) {
    System.out.println("Process saved successfully!");
}

Using XStream Directly (Uncompressed XML)

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.security.AnyTypePermission;

// Create XStream instance
XStream xstream = new XStream();

// Serialize to XML string
String xml = xstream.toXML(process);

// Save to file
try (FileWriter writer = new FileWriter("my_process.xml")) {
    writer.write(xml);
}

Loading Process Models

Loading Compressed .neqsim Files

import neqsim.util.serialization.NeqSimXtream;
import neqsim.process.processmodel.ProcessSystem;

try {
    // Load from .neqsim file
    Object loaded = NeqSimXtream.openNeqsim("my_process.neqsim");

    if (loaded instanceof ProcessSystem) {
        ProcessSystem process = (ProcessSystem) loaded;

        // Run the restored process
        process.run();

        System.out.println("Process loaded: " + process.getName());
        System.out.println("Number of units: " + process.getUnitOperations().size());
    }
} catch (IOException e) {
    System.err.println("Failed to load process: " + e.getMessage());
}

Loading Uncompressed XML

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.security.AnyTypePermission;

// Create XStream instance with security permissions
XStream xstream = new XStream();
xstream.addPermission(AnyTypePermission.ANY);

// Read XML from file
String xmlContent = new String(Files.readAllBytes(Paths.get("my_process.xml")));

// Deserialize
ProcessSystem process = (ProcessSystem) xstream.fromXML(xmlContent);
process.run();

State-Based Serialization

NeqSim provides a ProcessSystemState class for JSON-based state management, ideal for version control and lifecycle tracking:

import neqsim.process.processmodel.lifecycle.ProcessSystemState;
import neqsim.process.processmodel.ProcessSystem;
import java.io.File;

// Create a state snapshot from existing process
ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);

// Add metadata
state.setVersion("1.2.3");
state.setDescription("Post-commissioning tuned model");

// Save to JSON file (uncompressed) - using String path or File
state.saveToFile("asset_model_v1.2.3.json");
state.saveToFile(new File("asset_model_v1.2.3.json"));

// Later: Load the state - using String path or File
ProcessSystemState loadedState = ProcessSystemState.loadFromFile("asset_model_v1.2.3.json");
ProcessSystemState loadedState = ProcessSystemState.loadFromFile(new File("asset_model_v1.2.3.json"));

// Convert to ProcessSystem (limited reconstruction)
ProcessSystem restoredProcess = loadedState.toProcessSystem();

// Or apply state to existing process with matching structure
loadedState.applyTo(existingProcess);

Compressed State Files

For large process models, use GZIP compression to reduce file sizes (typically 5-20x reduction):

// Save to compressed file (.neqsim) - using String path or File
state.saveToCompressedFile("asset_model_v1.2.3.neqsim");
state.saveToCompressedFile(new File("asset_model_v1.2.3.neqsim"));

// Load from compressed file - using String path or File
ProcessSystemState loadedState = ProcessSystemState.loadFromCompressedFile("asset_model_v1.2.3.neqsim");
ProcessSystemState loadedState = ProcessSystemState.loadFromCompressedFile(new File("asset_model_v1.2.3.neqsim"));

// Auto-detect compression based on file extension - using String path or File
state.saveToFileAuto("asset_model.neqsim");  // Compressed
state.saveToFileAuto("asset_model.json");    // Uncompressed
state.saveToFileAuto(new File("asset_model.neqsim"));  // Also works with File

ProcessSystemState loaded = ProcessSystemState.loadFromFileAuto("asset_model.neqsim");
ProcessSystemState loaded = ProcessSystemState.loadFromFileAuto(new File("asset_model.neqsim"));

When to use compressed state files (.neqsim):

When to use plain JSON (.json):

JSON State Export/Import

// Export to JSON string
String json = state.toJson();

// Import from JSON string
ProcessSystemState restored = ProcessSystemState.fromJson(json);

ProcessSystem Methods for State Management

// Export current state to JSON file
process.exportStateToFile("process_state.json");

// Load and apply state from JSON file
process.loadStateFromFile("process_state.json");

Validation

The ProcessSystemState class includes validation capabilities to verify loaded states:

// Load a state file
ProcessSystemState state = ProcessSystemState.loadFromFile("old_model.json");

// Validate the state
ProcessSystemState.ValidationResult result = state.validate();

if (result.isValid()) {
    System.out.println("State is valid!");
} else {
    System.out.println("Validation errors: " + result.getErrors());
}

// Check for warnings even if valid
if (!result.getWarnings().isEmpty()) {
    System.out.println("Warnings: " + result.getWarnings());
}

// Check schema version
System.out.println("Schema version: " + state.getSchemaVersion());

Schema Versioning

ProcessSystemState includes automatic schema version tracking for backward compatibility:

// Current schema version is embedded automatically
ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);
System.out.println("Schema: " + state.getSchemaVersion()); // e.g., "1.1"

// Older files without schema version are automatically migrated on load
ProcessSystemState old = ProcessSystemState.loadFromFile("legacy_model.json");
// Migration is automatic - connectionStates initialized if missing

Connection State Capture

ProcessSystemState captures stream connections between equipment for topology analysis:

ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);

// View captured connections
for (ProcessSystemState.ConnectionState conn : state.getConnectionStates()) {
    System.out.println(conn.getSourceEquipmentName() + "." + conn.getSourcePortName() 
        + " -> " + conn.getTargetEquipmentName() + "." + conn.getTargetPortName());
}
// Example output: separator.gasOutStream -> compressor.inlet

Multi-Process Models (ProcessModel)

When your simulation requires multiple interconnected ProcessSystem instances (e.g., upstream production + downstream processing), use the ProcessModel class for coordinated execution and serialization.

ProcessModel Overview

ProcessModel manages a collection of ProcessSystem instances with:

Class Purpose Scope
ProcessSystem Single process flowsheet Individual equipment + streams
ProcessModel Multi-process container Multiple ProcessSystems
ProcessSystemState JSON state for single process Equipment states + connections
ProcessModelState JSON state for multi-process All ProcessSystemStates + inter-process links

Saving and Loading ProcessModel

Quick Start

import neqsim.process.processmodel.ProcessModel;
import neqsim.process.processmodel.ProcessSystem;

// Create a multi-process model
ProcessModel model = new ProcessModel();
model.add("upstream", createUpstreamProcess());
model.add("downstream", createDownstreamProcess());
model.setMaxIterations(50);
model.setFlowTolerance(1e-5);
model.run();

// Save to compressed .neqsim file (recommended)
model.saveToNeqsim("field_model.neqsim");

// Load later (auto-runs after loading)
ProcessModel loaded = ProcessModel.loadFromNeqsim("field_model.neqsim");
ProcessSystem upstream = loaded.get("upstream");
System.out.println("Upstream solved: " + upstream.solved());

Auto-Format Detection

// Save with auto-format detection by extension
model.saveAuto("field_model.neqsim");  // → Compressed XStream XML
model.saveAuto("field_model.json");    // → JSON state export
model.saveAuto("field_model.ser");     // → Java binary serialization

// Load with auto-detection
ProcessModel loaded = ProcessModel.loadAuto("field_model.neqsim");

Low-Level API with NeqSimXtream

import neqsim.util.serialization.NeqSimXtream;

// Save ProcessModel directly
NeqSimXtream.saveNeqsim(model, "field_model.neqsim");

// Load (returns Object, requires cast)
ProcessModel loaded = (ProcessModel) NeqSimXtream.openNeqsim("field_model.neqsim");
loaded.run();

ProcessModelState for JSON Export

For Git-friendly version control, use ProcessModelState to export multi-process models to JSON:

import neqsim.process.processmodel.lifecycle.ProcessModelState;

// Export state from model
ProcessModelState state = model.exportState();
state.setVersion("1.0.0");
state.setDescription("Full field development model");
state.setCreatedBy("engineering-team");

// Save to JSON (human-readable)
state.saveToFile("field_model_v1.0.0.json");

// Save to compressed JSON
state.saveToFile("field_model_v1.0.0.json.gz");

// Load later
ProcessModelState loadedState = ProcessModelState.loadFromFile("field_model_v1.0.0.json");
ProcessModel restoredModel = loadedState.toProcessModel();

Execution Configuration Preservation

ProcessModelState preserves execution settings:

ProcessModelState state = model.exportState();

// Access execution config
ProcessModelState.ExecutionConfig config = state.getExecutionConfig();
System.out.println("Max iterations: " + config.getMaxIterations());
System.out.println("Flow tolerance: " + config.getFlowTolerance());
System.out.println("Optimized execution: " + config.isUseOptimizedExecution());

Inter-Process Connections

ProcessModelState automatically captures streams shared between different ProcessSystems:

ProcessModelState state = model.exportState();

// View inter-process connections
for (ProcessModelState.InterProcessConnection conn : state.getInterProcessConnections()) {
    System.out.println(conn.getSourceProcess() + "/" + conn.getStreamName() 
        + " -> " + conn.getTargetProcess() + ":" + conn.getTargetPort());
}
// Example output: upstream/gas_export -> downstream:inlet

Validation

Validate multi-process model state before loading:

ProcessModelState state = ProcessModelState.loadFromFile("field_model.json");
ProcessModelState.ValidationResult result = state.validate();

if (!result.isValid()) {
    for (String error : result.getErrors()) {
        System.err.println("ERROR: " + error);
    }
    for (String warning : result.getWarnings()) {
        System.out.println("WARNING: " + warning);
    }
} else {
    ProcessModel model = state.toProcessModel();
    model.run();
}

Python API for ProcessModel

From Python, use the direct Java API access pattern:

import jpype
from neqsim import jneqsim

# Access ProcessModel class
ProcessModel = jneqsim.process.processmodel.ProcessModel
ProcessSystem = jneqsim.process.processmodel.ProcessSystem
NeqSimXtream = jneqsim.util.serialization.NeqSimXtream

# Load a ProcessModel
loaded = NeqSimXtream.openNeqsim("field_model.neqsim")
model = jpype.JObject(loaded, ProcessModel)
model.run()

# Access individual ProcessSystems
upstream = model.get("upstream")
print(f"Upstream solved: {upstream.solved()}")

# Save ProcessModel
model.saveToNeqsim("field_model_updated.neqsim")

Python API (neqsim-python)

The neqsim-python package provides Python wrappers for saving and loading NeqSim objects.

Quick Start

import neqsim
from neqsim.thermo import fluid
from neqsim.process import stream, separator, runProcess, clearProcess, getProcess

# Build and run a process
clearProcess()
feed_fluid = fluid('srk')
feed_fluid.addComponent('methane', 0.9)
feed_fluid.addComponent('ethane', 0.1)
feed_fluid.setTemperature(30.0, 'C')
feed_fluid.setPressure(50.0, 'bara')
feed_fluid.setTotalFlowRate(10.0, 'MSm3/day')

inlet = stream('inlet', feed_fluid)
sep = separator('separator', inlet)
runProcess()
process = getProcess()

# Save to compressed .neqsim file
neqsim.save_neqsim(process, "my_process.neqsim")

# Load from .neqsim file
loaded = neqsim.open_neqsim("my_process.neqsim")
loaded.run()
print(f"Loaded: {loaded.getName()}")

Saving and Loading .neqsim Files

The .neqsim format stores compressed XML, making it efficient for large process models:

import neqsim
from neqsim.thermo import fluid
from neqsim.process import stream, separator, runProcess, clearProcess, getProcess

# Build a process
clearProcess()
feed_fluid = fluid('srk')
feed_fluid.addComponent('methane', 0.9)
feed_fluid.addComponent('ethane', 0.1)
feed_fluid.setTemperature(30.0, 'C')
feed_fluid.setPressure(50.0, 'bara')
feed_fluid.setTotalFlowRate(10.0, 'MSm3/day')

inlet = stream('inlet', feed_fluid)
sep = separator('separator', inlet)
runProcess()

# Get the process object
process = getProcess()

# Save to compressed .neqsim file
neqsim.save_neqsim(process, "my_process.neqsim")
print("Process saved!")
# Load the process from .neqsim file
loaded_process = neqsim.open_neqsim("my_process.neqsim")

# Run the loaded process
loaded_process.run()

print(f"Loaded process: {loaded_process.getName()}")
print(f"Number of units: {loaded_process.getUnitOperations().size()}")

Saving and Loading XML Files

For debugging or when human-readable output is needed:

import neqsim
from neqsim.thermo import createfluid

# Create a fluid
fluid1 = createfluid("dry gas")

# Save to uncompressed XML
neqsim.save_xml(fluid1, "my_fluid.xml")

# Load from XML
fluid2 = neqsim.open_xml("my_fluid.xml")

# Verify the data was preserved
assert fluid1.getTemperature() == fluid2.getTemperature()

Direct Java API Access

For full control, you can use the Java API directly from Python via JPype:

import jpype
import jpype.imports
from jpype.types import *

# Start the JVM (if not already started by neqsim)
if not jpype.isJVMStarted():
    jpype.startJVM(classpath=['path/to/neqsim.jar'])

# Import Java classes directly
from neqsim.process.processmodel import ProcessSystem
from neqsim.process.processmodel.lifecycle import ProcessSystemState
from neqsim.thermo.system import SystemSrkEos
from neqsim.process.equipment.stream import Stream

# Create a process using Java API
fluid = SystemSrkEos(298.15, 50.0)
fluid.addComponent("methane", 10.0)
fluid.addComponent("ethane", 5.0)
fluid.setMixingRule("classic")

process = ProcessSystem("MyProcess")
feed = Stream("feed", fluid)
feed.setFlowRate(1000.0, "kg/hr")
process.add(feed)
process.run()

# Use the convenience methods
process.saveToNeqsim("model.neqsim")

# Load back
loaded = ProcessSystem.loadFromNeqsim("model.neqsim")
print(f"Loaded: {loaded.getName()}")

# Use ProcessSystemState for JSON export
state = ProcessSystemState.fromProcessSystem(process)
state.setVersion("1.0.0")
state.setDescription("Initial model")
state.saveToFile("model_state.json")

# Validate loaded state
loaded_state = ProcessSystemState.loadFromFile("model_state.json")
result = loaded_state.validate()
if result.isValid():
    print("State is valid")
else:
    print(f"Errors: {list(result.getErrors())}")

ProcessSystemState from Python

from neqsim.process.processmodel.lifecycle import ProcessSystemState

# Create state from process
state = ProcessSystemState.fromProcessSystem(process)

# Add metadata
state.setVersion("2.1.0")
state.setDescription("Updated heat exchanger configuration")
state.setCreatedBy("engineer@company.com")

# Save with auto-format detection
state.saveToFileAuto("model.neqsim")  # Compressed
state.saveToFileAuto("model.json")    # JSON (Git-friendly)

# Load and validate
loaded = ProcessSystemState.loadFromFileAuto("model.neqsim")
print(f"Schema version: {loaded.getSchemaVersion()}")
print(f"Equipment count: {loaded.getEquipmentStates().size()}")

# Check connections
for conn in loaded.getConnectionStates():
    print(f"  {conn.getSourceEquipmentName()} -> {conn.getTargetEquipmentName()}")

Using ProcessBuilder with Configuration Files

neqsim-python also supports JSON/YAML-based process configuration:

from neqsim.thermo import fluid
from neqsim.process import ProcessBuilder

# Create fluid
feed = fluid('srk')
feed.addComponent('methane', 0.9)
feed.addComponent('ethane', 0.1)
feed.setTemperature(30.0, 'C')
feed.setPressure(50.0, 'bara')

# Load process from JSON configuration
process = ProcessBuilder.from_json('process_config.json', 
                                   fluids={'feed': feed}).run()

# Get results as JSON
results = process.results_json()

# Save results to file
process.save_results('results.json', format='json')

Example JSON Configuration File

{
  "name": "Compression Train",
  "equipment": [
    {
      "type": "stream",
      "name": "inlet",
      "fluid": "feed",
      "flow_rate": 10.0,
      "flow_unit": "MSm3/day"
    },
    {
      "type": "separator",
      "name": "inlet_separator",
      "inlet": "inlet"
    },
    {
      "type": "compressor",
      "name": "stage1_compressor",
      "inlet": "inlet_separator",
      "outlet_pressure": 80.0
    }
  ]
}

Compressed Files (.neqsim format)

Internal Structure

A .neqsim file is a standard ZIP archive containing a single XML file:

my_process.neqsim (ZIP archive)
└── process.xml    (XStream-serialized XML)

File Size Comparison

Process Complexity XML Size .neqsim Size Compression Ratio
Simple (5 units) ~500 KB ~30 KB ~17:1
Medium (20 units) ~2 MB ~120 KB ~17:1
Complex (50+ units) ~10 MB ~600 KB ~17:1

Manual Inspection

You can manually inspect .neqsim files using any ZIP tool:

# Linux/Mac
unzip -l my_process.neqsim
unzip -p my_process.neqsim process.xml | head -100

# Windows PowerShell
Expand-Archive -Path my_process.neqsim -DestinationPath extracted
Get-Content extracted\process.xml | Select-Object -First 100

Best Practices

1. Use Compressed Format for Production

Always use .neqsim format for production deployments to minimize storage and transfer costs:

// Good - compressed
NeqSimXtream.saveNeqsim(process, "production_model.neqsim");

// Avoid for large models - uncompressed
// xstream.toXML(process, new FileWriter("production_model.xml"));

2. Version Your Models

Use ProcessSystemState with version metadata for proper model lifecycle management:

ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);
state.setVersion("2.1.0");
state.setDescription("Updated valve Cv values based on commissioning data");
state.setCreatedBy("process_engineer@company.com");
state.saveToFile("model_v2.1.0.json");

3. Run After Loading

Always run the process after loading to ensure the internal state is consistent:

ProcessSystem loaded = (ProcessSystem) NeqSimXtream.openNeqsim("model.neqsim");
loaded.run();  // Important: reinitialize calculations

4. Handle Security Permissions

When using XStream directly, explicitly set permissions:

XStream xstream = new XStream();
xstream.addPermission(AnyTypePermission.ANY);  // Required for deserialization
xstream.allowTypesByWildcard(new String[]{"neqsim.**"});

5. Use JSON for Version Control

JSON state files are ideal for Git-based version control:

# Track model changes in Git
git add asset_model_v1.2.3.json
git commit -m "Updated model with new heat exchanger configuration"

Troubleshooting

Common Issues

1. FileNotFoundException: process.xml not found in zip file

The .neqsim file is corrupted or not a valid ZIP archive.

Solution: Verify the file is a valid ZIP and contains process.xml:

unzip -l my_process.neqsim

2. ClassNotFoundException during deserialization

The serialized object references a class that doesn't exist in the current NeqSim version.

Solution: Ensure you're using the same (or compatible) NeqSim version that created the file.

3. Large file sizes

Complex processes with many components can create large files.

Solution:

4. ThreadLocal serialization error

XStream cannot serialize ThreadLocal fields.

Solution: The NeqSimXtream class automatically handles this by skipping ThreadLocal fields. Use NeqSimXtream instead of raw XStream.

Debugging Serialization Issues

Enable detailed logging to diagnose problems:

// Check what's being serialized
String xml = xstream.toXML(process);
System.out.println("XML length: " + xml.length());
System.out.println("First 1000 chars: " + xml.substring(0, Math.min(1000, xml.length())));

API Reference

Java Classes

Class Description
neqsim.process.processmodel.ProcessSystem Main process container with saveToNeqsim(), loadFromNeqsim(), saveAuto(), loadAuto() methods
neqsim.process.processmodel.ProcessModel Multi-process container with save/load methods for multiple ProcessSystems
neqsim.util.serialization.NeqSimXtream Low-level compressed XML serialization to .neqsim files
neqsim.process.processmodel.lifecycle.ProcessSystemState JSON-based state snapshots for single ProcessSystem
neqsim.process.processmodel.lifecycle.ProcessModelState JSON-based state snapshots for multi-process models
neqsim.process.processmodel.lifecycle.ProcessSystemState.ValidationResult Validation result with errors and warnings
neqsim.process.processmodel.lifecycle.ProcessSystemState.ConnectionState Stream connection between equipment
neqsim.process.processmodel.lifecycle.ProcessSystemState.EquipmentState Captured state of a single equipment unit
neqsim.process.processmodel.lifecycle.ProcessModelState.InterProcessConnection Connection between ProcessSystems
neqsim.process.processmodel.lifecycle.ProcessModelState.ExecutionConfig Execution configuration for ProcessModel

ProcessSystem Methods

Method Description
saveToNeqsim(String filename) Save to compressed .neqsim file
loadFromNeqsim(String filename) Load from .neqsim file (static, auto-runs)
saveAuto(String filename) Save with auto-format detection by extension
loadAuto(String filename) Load with auto-format detection (static)
exportStateToFile(String filename) Export JSON state to file
loadStateFromFile(String filename) Load and apply JSON state
exportState() Get ProcessSystemState snapshot

ProcessModel Methods

Method Description
saveToNeqsim(String filename) Save entire model to compressed .neqsim file
loadFromNeqsim(String filename) Load from .neqsim file (static, auto-runs)
saveAuto(String filename) Save with auto-format detection by extension
loadAuto(String filename) Load with auto-format detection (static)
saveStateToFile(String filename) Export JSON state to file
loadStateFromFile(String filename) Load JSON state (static)
exportState() Get ProcessModelState snapshot
add(String name, ProcessSystem) Add a ProcessSystem to the model
get(String name) Get a ProcessSystem by name
getAllProcesses() Get all ProcessSystems
validateSetup() Validate all processes

ProcessSystemState Methods

Method Description
fromProcessSystem(ProcessSystem) Create state snapshot (static)
saveToFile(String) / saveToFile(File) Save as JSON
loadFromFile(String) / loadFromFile(File) Load JSON (static)
saveToCompressedFile(String) Save as GZIP-compressed JSON
loadFromCompressedFile(String) Load compressed JSON (static)
saveToFileAuto(String) Auto-detect format by extension
loadFromFileAuto(String) Auto-detect format on load (static)
validate() Validate state, returns ValidationResult
applyTo(ProcessSystem) Apply state to existing process
toJson() / fromJson(String) JSON string conversion
getSchemaVersion() Get schema version string
getConnectionStates() Get list of stream connections
getEquipmentStates() Get list of equipment states

ProcessModelState Methods

Method Description
fromProcessModel(ProcessModel) Create state snapshot (static)
saveToFile(String) Save as JSON (or .gz for compressed)
loadFromFile(String) Load JSON (static)
toProcessModel() Reconstruct ProcessModel from state
validate() Validate state, returns ValidationResult
getProcessStates() Get map of ProcessSystemStates
getInterProcessConnections() Get inter-process connections
getExecutionConfig() Get execution configuration
getProcessCount() Get number of ProcessSystems
setVersion(String) Set version metadata
setDescription(String) Set description metadata
setCreatedBy(String) Set creator metadata
setCustomProperty(String, Object) Set custom property

Python Functions

Function Description
neqsim.save_neqsim(obj, filename) Save object to compressed .neqsim file
neqsim.open_neqsim(filename) Load object from compressed .neqsim file
neqsim.save_xml(obj, filename) Save object to uncompressed XML file
neqsim.open_xml(filename) Load object from uncompressed XML file

Python Direct Java API

Class (via JPype) Description
ProcessSystem.saveToNeqsim(filename) Save process to .neqsim
ProcessSystem.loadFromNeqsim(filename) Load process from .neqsim
ProcessModel.saveToNeqsim(filename) Save multi-process model to .neqsim
ProcessModel.loadFromNeqsim(filename) Load multi-process model from .neqsim
ProcessSystemState.fromProcessSystem(process) Create state snapshot
ProcessModelState.fromProcessModel(model) Create multi-process state snapshot
ProcessSystemState.loadFromFileAuto(filename) Load state with format detection
state.validate() Validate loaded state

See Also

Chapter 24: Pipeline Fundamentals

Fluid Mechanics Overview

Fluid Mechanics Package

The fluidmechanics package provides models for pipeline flow, pressure drop calculations, and transient flow simulation with rigorous non-equilibrium thermodynamic calculations for mass and heat transfer.

Table of Contents


Overview

Location: neqsim.fluidmechanics

Purpose:


Compatibility


Theoretical Foundation

The fluid mechanics module in NeqSim is based on the work presented in:

Solbraa, E. (2002). Equilibrium and Non-Equilibrium Thermodynamics of Natural Gas Processing. Dr.ing. thesis, Norwegian University of Science and Technology (NTNU). ISBN: 978-82-471-5541-7. Available at NVA

The key contributions from this work include:

  1. Two-fluid model for gas-liquid pipe flow with interphase mass and heat transfer
  2. Multicomponent mass transfer based on the Maxwell-Stefan equations
  3. Film theory with thermodynamic and finite flux corrections
  4. Reactive mass transfer with enhancement factors for chemical absorption
  5. High-pressure effects on mass transfer coefficients and equilibrium

Governing Equations

The two-phase flow is modeled using separate conservation equations for each phase:

Mass Conservation (per component i): $$\frac{\partial (\alpha_k \rho_k x_{i,k})}{\partial t} + \frac{\partial (\alpha_k \rho_k x_{i,k} v_k)}{\partial z} = \dot{m}_{i,k}$$

Momentum Conservation: $$\frac{\partial (\alpha_k \rho_k v_k)}{\partial t} + \frac{\partial (\alpha_k \rho_k v_k^2)}{\partial z} = -\alpha_k \frac{\partial P}{\partial z} - F_{w,k} - F_{i,k} + \alpha_k \rho_k g \sin\theta$$

Energy Conservation: $$\frac{\partial (\alpha_k \rho_k h_k)}{\partial t} + \frac{\partial (\alpha_k \rho_k h_k v_k)}{\partial z} = \dot{Q}_{w,k} + \dot{Q}_{i,k}$$

Where:


Package Structure

fluidmechanics/
├── FluidMech.java                    # Package marker
│
├── flowsystem/                       # Flow system definitions
│   ├── FlowSystem.java               # Base flow system
│   ├── FlowSystemInterface.java      # Interface
│   │
│   ├── onephaseflowsystem/           # Single-phase systems
│   │   ├── OnePhaseFlowSystem.java
│   │   └── pipeflowsystem/
│   │       └── OnePhasePipeFlowSystem.java
│   │
│   └── twophaseflowsystem/           # Two-phase systems
│       ├── TwoPhaseFlowSystem.java
│       └── pipeflowsystem/
│           ├── TwoPhasePipeFlowSystem.java
│           └── stratifiedflowsystem/
│               └── StratifiedFlowSystem.java
│
├── flownode/                         # Flow nodes
│   ├── FlowNode.java                 # Base node
│   ├── FlowNodeInterface.java        # Interface
│   ├── FlowNodeSelector.java         # Node selection
│   │
│   ├── onephasenode/                 # Single-phase nodes
│   │   ├── OnePhaseFlowNode.java
│   │   └── onephasepipeflownode/
│   │       └── OnePhasePipeFlowNode.java
│   │
│   ├── twophasenode/                 # Two-phase nodes
│   │   ├── TwoPhaseFlowNode.java
│   │   └── twophasepipeflownode/
│   │       ├── TwoPhasePipeFlowNode.java
│   │       ├── AnnularFlow.java
│   │       ├── StratifiedFlow.java
│   │       └── DropletFlow.java
│   │
│   ├── multiphasenode/               # Multi-phase nodes
│   │   └── MultiPhaseFlowNode.java
│   │
│   └── fluidboundary/                # Boundary conditions
│       ├── FluidBoundary.java
│       └── InterphaseTransport.java
│
├── flowleg/                          # Pipe segments
│   ├── FlowLeg.java
│   └── FlowLegInterface.java
│
├── flowsolver/                       # Numerical solvers
│   ├── FlowSolver.java
│   ├── FlowSolverInterface.java
│   ├── OnePhaseFlowSolver.java
│   └── TwoPhaseFlowSolver.java
│
├── geometrydefinitions/              # Pipe geometry
│   ├── GeometryDefinition.java
│   ├── GeometryDefinitionInterface.java
│   ├── pipe/
│   │   └── PipeGeometry.java
│   └── internalgeometry/
│       └── InternalGeometry.java
│
└── util/                             # Utilities
    ├── timeseries/
    │   └── TimeSeries.java
    └── fluidmechanicsvisualization/
        └── flowsystemvisualization/
            └── FlowSystemVisualization.java

Flow Systems

Single-Phase Pipe Flow

import neqsim.fluidmechanics.flowsystem.FlowSystemInterface;
import neqsim.fluidmechanics.flowsystem.onephaseflowsystem.pipeflowsystem.OnePhasePipeFlowSystem;
import neqsim.fluidmechanics.geometrydefinitions.pipe.PipeGeometry;

// Create fluid
SystemInterface gas = new SystemSrkEos(300.0, 50.0);
gas.addComponent("methane", 0.95);
gas.addComponent("ethane", 0.05);
gas.setMixingRule("classic");

// Create pipe geometry
PipeGeometry pipe = new PipeGeometry("Pipeline");
pipe.setDiameter(0.5, "m");        // 0.5 m inner diameter
pipe.setLength(10000.0, "m");      // 10 km length
pipe.setRoughness(0.00005, "m");   // Pipe roughness

// Create flow system
OnePhasePipeFlowSystem flowSystem = new OnePhasePipeFlowSystem();
flowSystem.setInletFluid(gas);
flowSystem.setGeometry(pipe);
flowSystem.setInletPressure(50.0, "bara");
flowSystem.setOutletPressure(40.0, "bara");
flowSystem.setNumberOfNodes(100);

// Initialize and solve
flowSystem.init();
flowSystem.solveTransient(1);

// Get results
double pressureDrop = flowSystem.getPressureDrop();
double velocity = flowSystem.getFlowVelocity();

Two-Phase Pipe Flow

import neqsim.fluidmechanics.flowsystem.twophaseflowsystem.twophasepipeflowsystem.*;
import neqsim.thermo.system.SystemSrkEos;

// Create two-phase fluid
SystemInterface fluid = new SystemSrkEos(300.0, 30.0);
fluid.addComponent("methane", 0.80, 0);    // Gas phase
fluid.addComponent("n-decane", 0.20, 1);   // Liquid phase
fluid.createDatabase(true);
fluid.setMixingRule(2);

// Create horizontal pipe using factory method
TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.horizontalPipe(fluid, 0.15, 1000, 50);

// Solve with mass transfer and get structured results
PipeFlowResult result = pipe.solveWithMassTransfer();

// Access results
System.out.println("Pressure drop: " + result.getTotalPressureDrop() + " bar");
System.out.println("Outlet temperature: " + result.getOutletTemperature() + " K");
System.out.println(result);  // Formatted summary

// Export for plotting (e.g., in Jupyter with neqsim-python)
Map<String, double[]> data = result.toMap();

Factory Methods

Method Description
horizontalPipe(fluid, diam, len, nodes) Horizontal pipe
verticalPipe(fluid, diam, len, nodes, upward) Vertical pipe
inclinedPipe(fluid, diam, len, nodes, angleDeg) Inclined pipe
subseaPipe(fluid, diam, len, nodes, seawaterTempC) Subsea pipeline
buriedPipe(fluid, diam, len, nodes, groundTempC) Buried pipeline

Builder Pattern (Full Control)

// For advanced configurations, use the builder
TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.builder()
    .withFluid(fluid)
    .withDiameter(0.15, "m")
    .withLength(1000, "m")
    .withNodes(50)
    .withFlowPattern(FlowPattern.STRATIFIED)
    .withConvectiveBoundary(278.15, "K", 10.0)
    .enableNonEquilibriumMassTransfer()
    .build();

PipeFlowResult result = pipe.solve();

Flow Nodes

Flow nodes discretize the pipe and calculate local conditions.

Node Properties

Property Description
Pressure Local pressure
Temperature Local temperature
Velocity Phase velocities
Holdup Liquid holdup
Reynolds number Flow regime indicator
Friction factor Wall friction

Flow Regimes (Two-Phase)

Regime Class Description
Stratified StratifiedFlow Separated gas-liquid layers
Annular AnnularFlow Liquid film on wall, gas core
Droplet/Mist DropletFlow Liquid droplets in gas
Slug SlugFlow Intermittent gas-liquid slugs
Bubble BubbleFlow Gas bubbles in liquid

Non-Equilibrium Modeling

NeqSim distinguishes between equilibrium and non-equilibrium calculations at the gas-liquid interface. In equilibrium calculations, the phases are assumed to be at thermodynamic equilibrium at the interface. In non-equilibrium calculations, finite mass and heat transfer rates are considered.

Architecture

FluidBoundary (abstract)
├── EquilibriumFluidBoundary       # Interface at equilibrium
└── NonEquilibriumFluidBoundary    # Finite transfer rates
    └── KrishnaStandartFilmModel   # Film theory implementation
        └── ReactiveKrishnaStandartFilmModel  # With chemical reactions

Equilibrium vs Non-Equilibrium

Aspect Equilibrium Non-Equilibrium
Interface conditions Thermodynamic equilibrium Finite driving forces
Mass transfer Instantaneous Rate-limited
Heat transfer Instantaneous Rate-limited
Computation Simpler More rigorous
Applications Long residence times Short contact times, absorption

Enabling Non-Equilibrium Calculations

// Get the flow node
FlowNodeInterface node = flowSystem.getNode(i);

// Enable mass and heat transfer calculations
node.getFluidBoundary().setMassTransferCalc(true);
node.getFluidBoundary().setHeatTransferCalc(true);

// Enable thermodynamic corrections (activity coefficients)
node.getFluidBoundary().setThermodynamicCorrections(0, true);  // Gas phase
node.getFluidBoundary().setThermodynamicCorrections(1, true);  // Liquid phase

// Enable finite flux corrections (Stefan flow)
node.getFluidBoundary().setFiniteFluxCorrection(0, true);
node.getFluidBoundary().setFiniteFluxCorrection(1, true);

Mass Transfer Models

Film Theory

The mass transfer in NeqSim is based on the film theory with multicomponent extensions. The key classes are:

Class Description
FluidBoundary Abstract base for interphase calculations
NonEquilibriumFluidBoundary Non-equilibrium mass/heat transfer
KrishnaStandartFilmModel Krishna-Standart multicomponent model
ReactiveKrishnaStandartFilmModel With chemical reaction enhancement

Single-Phase (Wall) Mass Transfer

For mass transfer from a flowing fluid to a wall (e.g., pipe wall, packing surface):

$$Sh = \frac{k_c \cdot d}{D_{AB}} = f(Re, Sc)$$

Where:

Correlations implemented:

Flow Regime Correlation Range
Laminar $Sh = 3.66$ $Re < 2300$
Turbulent $Sh = 0.023 \cdot Re^{0.83} \cdot Sc^{0.33}$ $Re > 10000$
Transition Interpolation $2300 < Re < 10000$

Heat Transfer Models

Single-Phase Heat Transfer

Heat transfer to/from pipe walls follows analogous correlations to mass transfer:

$$Nu = \frac{h \cdot d}{k} = f(Re, Pr)$$

Where:

Correlations implemented:

Flow Regime Correlation
Laminar $Nu = 3.66$ (constant wall temp)
Turbulent Dittus-Boelter: $Nu = 0.023 \cdot Re^{0.8} \cdot Pr^{n}$
Transition Gnielinski correlation

Where $n = 0.4$ for heating and $n = 0.3$ for cooling.

Heat Transfer with Phase Change

When mass transfer occurs, the heat transfer is coupled:

$$\dot{Q}_i = h_{eff} \cdot A \cdot (T_{bulk} - T_i) + \sum_j \dot{n}_j \cdot \Delta H_{vap,j}$$

The effective heat transfer coefficient accounts for the latent heat of evaporation/condensation.


Two-Phase Mass Transfer

Multicomponent Maxwell-Stefan Model

For multicomponent systems, the mass transfer is described by the Maxwell-Stefan equations rather than Fick's law. The molar flux of component $i$ relative to the molar average velocity is:

$$-c_t \nabla x_i = \sum_{j=1, j \neq i}^{n} \frac{x_i N_j - x_j N_i}{c_t D_{ij}}$$

In NeqSim, this is solved using the Krishna-Standart film model:

Binary Mass Transfer Coefficients

The binary mass transfer coefficients are calculated from:

$$k_{ij} = \frac{Sh \cdot D_{ij}}{d}$$

Where $D_{ij}$ is the binary diffusion coefficient calculated from:

Mass Transfer Coefficient Matrix

For multicomponent systems, the mass transfer coefficients form a matrix $[k]$:

// In KrishnaStandartFilmModel
public double calcMassTransferCoefficients(int phaseNum) {
    int n = getNumberOfComponents() - 1;

    for (int i = 0; i < n; i++) {
        double tempVar = 0;
        for (int j = 0; j < getNumberOfComponents(); j++) {
            if (i != j) {
                tempVar += x[j] / k_binary[i][j];
            }
            if (j < n) {
                K[i][j] = -x[i] * (1.0/k_binary[i][j] - 1.0/k_binary[i][n]);
            }
        }
        K[i][i] = tempVar + x[i] / k_binary[i][n];
    }
    return K.inverse();  // [k] matrix
}

Interphase Mass Transfer

The total molar flux vector is:

$$\mathbf{N} = c_t [\mathbf{k}] (\mathbf{x}_{bulk} - \mathbf{x}_{interface})$$

With corrections for:

  1. Thermodynamic non-ideality: Activity coefficient gradients
  2. Finite flux (Stefan flow): High mass transfer rates
  3. Film thickness variations: Due to flow regime

Schmidt Number

The Schmidt number characterizes the ratio of momentum to mass diffusivity:

$$Sc_{ij} = \frac{\nu}{D_{ij}}$$

// Calculation in KrishnaStandartFilmModel
for (int i = 0; i < nComponents; i++) {
    for (int j = 0; j < nComponents; j++) {
        binarySchmidtNumber[phase][i][j] = 
            kinematicViscosity / diffusionCoefficient[i][j];
    }
}

Interphase Transport Coefficients

The interphase transport coefficients depend on the flow regime:

Flow Regime Gas-side $k_G$ Liquid-side $k_L$
Stratified Smooth interface correlation Penetration theory
Annular Film correlation Film flow correlation
Droplet Droplet correlations Internal circulation
Bubble External mass transfer Higbie penetration

Two-Phase Heat Transfer

Interphase Heat Transfer

Heat transfer between gas and liquid phases occurs at the interface:

$$\dot{Q}_{GL} = h_{GL} \cdot A_i \cdot (T_G - T_L)$$

Where $A_i$ is the interfacial area per unit volume.

Heat Transfer Coefficient Correlations

The interphase heat transfer coefficient is related to mass transfer through the Chilton-Colburn analogy:

$$\frac{h}{k_c \cdot \rho \cdot c_p} = \left(\frac{Sc}{Pr}\right)^{2/3}$$

Implemented correlations by flow regime:

Flow Regime Correlation Type
Stratified Flat interface model
Annular Film evaporation/condensation
Dispersed Droplet/bubble heat transfer

Coupling of Heat and Mass Transfer

In non-equilibrium calculations, heat and mass transfer are coupled through:

  1. Latent heat effects: Evaporation/condensation carries enthalpy
  2. Sensible heat: Temperature gradients drive conduction
  3. Dufour effect: Mass flux induces heat flux (usually negligible)
  4. Soret effect: Temperature gradient induces mass flux (usually negligible)

The interphase heat flux includes both contributions:

$$\dot{Q}_i = h \cdot (T_{bulk} - T_i) + \sum_j N_j \cdot \bar{H}_j$$

Where $\bar{H}_j$ is the partial molar enthalpy of component $j$.

Wall Heat Transfer in Two-Phase Flow

For heat transfer to the pipe wall in two-phase flow:

// Set overall heat transfer coefficient
pipe.setOverallHeatTransferCoefficient(10.0);  // W/(m²·K)

// Or calculate from resistances
// 1/U = 1/h_inner + ln(r_o/r_i)/(2πkL) + 1/h_outer

Reactive Mass Transfer

Enhancement Factors

For absorption with chemical reaction (e.g., CO₂ into amine solutions), the mass transfer is enhanced:

$$N_{CO2} = E \cdot k_L \cdot (C_{CO2,i} - C_{CO2,bulk})$$

Where $E$ is the enhancement factor.

Enhancement Factor Models

Model Description Application
Film theory $E = \sqrt{1 + Ha^2}$ Fast reactions
Penetration theory Numerical solution Moderate reactions
Danckwerts Pseudo-first order Industrial absorbers

The Hatta number characterizes the reaction regime:

$$Ha = \frac{\sqrt{k_{rxn} \cdot D_A}}{k_L}$$

Reactive Film Model

// ReactiveKrishnaStandartFilmModel extends KrishnaStandartFilmModel

// Enhancement factor calculation
EnhancementFactor enhancement = new EnhancementFactor();
double E = enhancement.calculate(hattaNumber, reactionOrder);

// Apply to mass transfer
double N_CO2 = E * k_L * (C_interface - C_bulk);

CO₂-Amine Systems

NeqSim includes specific models for CO₂ absorption into:

Reaction kinetics: $$r_{CO2} = k_2 \cdot [CO2] \cdot [Amine]$$

With temperature-dependent rate constants from experimental data.


Pressure Drop Correlations

Single-Phase

// Darcy-Weisbach equation
// ΔP = f * (L/D) * (ρ * v²/2)

// Friction factor correlations:
// - Moody (explicit)
// - Colebrook-White (implicit)
// - Chen (explicit approximation)

Two-Phase

Correlation Application
Beggs-Brill General two-phase
Lockhart-Martinelli Separated flow
Duns-Ros Vertical wells
Hagedorn-Brown Vertical wells
Gray Gas-condensate wells

Transient Flow Simulation

// Set up transient simulation
flowSystem.init();

double simulationTime = 3600.0;  // 1 hour
double timeStep = 1.0;           // 1 second

for (double t = 0; t < simulationTime; t += timeStep) {
    flowSystem.solveTransient(1);

    // Get time series data
    TimeSeries data = flowSystem.getTimeSeries();

    // Log results
    for (int i = 0; i < flowSystem.getNumberOfNodes(); i++) {
        double x = flowSystem.getNode(i).getPosition();
        double P = flowSystem.getNode(i).getPressure();
        double T = flowSystem.getNode(i).getTemperature();
    }
}

Heat Transfer

// Set ambient conditions
flowSystem.setSurroundingTemperature(288.15);  // K

// Set overall heat transfer coefficient
pipe.setOverallHeatTransferCoefficient(10.0);  // W/(m²·K)

// Or specify insulation
pipe.setInsulationThickness(0.05, "m");
pipe.setInsulationConductivity(0.04);  // W/(m·K)

// Solve with heat transfer
flowSystem.setCalculateHeatTransfer(true);
flowSystem.solveTransient(1);

// Get temperature profile
for (int i = 0; i < flowSystem.getNumberOfNodes(); i++) {
    double T = flowSystem.getNode(i).getTemperature();
}

Geometry Definitions

Pipe Geometry

PipeGeometry pipe = new PipeGeometry("Export Pipeline");

// Dimensions
pipe.setDiameter(0.4, "m");
pipe.setLength(50000.0, "m");  // 50 km
pipe.setRoughness(0.000045, "m");

// Inclination profile (optional)
double[] distances = {0, 10000, 20000, 30000, 40000, 50000};
double[] elevations = {0, 50, 100, 80, 120, 150};
pipe.setElevationProfile(distances, elevations);

Internal Geometry

For complex internal structures (coatings, deposits).

InternalGeometry internal = new InternalGeometry();
internal.setCoatingThickness(0.002, "m");
internal.setWaxThickness(0.001, "m");
pipe.setInternalGeometry(internal);

Flow Solver Options

FlowSolverInterface solver = flowSystem.getSolver();

// Solver settings
solver.setMaxIterations(100);
solver.setConvergenceCriteria(1e-6);
solver.setRelaxationFactor(0.8);

Integration with Process Equipment

import neqsim.process.equipment.pipeline.Pipeline;

// Use Pipeline equipment in ProcessSystem
Pipeline pipeline = new Pipeline("Export Line", inletStream);
pipeline.setLength(50.0, "km");
pipeline.setDiameter(0.5, "m");
pipeline.setOutletPressure(30.0, "bara");
pipeline.run();

Stream outlet = pipeline.getOutletStream();
double Tout = outlet.getTemperature("C");

Visualization

// Get display interface
FlowSystemVisualizationInterface display = flowSystem.getDisplay();

// Plot pressure profile
display.plotPressureProfile();

// Plot temperature profile
display.plotTemperatureProfile();

// Plot holdup (two-phase)
display.plotHoldupProfile();

Example: Gas Pipeline

// Natural gas pipeline simulation
SystemInterface gas = new SystemSrkEos(288.15, 80.0);
gas.addComponent("nitrogen", 0.02);
gas.addComponent("CO2", 0.01);
gas.addComponent("methane", 0.90);
gas.addComponent("ethane", 0.05);
gas.addComponent("propane", 0.02);
gas.setMixingRule("classic");

// Set flow rate
gas.setTotalFlowRate(50.0, "MSm3/day");

// Pipeline geometry
PipeGeometry pipe = new PipeGeometry("Gas Export");
pipe.setDiameter(0.9, "m");
pipe.setLength(200000.0, "m");  // 200 km
pipe.setRoughness(0.00004, "m");

// Flow system
OnePhasePipeFlowSystem gasFlow = new OnePhasePipeFlowSystem();
gasFlow.setInletFluid(gas);
gasFlow.setGeometry(pipe);
gasFlow.setInletPressure(80.0, "bara");
gasFlow.setNumberOfNodes(200);

gasFlow.init();
gasFlow.solveTransient(1);

// Results
System.out.println("Inlet pressure: " + gasFlow.getInletPressure() + " bar");
System.out.println("Outlet pressure: " + gasFlow.getOutletPressure() + " bar");
System.out.println("Pressure drop: " + gasFlow.getPressureDrop() + " bar");
System.out.println("Flow velocity: " + gasFlow.getFlowVelocity() + " m/s");

Best Practices

  1. Use appropriate number of nodes - more nodes for accuracy, fewer for speed
  2. Check flow regime in two-phase calculations
  3. Validate against correlations for your specific application
  4. Consider elevation profile for long pipelines
  5. Include heat transfer for hot fluids or cold environments
  6. Enable non-equilibrium for absorption and short-contact processes
  7. Use thermodynamic corrections for non-ideal liquid phases

Test Suite

The fluid mechanics package includes comprehensive unit tests:

Test File Coverage
TwoPhasePipeFlowSystemTest.java System setup, steady-state solving, mass/heat transfer, model comparisons
NonEquilibriumPipeFlowTest.java Non-equilibrium mass transfer, evaporation, dissolution, bidirectional transfer
FlowPatternDetectorTest.java Flow pattern detection models (Taitel-Dukler, Baker, Barnea, Beggs-Brill)
InterfacialAreaCalculatorTest.java Interfacial area calculations for all flow patterns
MassTransferCoefficientCalculatorTest.java Mass transfer coefficient correlations
TwoPhasePipeFlowSystemBuilderTest.java Builder API tests

Known Test Limitations

Some advanced test scenarios are disabled pending solver optimization:

See TwoPhasePipeFlowSystem_Development_Plan.md for details.


References

  1. Solbraa, E. (2002). Equilibrium and Non-Equilibrium Thermodynamics of Natural Gas Processing. Dr.ing. thesis, NTNU. ISBN: 978-82-471-5541-7. Available at NVA

  2. Krishna, R., Standart, G.L. (1976). Mass and energy transfer in multicomponent systems. Chemical Engineering Communications, 3(4-5), 201-275.

  3. Taylor, R., Krishna, R. (1993). Multicomponent Mass Transfer. Wiley.

  4. Bird, R.B., Stewart, W.E., Lightfoot, E.N. (2002). Transport Phenomena. 2nd ed. Wiley.

  5. Danckwerts, P.V. (1970). Gas-Liquid Reactions. McGraw-Hill.


Pipeline Index

Pipeline Modeling Documentation

Documentation Index

This documentation covers pipeline pressure drop, flow, and heat transfer calculations in NeqSim.

Overview & Getting Started

Document Description
Pipeline Pressure Drop Overview of all pipeline models, quick start examples
Model Recommendations Which model to use for your application

Detailed Model Documentation

Document Description
Beggs & Brill Correlation Multiphase flow correlation theory and usage
Friction Factor Models Haaland, Colebrook-White, laminar/turbulent
Heat Transfer Non-adiabatic operation, cooling, Gnielinski
Transient Simulation Dynamic simulation, slow wave propagation
Water Hammer Fast transients, pressure surges, MOC solver

Quick Model Selection

┌─────────────────────────────────────────────────────────────────┐
│                        PIPELINE MODELS                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Single-Phase Gas         →  AdiabaticPipe                     │
│  Single-Phase Liquid      →  PipeBeggsAndBrills                │
│  Two-Phase (Gas-Liquid)   →  PipeBeggsAndBrills                │
│  Three-Phase (G-O-W)      →  PipeBeggsAndBrills                │
│  With Elevation           →  PipeBeggsAndBrills                │
│  With Heat Transfer       →  PipeBeggsAndBrills                │
│  Slow Transient/Dynamic   →  PipeBeggsAndBrills                │
│  Water Hammer/Fast Trans. →  WaterHammerPipe                   │
│  Quick Estimate           →  AdiabaticTwoPhasePipe             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Key Classes

Class Package Description
PipeBeggsAndBrills neqsim.process.equipment.pipeline Multiphase, elevation, heat transfer, slow transient
WaterHammerPipe neqsim.process.equipment.pipeline Water hammer, fast pressure transients (MOC)
AdiabaticPipe neqsim.process.equipment.pipeline Single-phase compressible gas
AdiabaticTwoPhasePipe neqsim.process.equipment.pipeline Two-phase, horizontal
TwoFluidPipe neqsim.process.equipment.pipeline Two-fluid model with drift-flux
TransientPipe neqsim.process.equipment.pipeline Transient drift-flux with AUSM+ scheme
TwoPhasePipeFlowSystem neqsim.fluidmechanics.flowsystem Low-level non-equilibrium mass/heat transfer

Low-Level Fluid Mechanics

For detailed non-equilibrium mass and heat transfer calculations, the TwoPhasePipeFlowSystem in the fluidmechanics package provides:

See Fluid Mechanics README and Two-Phase Pipe Flow Model for details.

Common Parameters

Geometry

Numerical

Calculation Mode

Heat Transfer

Transient

Typical Roughness Values

Pipe Material Roughness (mm) Roughness (m)
New steel 0.046 4.6×10⁻⁵
Corroded steel 0.15-0.3 1.5-3×10⁻⁴
Stainless 0.015 1.5×10⁻⁵
Plastic/GRP 0.005 5×10⁻⁶

Validation Summary

Test Case Model Deviation
Gas (Darcy-Weisbach) All models <1%
Liquid turbulent Beggs-Brill -1.4%
Liquid laminar Beggs-Brill 0%
Uphill two-phase Beggs-Brill Validated
Transient convergence Beggs-Brill <15%

Version History

Support

For questions or issues:

Flow Equations

Pipeline Flow Simulation: Governing Equations and Numerical Methods

This document provides a comprehensive reference for single-phase pipeline flow simulation in NeqSim, including the governing equations, discretization schemes, and numerical solution methods.

Table of Contents

  1. Governing Equations
  2. Steady-State Solution
  3. Transient Solution
  4. Compositional Tracking
  5. Advection Schemes and Numerical Dispersion
  6. Boundary Conditions
  7. Solution Algorithm

Governing Equations

Conservation of Mass (Continuity)

The one-dimensional continuity equation for compressible flow in a pipe:

$$ \frac{\partial \rho}{\partial t} + \frac{\partial (\rho v)}{\partial x} = 0 $$

Where:

Conservation of Momentum

The momentum equation including friction and gravity:

$$ \frac{\partial (\rho v)}{\partial t} + \frac{\partial (\rho v^2)}{\partial x} = -\frac{\partial P}{\partial x} - \frac{f \rho v |v|}{2D} - \rho g \sin\theta $$

Where:

Conservation of Energy

The energy equation with heat transfer:

$$ \frac{\partial (\rho e)}{\partial t} + \frac{\partial (\rho v h)}{\partial x} = \frac{q_{wall}}{\pi D^2 / 4} $$

Where:

Species Conservation (Compositional Tracking)

For each component $i$:

$$ \frac{\partial (\rho w_i)}{\partial t} + \frac{\partial (\rho v w_i)}{\partial x} = 0 $$

Where:


Steady-State Solution

Pressure Drop Calculation

For steady-state flow, the momentum equation simplifies to:

$$ \frac{dP}{dx} = -\frac{f \rho v^2}{2D} - \rho g \sin\theta $$

The Darcy friction factor $f$ is calculated using the Colebrook-White equation:

$$ \frac{1}{\sqrt{f}} = -2 \log_{10}\left(\frac{\varepsilon/D}{3.7} + \frac{2.51}{Re \sqrt{f}}\right) $$

Where:

Discretization

The pipe is divided into $N$ nodes. For node $i$:

$$ P_{i+1} = P_i - \Delta x \left(\frac{f_i \rho_i v_i^2}{2D} + \rho_i g \sin\theta_i\right) $$


Transient Solution

Finite Volume Method

NeqSim uses a staggered grid finite volume method. The pipe is divided into control volumes with:

Time Discretization

Implicit (backward) Euler scheme for stability:

$$ \frac{\phi^{n+1} - \phi^n}{\Delta t} + \frac{\partial F}{\partial x}\bigg|^{n+1} = S^{n+1} $$

Where:

CFL Condition

For numerical stability, the Courant-Friedrichs-Lewy (CFL) number should satisfy:

$$ CFL = \frac{(v + c) \Delta t}{\Delta x} \leq 1 $$

Where $c$ is the speed of sound in the fluid.

TDMA Solver

The discretized equations form a tri-diagonal matrix system:

$$ a_i \phi_{i-1} + b_i \phi_i + c_i \phi_{i+1} = r_i $$

Solved using the Thomas algorithm (TDMA).


Compositional Tracking

Conservation Equation

The mass fraction transport equation in conservative form:

$$ \frac{\partial (\rho A w)}{\partial t} + \frac{\partial (\dot{m} w)}{\partial x} = 0 $$

Where:

Discretized Form

For control volume $i$:

$$ \frac{(\rho A w)_i^{n+1} - (\rho A w)_i^{n}}{\Delta t} + \frac{F_e - F_w}{\Delta x} = 0 $$

Where $F_e$ and $F_w$ are the convective fluxes at east and west faces.

First-Order Upwind Scheme

The convective flux at a face is:

$$ F_e = \max(\dot{m}_e, 0) w_i + \min(\dot{m}_e, 0) w_{i+1} $$

This leads to the coefficient matrix:


Advection Schemes and Numerical Dispersion

The Numerical Dispersion Problem

First-order upwind introduces artificial (numerical) diffusion:

$$ D_{num} = \frac{v \Delta x}{2} (1 - CFL) $$

This causes composition fronts to spread over distance:

$$ \sigma = \sqrt{2 D_{num} t} = \sqrt{\Delta x \cdot L \cdot (1 - CFL)} $$

Where $L$ is the transport distance.

Available Advection Schemes

Scheme Order Numerical Dispersion Stability
First-Order Upwind 1 High Unconditional
Second-Order Upwind 2 Low CFL ≤ 0.5
QUICK 3 Very Low CFL ≤ 0.5
TVD Van Leer 2 Low CFL ≤ 1.0
TVD Minmod 2 Medium CFL ≤ 1.0
TVD Superbee 2 Very Low CFL ≤ 1.0
TVD Van Albada 2 Low CFL ≤ 1.0
MUSCL Van Leer 2 Low CFL ≤ 1.0

TVD (Total Variation Diminishing) Schemes

TVD schemes use flux limiters to achieve high accuracy in smooth regions while preventing oscillations near discontinuities.

The flux limiter $\psi(r)$ depends on the gradient ratio:

$$ r = \frac{\phi_i - \phi_{i-1}}{\phi_{i+1} - \phi_i} $$

Limiter Functions

Minmod (most diffusive): $$ \psi(r) = \max(0, \min(r, 1)) $$

Van Leer (recommended): $$ \psi(r) = \frac{r + |r|}{1 + |r|} $$

Superbee (least diffusive): $$ \psi(r) = \max(0, \min(2r, 1), \min(r, 2)) $$

Van Albada (smooth): $$ \psi(r) = \frac{r^2 + r}{r^2 + 1} $$

TVD Flux Correction

The higher-order flux correction is:

$$ F_{HO} = F_{LO} + \frac{1}{2} \psi(r) |F| (1 - |CFL|) (\phi_{downstream} - \phi_{upstream}) $$

Where $F_{LO}$ is the first-order upwind flux.

Dispersion Reduction

The effective numerical diffusion with TVD schemes:

$$ D_{eff} = D_{num} \times \text{ReductionFactor} $$

Typical reduction factors:


Boundary Conditions

Inlet Boundary

Fixed conditions from upstream:

Outlet Boundary

Typically one of:

Implementation

// Inlet: Dirichlet condition
a[0] = 0;
b[0] = 1;
c[0] = 0;
r[0] = w_inlet;

// Interior nodes: discretized conservation equation
// ... (TDMA coefficients)

// Outlet: extrapolation or fixed value

Solution Algorithm

Steady-State Algorithm

  1. Initialize with linear pressure profile
  2. Calculate fluid properties at each node
  3. Calculate friction factors
  4. Solve momentum equation for pressure
  5. Update velocities
  6. Repeat until convergence

Transient Algorithm

for each time step:
    1. Apply inlet boundary conditions
    2. Calculate time step (CFL condition)
    3. Assemble coefficient matrices
    4. Solve TDMA for each conservation equation:
       - Momentum (pressure/velocity)
       - Energy (temperature)
       - Species (composition)
    5. Update fluid properties (EOS flash)
    6. Update outlet stream
    7. Store results

Code Example

// Create pipeline
OnePhasePipeLine pipe = new OnePhasePipeLine("GasPipe", inletStream);
pipe.setNumberOfLegs(1);
pipe.setNumberOfNodesInLeg(100);
pipe.setPipeDiameters(new double[] {0.3, 0.3});
pipe.setLegPositions(new double[] {0.0, 5000.0});

// Enable compositional tracking with TVD scheme
pipe.setCompositionalTracking(true);
pipe.setAdvectionScheme(AdvectionScheme.TVD_VAN_LEER);

// Run steady-state initialization
pipe.run();

// Run transient simulation
UUID id = UUID.randomUUID();
for (int step = 0; step < 100; step++) {
    pipe.runTransient(1.0, id);  // 1 second time step
}

References

  1. Patankar, S.V. (1980). Numerical Heat Transfer and Fluid Flow. Taylor & Francis.
  2. Versteeg, H.K. & Malalasekera, W. (2007). An Introduction to Computational Fluid Dynamics. Pearson.
  3. LeVeque, R.J. (2002). Finite Volume Methods for Hyperbolic Problems. Cambridge University Press.
  4. Sweby, P.K. (1984). "High Resolution Schemes Using Flux Limiters for Hyperbolic Conservation Laws". SIAM J. Numer. Anal. 21(5): 995-1011.

Single Phase Flow

Single-Phase Gas Pipe Flow Simulation

Overview

NeqSim provides single-phase gas pipeline simulation capabilities through the PipeFlowSystem class, implementing a staggered grid finite volume method with TDMA (Tri-Diagonal Matrix Algorithm) solver.

Architecture

Class Hierarchy

FlowSystem (abstract)
└── OnePhaseFlowSystem (abstract)
    └── PipeFlowSystem (concrete)

Key Components

Component Description
PipeFlowSystem Main flow system for single-phase pipe flow
OnePhaseFixedStaggeredGrid Staggered grid solver with TDMA
onePhasePipeFlowNode Flow node for single-phase pipe segments
TimeSeries Time-varying inlet conditions for transient simulation

Governing Equations

The solver implements the following conservation equations:

Mass Conservation

$$\frac{\partial \rho}{\partial t} + \frac{\partial (\rho v)}{\partial x} = 0$$

Momentum Conservation

$$\frac{\partial (\rho v)}{\partial t} + \frac{\partial (\rho v^2)}{\partial x} = -\frac{\partial P}{\partial x} - \rho g \sin(\theta) - \frac{f \rho v |v|}{2D}$$

where:

Energy Conservation

$$\frac{\partial (\rho h)}{\partial t} + \frac{\partial (\rho v h)}{\partial x} = Q_{wall} + \rho v g \sin(\theta)$$

where:

Component Conservation

For each component $i$:

$$\frac{\partial (\rho \omega_i)}{\partial t} + \frac{\partial (\rho v \omega_i)}{\partial x} = 0$$

where $\omega_i$ is the mass fraction of component $i$.

Numerical Method

Staggered Grid Discretization

The solver uses a staggered grid approach:

TDMA Solver

The Tri-Diagonal Matrix Algorithm efficiently solves the linearized system:

a[i] * φ[i-1] + b[i] * φ[i] + c[i] * φ[i+1] = r[i]

Upwind Scheme

Convective terms use upwind differencing for stability:

a[i] = Math.max(Fw, 0);  // West face flux
c[i] = Math.max(-Fe, 0); // East face flux

Solver Types

The solver supports different levels of physics:

Type Description
0 Momentum only (isothermal, incompressible)
1 Momentum + mass (compressible)
10 Momentum + mass + energy
20 Momentum + mass + energy + composition

Usage Example

Steady-State Simulation

import neqsim.fluidmechanics.flowsystem.onephaseflowsystem.pipeflowsystem.PipeFlowSystem;
import neqsim.fluidmechanics.geometrydefinitions.pipe.PipeData;
import neqsim.thermo.system.SystemSrkEos;

// Create gas system
SystemInterface gas = new SystemSrkEos(288.15, 100.0); // 15°C, 100 bar
gas.addComponent("methane", 0.90);
gas.addComponent("ethane", 0.10);
gas.createDatabase(true);
gas.init(0);
gas.init(3);
gas.initPhysicalProperties();
gas.setTotalFlowRate(10.0, "MSm3/day");

// Configure pipeline
FlowSystemInterface pipe = new PipeFlowSystem();
pipe.setInletThermoSystem(gas);
pipe.setNumberOfLegs(10);
pipe.setNumberOfNodesInLeg(20);

// Set geometry (10 segments)
double[] heights = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
double[] positions = {0, 10000, 20000, 30000, 40000, 50000, 
                      60000, 70000, 80000, 90000, 100000}; // meters

GeometryDefinitionInterface[] geometry = new PipeData[11];
for (int i = 0; i <= 10; i++) {
    geometry[i] = new PipeData();
    geometry[i].setDiameter(1.0);  // 1 meter diameter
    geometry[i].setInnerSurfaceRoughness(1e-5);
}

pipe.setEquipmentGeometry(geometry);
pipe.setLegHeights(heights);
pipe.setLegPositions(positions);
pipe.setLegOuterTemperatures(new double[]{278, 278, 278, 278, 278, 278, 
                                           278, 278, 278, 278, 278});
pipe.setLegWallHeatTransferCoefficients(new double[]{15, 15, 15, 15, 15, 15,
                                                      15, 15, 15, 15, 15});
pipe.setLegOuterHeatTransferCoefficients(new double[]{5, 5, 5, 5, 5, 5,
                                                       5, 5, 5, 5, 5});

// Solve
pipe.createSystem();
pipe.init();
pipe.solveSteadyState(10);  // Type 10: with energy equation

// Get results
double pressureDrop = pipe.getTotalPressureDrop();
double outletTemp = pipe.getNode(pipe.getTotalNumberOfNodes() - 1)
    .getBulkSystem().getTemperature();

Dynamic/Transient Simulation

The transient solver supports time-varying inlet conditions including changes in:

Transient Simulation Example

// First solve steady state to initialize
pipe.createSystem();
pipe.init();
pipe.solveSteadyState(10);

// Setup time series with varying inlet conditions
// Note: times array has N points, systems array has N-1 entries (one per interval)
double[] times = {0, 3000, 6000};  // 3 time points = 2 intervals
pipe.getTimeSeries().setTimes(times);

// Initial cold gas
SystemInterface coldGas = new SystemSrkEos(280.0, 100.0);
coldGas.addComponent("methane", 0.90);
coldGas.addComponent("ethane", 0.10);
coldGas.createDatabase(true);
coldGas.init(0);
coldGas.init(3);
coldGas.initPhysicalProperties();
coldGas.setTotalFlowRate(10.0, "MSm3/day");

// Hot gas with different composition
SystemInterface hotGas = new SystemSrkEos(320.0, 100.0);
hotGas.addComponent("methane", 0.80);
hotGas.addComponent("ethane", 0.20);
hotGas.createDatabase(true);
hotGas.init(0);
hotGas.init(3);
hotGas.initPhysicalProperties();
hotGas.setTotalFlowRate(10.0, "MSm3/day");

// 2 intervals: [0-3000] cold, [3000-6000] hot
SystemInterface[] systems = {coldGas, hotGas};
pipe.getTimeSeries().setInletThermoSystems(systems);
pipe.getTimeSeries().setNumberOfTimeStepsInInterval(5);

// Run transient simulation with full physics (type 20 = momentum + mass + energy + composition)
pipe.solveTransient(20);

Compositional Tracking

Steady-State Composition

In steady-state single-phase flow, composition is uniform throughout the pipeline:

Dynamic Composition Tracking

Dynamic compositional tracking enables simulating slug flow, batch processing, and compositional transitions:

  1. oldComposition[component][node] stores previous time step values
  2. setComponentConservationMatrix() builds the discretized equations
  3. initComposition() updates node compositions after each time step

Example - Compositional Change at Inlet:

// Initial gas with ethane
SystemInterface initialGas = new SystemSrkEos(298.0, 30.0);
initialGas.addComponent("methane", 0.9);
initialGas.addComponent("ethane", 0.1);
initialGas.initPhysicalProperties();
initialGas.setTotalFlowRate(10.0, "MSm3/day");

// New gas (pure methane)
SystemInterface newGas = initialGas.clone();
newGas.addComponent("methane", 0.1);  // Shift to 100% methane
newGas.initPhysicalProperties();

// TimeSeries with 2 intervals
SystemInterface[] systems = {initialGas, newGas};
pipe.getTimeSeries().setInletThermoSystems(systems);
pipe.getTimeSeries().setNumberOfTimeStepsInInterval(10);

// Run with compositional tracking (type 20)
pipe.solveTransient(20);

Physical Effects Captured

Pressure Drop

Temperature Effects

Compressibility

Validation Results

The steady-state solver has been validated for:

Test Result
Pressure monotonically decreases ✓ Pass
Temperature approaches surroundings ✓ Pass
Mass conservation (inlet ≈ outlet) ✓ Pass (within 15%)
Reynolds number physically correct ✓ Pass
Friction factor in reasonable range ✓ Pass
Composition preserved ✓ Pass
Numerical stability (high flow) ✓ Pass
Inclined pipeline handling ✓ Pass

Known Limitations

  1. Single-phase only: No phase transition handling
  2. Composition drift: Small numerical drift (~1%) in composition over long pipelines
  3. TimeSeries API: Inlet systems array must have N-1 elements for N time points (one system per interval)

Recommendations

For Improved Mass Conservation

Consider implementing:

TimeSeries Best Practices

When setting up transient simulations:

// CORRECT: 3 time points → 2 systems (one per interval)
double[] times = {0, 3000, 6000};
pipe.getTimeSeries().setOutletMolarFlowRates(times, "kg/sec");

SystemInterface[] systems = {gasForInterval1, gasForInterval2};
pipe.getTimeSeries().setInletThermoSystems(systems);

References

  1. Patankar, S.V. (1980). Numerical Heat Transfer and Fluid Flow. Hemisphere Publishing.
  2. Solbraa, E. (2002). Equilibrium and Non-Equilibrium Thermodynamics of Natural Gas Processing. PhD Thesis, NTNU.

Chapter 25: Pressure Drop Calculations

Pressure Drop

Pipeline Pressure Drop Calculations in NeqSim

Overview

NeqSim provides three main pipeline models for calculating pressure drop:

Model Class Best For
AdiabaticPipe Single-phase compressible gas High-pressure gas transmission
AdiabaticTwoPhasePipe General two-phase flow Moderate accuracy, fast computation
PipeBeggsAndBrills Multiphase flow with correlations Wells, flowlines, complex terrain

Quick Start

Single-Phase Gas Pipeline

// Create gas system
SystemInterface gas = new SystemSrkEos(298.15, 100.0); // 25°C, 100 bara
gas.addComponent("methane", 0.9);
gas.addComponent("ethane", 0.1);
gas.setMixingRule("classic");

Stream feed = new Stream("feed", gas);
feed.setFlowRate(50000, "kg/hr");
feed.run();

// Simple pipe model
AdiabaticPipe pipe = new AdiabaticPipe("pipeline", feed);
pipe.setLength(10000);        // 10 km
pipe.setDiameter(0.3);        // 300 mm
pipe.run();

double pressureDrop = feed.getPressure() - pipe.getOutletPressure();

Two-Phase (Gas-Liquid) Pipeline

// Create two-phase system
SystemInterface fluid = new SystemSrkEos(333.15, 50.0); // 60°C, 50 bara
fluid.addComponent("methane", 5000, "kg/hr");
fluid.addComponent("nC10", 50000, "kg/hr");
fluid.setMixingRule(2);

Stream feed = new Stream("feed", fluid);
feed.run();

// Beggs & Brill correlation
PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("flowline", feed);
pipe.setLength(1000);           // 1 km
pipe.setDiameter(0.1);          // 100 mm
pipe.setElevation(50);          // 50 m uphill
pipe.setPipeWallRoughness(4.6e-5); // Steel roughness
pipe.setNumberOfIncrements(20);
pipe.run();

// Get results
double dp = pipe.getInletPressure() - pipe.getOutletPressure();
String flowRegime = pipe.getFlowRegime().toString();
double liquidHoldup = pipe.getSegmentLiquidHoldup(20);

Calculate Flow Rate from Outlet Pressure

All pipeline models support a reverse calculation mode where you specify the desired outlet pressure and the model calculates the required flow rate:

// Create gas system with initial flow estimate
SystemInterface gas = new SystemSrkEos(298.15, 100.0); // 25°C, 100 bara
gas.addComponent("methane", 1.0);
gas.setMixingRule("classic");
gas.setTotalFlowRate(10000, "kg/hr"); // Initial estimate (will be recalculated)

Stream feed = new Stream("feed", gas);
feed.run();

// PipeBeggsAndBrills - most accurate for flow calculation
PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("pipeline", feed);
pipe.setLength(10000);           // 10 km
pipe.setDiameter(0.3);           // 300 mm
pipe.setPipeWallRoughness(4.6e-5);
pipe.setNumberOfIncrements(10);
pipe.setOutletPressure(90.0);    // Specify target outlet pressure (bara)
pipe.run();

// Get calculated flow rate
double flowRate = pipe.getInletStream().getFlowRate("kg/hr");
double achievedOutletP = pipe.getOutletPressure();
// flowRate ≈ 144,000 kg/hr for 10 bar pressure drop

Supported Models for Flow Calculation

Model Method Accuracy Notes
PipeBeggsAndBrills setOutletPressure(double) Best Uses bisection iteration
AdiabaticPipe setOutPressure(double) Good Single-phase gas only
AdiabaticTwoPhasePipe setOutPressure(double) Moderate Two-phase capable

Model Selection Guide

Use AdiabaticPipe when:

Use AdiabaticTwoPhasePipe when:

Use PipeBeggsAndBrills when:

Accuracy Comparison

Based on validation against Darcy-Weisbach reference:

Condition AdiabaticPipe TwoPhasePipe BeggsAndBrills
Single-phase gas +0.9% -0.1% +0.5%
Single-phase liquid (turbulent) -4.1% -1.4% -1.4%
Single-phase liquid (laminar) N/A ~0% ~0%
Two-phase horizontal N/A N/A Validated
Inclined pipe N/A N/A Validated

Calculation Modes

Forward Mode (Default)

Specify flow rate → Calculate outlet pressure

feed.setFlowRate(50000, "kg/hr");  // Known flow rate
pipe.run();
double pOut = pipe.getOutletPressure();  // Calculated

Reverse Mode

Specify outlet pressure → Calculate flow rate

pipe.setOutletPressure(90.0);  // Target outlet pressure
pipe.run();
double flow = feed.getFlowRate("kg/hr");  // Calculated

The reverse calculation uses a bisection algorithm that iteratively adjusts the flow rate until the calculated outlet pressure matches the specified target.

See Also

Beggs & Brill

Beggs & Brill Correlation for Multiphase Pipe Flow

Overview

The Beggs & Brill correlation (1973) is a widely-used empirical method for predicting pressure drop and liquid holdup in multiphase pipe flow. It handles:

Theory

Total Pressure Gradient

The total pressure gradient consists of three components:

$$\frac{dP}{dL} = \frac{dP}{dL}_{friction} + \frac{dP}{dL}_{hydrostatic} + \frac{dP}{dL}_{acceleration}$$

In NeqSim, the acceleration term is typically neglected (small for steady flow), so:

$$\Delta P = \Delta P_{friction} + \Delta P_{hydrostatic}$$

Flow Regime Determination

The correlation identifies four flow regimes based on dimensionless parameters:

Flow Regime Description Typical Conditions
Segregated Stratified or wavy flow Low velocities, horizontal
Intermittent Slug or plug flow Moderate velocities
Distributed Bubble or mist flow High velocities
Transition Between segregated and intermittent Transitional

Flow regime is determined by:

Where:

Liquid Holdup Calculation

Liquid holdup ($H_L$ or $E_L$) is the fraction of pipe cross-section occupied by liquid:

$$H_L = H_L(0) \cdot \psi$$

Where:

Horizontal holdup correlations:

Regime Correlation
Segregated $H_L(0) = \frac{0.98 \lambda_L^{0.4846}}{Fr^{0.0868}}$
Intermittent $H_L(0) = \frac{0.845 \lambda_L^{0.5351}}{Fr^{0.0173}}$
Distributed $H_L(0) = \frac{1.065 \lambda_L^{0.5824}}{Fr^{0.0609}}$

Friction Pressure Loss

$$\Delta P_{friction} = \frac{f_{tp} \cdot \rho_{ns} \cdot v_m^2 \cdot L}{2D}$$

Where:

Hydrostatic Pressure Drop

$$\Delta P_{hydrostatic} = \rho_m \cdot g \cdot \Delta h$$

Where:

Usage in NeqSim

Basic Configuration

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("flowline", inletStream);

// Geometry
pipe.setLength(1000);              // meters
pipe.setDiameter(0.1);             // meters
pipe.setElevation(100);            // meters (positive = uphill)
pipe.setAngle(5.7);                // degrees (alternative to elevation)
pipe.setPipeWallRoughness(4.6e-5); // meters (steel ≈ 0.046 mm)

// Numerical settings
pipe.setNumberOfIncrements(20);    // segments for integration

pipe.run();

Accessing Results

// Overall results
double pressureDrop = pipe.getInletPressure() - pipe.getOutletPressure();
double outletTemp = pipe.getOutletTemperature();

// Flow regime
PipeBeggsAndBrills.FlowRegime regime = pipe.getFlowRegime();
// Returns: SEGREGATED, INTERMITTENT, DISTRIBUTED, TRANSITION, or SINGLE_PHASE

// Profile data (for segment i)
double holdup = pipe.getSegmentLiquidHoldup(i);
double mixtureDensity = pipe.getSegmentMixtureDensity(i);
double velocity = pipe.getSegmentMixtureSuperficialVelocity(i);

// Full profiles
List<Double> pressureProfile = pipe.getPressureProfile();
List<Double> temperatureProfile = pipe.getTemperatureProfile();

Heat Transfer Options

// Adiabatic (default)
pipe.setRunAdiabatic(true);

// With heat transfer
pipe.setRunAdiabatic(false);
pipe.setConstantSurfaceTemperature(283.15);  // 10°C ambient
pipe.setHeatTransferCoefficient(10.0);       // W/m²K
// Or let it estimate:
pipe.setHeatTransferCoefficientMethod("Estimated");

Three-Phase Flow (Gas-Oil-Water)

For three-phase systems, the liquid phase properties are calculated as volume-weighted averages:

SystemInterface fluid = new SystemSrkEos(333.15, 30.0);
fluid.addComponent("methane", 3000, "kg/hr");
fluid.addComponent("nC10", 40000, "kg/hr");
fluid.addComponent("water", 20000, "kg/hr");
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);  // Enable water phase

Stream feed = new Stream("feed", fluid);
feed.run();

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("pipe", feed);
// ... configure and run

Limitations

  1. Developed for oil & gas: Correlations based on oil/gas/water systems
  2. Pipe diameter range: Validated for 1-12 inch pipes
  3. Pressure range: Best for moderate pressures (1-100 bara)
  4. Inclination: Valid for -90° to +90° (horizontal to vertical)
  5. Viscosity: May underpredict for very high viscosity fluids
  6. Flow patterns: Simplified flow regime map; real systems may differ

Validation

NeqSim's implementation has been validated against:

Test Case Reference Deviation
Single-phase gas (turbulent) Darcy-Weisbach +0.5%
Single-phase liquid (turbulent) Darcy-Weisbach -1.4%
Single-phase liquid (laminar) Darcy-Weisbach 0.0%
Two-phase horizontal Dukler, Homogeneous Reasonable
Inclined pipes Steady-state physics Validated

References

  1. Beggs, H.D. and Brill, J.P. (1973). "A Study of Two-Phase Flow in Inclined Pipes". Journal of Petroleum Technology, 25(5), 607-617.

  2. Brill, J.P. and Mukherjee, H. (1999). Multiphase Flow in Wells. SPE Monograph Series.

  3. Shoham, O. (2006). Mechanistic Modeling of Gas-Liquid Two-Phase Flow in Pipes. SPE Books.

See Also

Friction Factors

Friction Factor Models in NeqSim Pipelines

Overview

Friction factor is a critical parameter in pressure drop calculations. NeqSim implements industry-standard correlations for both laminar and turbulent flow.

Friction Factor Equations

Laminar Flow (Re < 2300)

For laminar flow, the Darcy friction factor is:

$$f = \frac{64}{Re}$$

Where Reynolds number: $$Re = \frac{\rho v D}{\mu}$$

Transition Zone (2300 < Re < 4000)

Linear interpolation between laminar and turbulent:

$$f = f_{laminar} + \frac{Re - 2300}{1700}(f_{turbulent,4000} - f_{laminar,2300})$$

Turbulent Flow (Re > 4000)

Haaland Equation (Default)

NeqSim uses the Haaland equation, an explicit approximation of Colebrook-White:

$$f = \left[ -1.8 \log_{10}\left( \left(\frac{\varepsilon/D}{3.7}\right)^{1.11} + \frac{6.9}{Re} \right) \right]^{-2}$$

Where:

Advantages:

Colebrook-White Equation (Reference)

The implicit Colebrook-White equation (used for validation):

$$\frac{1}{\sqrt{f}} = -2 \log_{10}\left( \frac{\varepsilon/D}{3.7} + \frac{2.51}{Re\sqrt{f}} \right)$$

Solved iteratively using Newton-Raphson method.

Two-Phase Friction Factor

For multiphase flow, the single-phase friction factor is modified:

$$f_{tp} = f_{ns} \cdot e^S$$

Where:

The slip factor $S$ depends on the liquid holdup ratio: $$y = \frac{\lambda_L}{H_L^2}$$

For $1 < y < 1.2$: $$S = \ln(2.2y - 1.2)$$

Otherwise: $$S = \frac{\ln(y)}{-0.0523 + 3.18\ln(y) - 0.872[\ln(y)]^2 + 0.01853[\ln(y)]^4}$$

Pipe Roughness Values

Typical Roughness Values

Material Roughness ε (mm) Roughness ε (m)
Commercial steel (new) 0.046 4.6×10⁻⁵
Commercial steel (rusted) 0.15-0.3 1.5-3×10⁻⁴
Stainless steel 0.015 1.5×10⁻⁵
Drawn tubing (copper, brass) 0.0015 1.5×10⁻⁶
Cast iron 0.26 2.6×10⁻⁴
Concrete 0.3-3.0 3×10⁻⁴ to 3×10⁻³
PVC/Plastic 0.0015-0.007 1.5-7×10⁻⁶
GRP/FRP 0.01 1×10⁻⁵

Setting Roughness in NeqSim

// For PipeBeggsAndBrills
pipe.setPipeWallRoughness(4.6e-5);  // meters

// For AdiabaticPipe
pipe.setWallRoughness(4.6e-5);      // meters

Implementation Details

Reynolds Number Calculation

For two-phase flow, the no-slip Reynolds number is used:

$$Re_{ns} = \frac{\rho_{ns} \cdot v_m \cdot D}{\mu_{ns}}$$

Where:

Code Example

// Get friction-related results
PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("pipe", stream);
pipe.setLength(1000);
pipe.setDiameter(0.1);
pipe.setPipeWallRoughness(4.6e-5);
pipe.run();

// Access Reynolds number and friction factor for each segment
for (int i = 1; i <= pipe.getNumberOfIncrements(); i++) {
    double Re = pipe.getSegmentMixtureReynoldsNumber(i);
    // Friction factor is internal but affects pressure drop
}

Validation Results

Comparison of NeqSim friction factor implementation against Colebrook-White:

Reynolds ε/D Haaland f Colebrook f Deviation
10,000 0.001 0.0380 0.0382 -0.5%
100,000 0.001 0.0227 0.0228 -0.4%
1,000,000 0.001 0.0197 0.0197 0.0%
10,000,000 0.001 0.0191 0.0191 0.0%

Common Issues

1. Zero or Negative Friction Factor

2. Unrealistic Pressure Drop

3. Laminar Flow Not Recognized

References

  1. Haaland, S.E. (1983). "Simple and Explicit Formulas for the Friction Factor in Turbulent Pipe Flow". Journal of Fluids Engineering, 105(1), 89-90.

  2. Colebrook, C.F. (1939). "Turbulent Flow in Pipes with Particular Reference to the Transition Region Between Smooth and Rough Pipe Laws". Journal of the Institution of Civil Engineers, 11, 133-156.

  3. Moody, L.F. (1944). "Friction Factors for Pipe Flow". Transactions of the ASME, 66, 671-684.

See Also

Chapter 26: Heat Transfer in Pipelines

Heat Transfer

Heat Transfer in Pipelines

Overview

NeqSim's PipeBeggsAndBrills class supports non-adiabatic operation with heat exchange to/from the surroundings. This is important for:

Heat Transfer Modes

1. Adiabatic (Default)

No heat exchange with surroundings:

pipe.setRunAdiabatic(true);  // Default

2. Constant Surface Temperature

Heat transfer with fixed ambient temperature:

pipe.setRunAdiabatic(false);
pipe.setRunConstantSurfaceTemperature(true);
pipe.setConstantSurfaceTemperature(277.15);  // 4°C (seawater)

3. Specified Heat Transfer Coefficient

pipe.setHeatTransferCoefficient(50.0);  // W/m²K

4. Estimated Heat Transfer

Uses internal correlations:

pipe.setHeatTransferCoefficientMethod("Estimated");

Heat Transfer Equations

Overall Heat Balance

The temperature change across a segment is calculated from:

$$\dot{Q} = U \cdot A \cdot \Delta T_{lm}$$

Where:

Log-Mean Temperature Difference

$$\Delta T_{lm} = \frac{(T_s - T_{out}) - (T_s - T_{in})}{\ln\left(\frac{T_s - T_{out}}{T_s - T_{in}}\right)}$$

Where:

Gnielinski Correlation

For internal convection in turbulent flow (3000 < Re < 5×10⁶):

$$Nu = \frac{(f/8)(Re - 1000)Pr}{1 + 12.7\sqrt{f/8}(Pr^{2/3} - 1)}$$

Where:

The internal heat transfer coefficient: $$h_{internal} = \frac{Nu \cdot k}{D}$$

Typical Heat Transfer Coefficients

Overall U-Values (Pipeline + Insulation)

Configuration U (W/m²K) Application
Bare steel in air 10-25 Onshore exposed
Bare steel in water 300-500 Uninsulated subsea
25mm insulation 3-5 Standard insulated
50mm insulation 1.5-3 Well insulated
75mm+ insulation <1.5 Heavily insulated
Pipe-in-pipe 0.5-2 High spec subsea

Internal Convection Coefficients

Fluid h_internal (W/m²K)
Gas (low P) 20-50
Gas (high P) 100-300
Light oil 100-300
Heavy oil 50-150
Water 1000-5000
Two-phase 200-1000

Usage Examples

Subsea Pipeline Cooling

SystemInterface gas = new SystemSrkEos(353.15, 100.0);  // 80°C wellhead
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.10);
gas.addComponent("propane", 0.05);
gas.setMixingRule(2);

Stream wellhead = new Stream("wellhead", gas);
wellhead.setFlowRate(100000, "kg/hr");
wellhead.run();

PipeBeggsAndBrills subsea = new PipeBeggsAndBrills("subsea", wellhead);
subsea.setLength(20000);           // 20 km
subsea.setDiameter(0.254);         // 10 inch
subsea.setElevation(-200);         // 200m water depth
subsea.setPipeWallRoughness(4.6e-5);
subsea.setNumberOfIncrements(40);

// Heat transfer to seawater
subsea.setRunAdiabatic(false);
subsea.setRunConstantSurfaceTemperature(true);
subsea.setConstantSurfaceTemperature(277.15);  // 4°C seabed
subsea.setHeatTransferCoefficient(5.0);        // Insulated

subsea.run();

double outletTemp = subsea.getOutletTemperature() - 273.15;
System.out.println("Arrival temperature: " + outletTemp + " °C");

Temperature Profile

// Get temperature along the pipeline
List<Double> tempProfile = subsea.getTemperatureProfile();
double segmentLength = 20000.0 / 40;

for (int i = 0; i < tempProfile.size(); i++) {
    double distance = i * segmentLength / 1000.0;  // km
    double tempC = tempProfile.get(i) - 273.15;
    System.out.println(distance + " km: " + tempC + " °C");
}

Hydrate and Wax Considerations

Hydrate Formation

Monitor temperature relative to hydrate equilibrium:

double hydroEqTemp = ...; // From hydrate flash
double margin = outletTemp - hydroEqTemp;
if (margin < 5.0) {
    System.out.println("WARNING: Close to hydrate region");
}

Wax Appearance

Check against wax appearance temperature (WAT):

double WAT = ...; // From wax analysis
if (outletTemp < WAT) {
    System.out.println("WARNING: Below WAT - wax may deposit");
}

Limitations

  1. Steady-state heat transfer: No thermal mass of pipe wall
  2. Constant ambient: No variation along pipe length
  3. Single U-value: Same coefficient for entire pipe
  4. No Joule-Thomson: Expansion cooling handled separately

Best Practices

1. Segment Sizing for Heat Transfer

Use more segments for accurate temperature profiles:

pipe.setNumberOfIncrements(50);  // For long, cooling pipelines

2. Validate Against Simple Cases

For long pipes with large temperature change, check: $$T_{out} \approx T_s + (T_{in} - T_s) \cdot e^{-UAL/(\dot{m}c_p)}$$

3. Consider Two-Phase Effects

Heat transfer coefficients are higher for two-phase flow due to turbulence.

See Also

Heat Transfer Module

Heat Transfer Modeling in NeqSim

This document provides detailed documentation of the heat transfer models implemented in the NeqSim fluid mechanics package.

Table of Contents


Overview

NeqSim implements comprehensive heat transfer models for:

The models are based on established correlations and are coupled with the rigorous thermodynamic calculations in NeqSim.


Theoretical Background

Energy Balance

The energy conservation equation for pipe flow:

$$\frac{\partial (\rho h)}{\partial t} + \frac{\partial (\rho v h)}{\partial z} = \dot{Q}_{wall} + \dot{Q}_{interphase}$$

Where:

Heat Transfer Mechanisms

Mechanism Equation Application
Conduction $q = -k \nabla T$ Through solid walls, stagnant fluids
Convection $q = h (T_w - T_f)$ Flowing fluids to walls
Radiation $q = \epsilon \sigma (T_1^4 - T_2^4)$ High temperature systems
Latent heat $\dot{Q} = \dot{m} \Delta H_{vap}$ Phase change

Single-Phase Heat Transfer

Dimensionless Numbers

Number Definition Physical Meaning
Nusselt (Nu) $h \cdot d / k$ Ratio of convective to conductive heat transfer
Prandtl (Pr) $\mu c_p / k$ Ratio of momentum to thermal diffusivity
Reynolds (Re) $\rho v d / \mu$ Ratio of inertial to viscous forces
Péclet (Pe) $Re \cdot Pr$ Ratio of advective to diffusive heat transport

Prandtl Number

The Prandtl number characterizes the relative thickness of thermal and velocity boundary layers:

// Calculated in FlowNode
double Pr = viscosity * heatCapacity / thermalConductivity;

Typical values:

Fluid Pr
Gases 0.7 - 1.0
Water 1.7 - 13
Light oils 10 - 1000
Heavy oils 100 - 100,000

Correlations

Laminar Flow (Re < 2300)

Constant wall temperature: $$Nu = 3.66$$

Constant heat flux: $$Nu = 4.36$$

Developing flow (Sieder-Tate): $$Nu = 1.86 \left(\frac{Re \cdot Pr \cdot d}{L}\right)^{1/3} \left(\frac{\mu}{\mu_w}\right)^{0.14}$$

Turbulent Flow (Re > 10,000)

Dittus-Boelter equation: $$Nu = 0.023 \cdot Re^{0.8} \cdot Pr^{n}$$

Where:

Gnielinski correlation (more accurate): $$Nu = \frac{(f/8)(Re - 1000)Pr}{1 + 12.7(f/8)^{0.5}(Pr^{2/3} - 1)}$$

Valid for: $3000 < Re < 5 \times 10^6$, $0.5 < Pr < 2000$

Transition Region (2300 < Re < 10,000)

Gnielinski correlation or linear interpolation between laminar and turbulent.

Implementation

// In InterphaseTransportCoefficientBaseClass
public double calcWallHeatTransferCoefficient(int phase, double prandtlNumber, 
                                               FlowNodeInterface node) {
    double Re = node.getReynoldsNumber(phase);
    double Nu;

    if (Re < 2300) {
        Nu = 3.66;  // Laminar
    } else if (Re < 10000) {
        // Transition - Gnielinski
        double f = calcWallFrictionFactor(phase, node);
        Nu = (f/8) * (Re - 1000) * prandtlNumber 
             / (1 + 12.7 * Math.sqrt(f/8) * (Math.pow(prandtlNumber, 2.0/3.0) - 1));
    } else {
        // Turbulent - Dittus-Boelter
        Nu = 0.023 * Math.pow(Re, 0.8) * Math.pow(prandtlNumber, 0.4);
    }

    return Nu * thermalConductivity / hydraulicDiameter;
}

Two-Phase Heat Transfer

Flow Pattern Effects

Heat transfer in two-phase flow depends strongly on the flow pattern:

Flow Pattern Dominant Mechanism Heat Transfer Characteristics
Stratified Convection in each phase Independent gas/liquid correlations
Annular Film evaporation/condensation High liquid-side coefficients
Slug Alternating mechanisms Time-averaged values
Bubble Enhanced liquid mixing Increased liquid-side coefficient
Mist Droplet evaporation Reduced wall wetting

Two-Phase Multiplier Approach

Some correlations use a two-phase multiplier:

$$h_{TP} = F \cdot h_{LO}$$

Where:

Flow Pattern-Specific Correlations

Stratified Flow

Gas and liquid are treated separately:

$$h_{gas} = \text{Single-phase correlation with } d_h = 4A_G/P_G$$ $$h_{liquid} = \text{Single-phase correlation with } d_h = 4A_L/P_L$$

Annular Flow

Liquid film: $$h_L = 0.023 \cdot Re_f^{0.8} \cdot Pr_L^{0.4} \cdot \frac{k_L}{\delta}$$

Where $\delta$ is the film thickness and $Re_f = 4\Gamma/\mu_L$ is the film Reynolds number.

Gas core: $$h_G = 0.023 \cdot Re_G^{0.8} \cdot Pr_G^{0.4} \cdot \frac{k_G}{d - 2\delta}$$


Wall Heat Transfer

Overall Heat Transfer Coefficient

For heat transfer from fluid to surroundings through the pipe wall:

$$\frac{1}{U} = \frac{1}{h_i} + \frac{r_i \ln(r_o/r_i)}{k_{wall}} + \frac{r_i}{r_o \cdot h_o}$$

Where:

Insulation

For insulated pipes, add insulation resistance:

$$\frac{1}{U} = \frac{1}{h_i} + \frac{r_i \ln(r_o/r_i)}{k_{wall}} + \frac{r_i \ln(r_{ins}/r_o)}{k_{ins}} + \frac{r_i}{r_{ins} \cdot h_o}$$

Buried Pipelines

For buried pipelines, the outer resistance includes soil conduction:

$$R_{soil} = \frac{\ln(2z/r_o)}{2\pi k_{soil}}$$

Where $z$ is the burial depth.

Usage in NeqSim

// Set overall heat transfer coefficient directly
pipe.setOverallHeatTransferCoefficient(10.0);  // W/(m²·K)

// Or specify components
pipe.setInnerHeatTransferCoefficient(1000.0);   // W/(m²·K)
pipe.setWallThickness(0.01);                     // m
pipe.setWallConductivity(50.0);                  // W/(m·K)
pipe.setInsulationThickness(0.05);               // m
pipe.setInsulationConductivity(0.04);            // W/(m·K)
pipe.setOuterHeatTransferCoefficient(10.0);     // W/(m²·K)

// Set ambient conditions
flowSystem.setSurroundingTemperature(288.15);    // K

Interphase Heat Transfer

Heat Transfer Between Phases

At the gas-liquid interface:

$$\dot{Q}_{GL} = h_{GL} \cdot a_i \cdot (T_G - T_L)$$

Where:

Interfacial Area

The interfacial area depends on the flow pattern:

Flow Pattern Interfacial Area $a_i$
Stratified $W/A_{pipe}$ (width / cross-section)
Annular $\pi(d - 2\delta)/A_{pipe}$
Bubble $6\epsilon_G/d_b$
Droplet $6\epsilon_L/d_d$

Chilton-Colburn Analogy

The heat and mass transfer coefficients are related:

$$\frac{h}{k_c \cdot \rho \cdot c_p} = \left(\frac{Sc}{Pr}\right)^{2/3}$$

Or in terms of j-factors:

$$j_H = j_D$$

Where: $$j_H = \frac{Nu}{Re \cdot Pr^{1/3}} = St \cdot Pr^{2/3}$$ $$j_D = \frac{Sh}{Re \cdot Sc^{1/3}}$$


Heat-Mass Transfer Coupling

Latent Heat Effects

When mass transfer occurs, the associated enthalpy must be considered:

$$\dot{Q}_{total} = \dot{Q}_{sensible} + \dot{Q}_{latent}$$

$$\dot{Q}_{latent} = \sum_j N_j \cdot \Delta H_{vap,j}$$

Interface Energy Balance

At the gas-liquid interface, the energy balance:

$$h_G (T_G - T_i) + \sum_j N_j H_j^G = h_L (T_i - T_L) + \sum_j N_j H_j^L$$

Rearranging:

$$h_G (T_G - T_i) - h_L (T_i - T_L) = \sum_j N_j (H_j^L - H_j^G) = -\sum_j N_j \Delta H_{vap,j}$$

Ackermann Correction

For high mass transfer rates, the sensible heat transfer is modified:

$$\dot{Q}_{sensible} = h \cdot \Phi \cdot (T_{bulk} - T_i)$$

Where the Ackermann correction factor:

$$\Phi = \frac{\phi}{e^\phi - 1}$$

And: $$\phi = \frac{\sum_j N_j c_{p,j}}{h}$$

Implementation

// In FluidBoundary
public double[] calcInterphaseHeatFlux() {
    // Sensible heat
    double Q_sensible = heatTransferCoefficient[0] * heatTransferCorrection[0] 
                        * (T_bulk - T_interface);

    // Latent heat
    double Q_latent = 0;
    for (int j = 0; j < nComponents; j++) {
        Q_latent += nFlux.get(j, 0) * deltaHvap[j];
    }

    interphaseHeatFlux[0] = Q_sensible + Q_latent;
    return interphaseHeatFlux;
}

Implementation Classes

Class Hierarchy

FluidBoundary
├── heatTransferCoefficient[2]    // Gas, Liquid
├── heatTransferCorrection[2]     // Ackermann factors
├── prandtlNumber[2]
└── interphaseHeatFlux[2]

InterphaseTransportCoefficientBaseClass
├── calcWallHeatTransferCoefficient()
├── calcInterphaseHeatTransferCoefficient()
└── calcWallFrictionFactor()

Key Methods

// InterphaseTransportCoefficientInterface
double calcWallHeatTransferCoefficient(int phase, double prandtlNumber, FlowNodeInterface node);
double calcInterphaseHeatTransferCoefficient(int phase, double prandtlNumber, FlowNodeInterface node);

// FluidBoundary
void setHeatTransferCalc(boolean calc);
double[] getInterphaseHeatFlux();
double getHeatTransferCoefficient(int phase);

Usage Examples

Basic Heat Transfer in Pipe Flow

import neqsim.fluidmechanics.flowsystem.onephaseflowsystem.pipeflowsystem.OnePhasePipeFlowSystem;
import neqsim.fluidmechanics.geometrydefinitions.pipe.PipeGeometry;
import neqsim.thermo.system.SystemSrkEos;

// Create hot gas
SystemSrkEos gas = new SystemSrkEos(373.15, 50.0);  // 100°C, 50 bar
gas.addComponent("methane", 0.95);
gas.addComponent("ethane", 0.05);
gas.setMixingRule("classic");

// Pipe geometry
PipeGeometry pipe = new PipeGeometry("Pipeline");
pipe.setDiameter(0.3, "m");
pipe.setLength(10000.0, "m");

// Heat transfer setup
pipe.setOverallHeatTransferCoefficient(5.0);  // W/(m²·K)

// Flow system
OnePhasePipeFlowSystem flow = new OnePhasePipeFlowSystem();
flow.setInletFluid(gas);
flow.setGeometry(pipe);
flow.setSurroundingTemperature(283.15);  // 10°C ambient
flow.setCalculateHeatTransfer(true);
flow.setNumberOfNodes(100);

flow.init();
flow.solveTransient(1);

// Temperature profile
for (int i = 0; i < flow.getNumberOfNodes(); i++) {
    double x = flow.getNode(i).getPosition();
    double T = flow.getNode(i).getTemperature() - 273.15;  // °C
    System.out.println("x = " + x + " m, T = " + T + " °C");
}

Two-Phase with Interphase Heat Transfer

import neqsim.fluidmechanics.flownode.twophasenode.twophasepipeflownode.StratifiedFlowNode;

// Create two-phase system
SystemSrkEos fluid = new SystemSrkEos(280.0, 30.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("n-pentane", 0.10);
fluid.addComponent("n-decane", 0.05);
fluid.setMixingRule("classic");

// Initialize with phase split
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Create flow node
PipeData pipe = new PipeData(0.2);  // 0.2 m diameter
StratifiedFlowNode node = new StratifiedFlowNode(fluid, pipe);
node.init();

// Enable heat transfer calculations
node.getFluidBoundary().setHeatTransferCalc(true);
node.getFluidBoundary().setMassTransferCalc(true);

// Solve
node.getFluidBoundary().solve();

// Get interphase heat flux
double[] Q = node.getFluidBoundary().getInterphaseHeatFlux();
System.out.println("Gas-side heat flux: " + Q[0] + " W/m²");
System.out.println("Liquid-side heat flux: " + Q[1] + " W/m²");

Condensation in Pipeline

// Hot gas entering cold pipeline
SystemSrkEos gas = new SystemSrkEos(320.0, 80.0);  // Hot, high pressure
gas.addComponent("methane", 0.80);
gas.addComponent("ethane", 0.10);
gas.addComponent("propane", 0.05);
gas.addComponent("n-butane", 0.03);
gas.addComponent("n-pentane", 0.02);
gas.setMixingRule("classic");

// Cold seabed pipeline
TwoPhasePipeFlowSystem flow = new TwoPhasePipeFlowSystem();
flow.setInletFluid(gas);
flow.setGeometry(seabedPipe);
flow.setSurroundingTemperature(277.15);  // 4°C seabed
flow.setCalculateHeatTransfer(true);

flow.init();
flow.solveTransient(1);

// Check for liquid formation
for (int i = 0; i < flow.getNumberOfNodes(); i++) {
    double liquidHoldup = flow.getNode(i).getPhaseFraction(1);
    if (liquidHoldup > 0.01) {
        System.out.println("Condensation at x = " + flow.getNode(i).getPosition() + " m");
        break;
    }
}

References

  1. Incropera, F.P., DeWitt, D.P., et al. (2007). Fundamentals of Heat and Mass Transfer. 6th ed. Wiley.

  2. Gnielinski, V. (1976). New equations for heat and mass transfer in turbulent pipe and channel flow. Int. Chem. Eng., 16(2), 359-368.

  3. Dittus, F.W., Boelter, L.M.K. (1930). Heat transfer in automobile radiators of the tubular type. Univ. Calif. Publ. Eng., 2(13), 443-461.

  4. Chilton, T.H., Colburn, A.P. (1934). Mass transfer (absorption) coefficients: Prediction from data on heat transfer and fluid friction. Ind. Eng. Chem., 26(11), 1183-1187.

  5. Solbraa, E. (2002). Equilibrium and Non-Equilibrium Thermodynamics of Natural Gas Processing. Dr.ing. thesis, NTNU. NVA

  6. Bird, R.B., Stewart, W.E., Lightfoot, E.N. (2002). Transport Phenomena. 2nd ed. Wiley.


Pipe Wall

Pipe Wall Construction and Heat Transfer Modeling

This document describes the pipe wall construction and heat transfer modeling capabilities in NeqSim, including material properties, multi-layer walls, and surrounding environment modeling.

Table of Contents

  1. Overview
  2. Pipe Materials
  3. Material Layers
  4. Pipe Wall Assembly
  5. Surrounding Environment
  6. Heat Transfer Calculations
  7. API Reference
  8. Examples

Overview

The pipe wall modeling system in NeqSim provides:


Pipe Materials

Standard Materials

NeqSim includes pre-defined pipe materials with thermal properties:

Material Thermal Conductivity (W/m·K) Density (kg/m³) Specific Heat (J/kg·K)
Carbon Steel 50.0 7850 490
Stainless Steel 316 16.3 8000 500
Duplex Steel 15.0 7800 500
Super Duplex 14.0 7800 500
Titanium 21.9 4500 523
Inconel 625 9.8 8440 410
Monel 400 21.8 8800 427
Copper 401.0 8960 385
HDPE 0.5 960 1800
PVC 0.19 1400 1000
GRP (Fiberglass) 0.3 1850 900

Creating Custom Materials

// Using standard material
PipeMaterial steel = PipeMaterial.CARBON_STEEL;

// Creating custom material
PipeMaterial custom = new PipeMaterial(
    "Custom Alloy",
    25.0,    // thermalConductivity (W/m·K)
    7500,    // density (kg/m³)
    480      // specificHeatCapacity (J/kg·K)
);

Material Properties


Material Layers

A MaterialLayer combines a material with its thickness:

// Create insulation layer
MaterialLayer insulation = new MaterialLayer(
    "Polyurethane Foam",
    0.025,   // thermalConductivity (W/m·K)
    40,      // density (kg/m³)
    1500,    // specificHeatCapacity (J/kg·K)
    0.05     // thickness (m) = 50 mm
);

// Using pipe material
MaterialLayer pipeWall = new MaterialLayer(
    PipeMaterial.CARBON_STEEL,
    0.012    // thickness = 12 mm
);

Layer Properties

Property Description Units
thickness Layer thickness m
thermalConductivity Heat conduction coefficient W/(m·K)
density Material density kg/m³
specificHeatCapacity Thermal capacity J/(kg·K)

Pipe Wall Assembly

Multi-Layer Construction

The PipeWall class represents a complete pipe wall with multiple layers:

// Method 1: Create layer by layer
PipeWall wall = new PipeWall(0.15);  // inner radius = 150 mm

wall.addLayer(new MaterialLayer(PipeMaterial.CARBON_STEEL, 0.012));
wall.addLayer(new MaterialLayer("Insulation", 0.025, 40, 1500, 0.050));
wall.addLayer(new MaterialLayer("Coating", 0.3, 1200, 1400, 0.005));

// Method 2: Using PipeWallBuilder (fluent API)
PipeWall wall = new PipeWallBuilder()
    .innerRadius(0.15)
    .addPipeLayer(PipeMaterial.CARBON_STEEL, 0.012)
    .addInsulationLayer(0.025, 0.050)
    .addCoatingLayer(0.3, 0.005)
    .build();

Thermal Resistance

The total radial thermal resistance through a cylindrical wall:

$$ R_{total} = \sum_{i=1}^{n} R_i = \sum_{i=1}^{n} \frac{\ln(r_{i+1}/r_i)}{2\pi k_i L} $$

Where:

Per unit length:

$$ R'_{total} = \sum_{i=1}^{n} \frac{\ln(r_{i+1}/r_i)}{2\pi k_i} \quad \text{(m·K/W)} $$

Key Properties

double outerRadius = wall.getOuterRadius();           // m
double totalThickness = wall.getTotalWallThickness(); // m
double resistance = wall.getTotalResistancePerLength(); // m·K/W
double heatCapacity = wall.getThermalMass();          // J/(m·K)
int layerCount = wall.getLayerCount();

Surrounding Environment

Environment Types

The PipeSurroundingEnvironment class models the external conditions:

Environment Description Typical $h$ (W/m²·K)
Still Air Natural convection 5-25
Moving Air Forced convection 10-200
Seawater Subsea pipelines 150-1000
Soil Buried pipelines 1-10

Creating Environments

// Using factory methods
PipeSurroundingEnvironment air = 
    PipeSurroundingEnvironment.stillAir(25.0);  // 25°C

PipeSurroundingEnvironment seawater = 
    PipeSurroundingEnvironment.seawater(4.0);   // 4°C

PipeSurroundingEnvironment soil = 
    PipeSurroundingEnvironment.soil(15.0, 1.5); // 15°C, k=1.5 W/m·K

// Custom environment
PipeSurroundingEnvironment custom = 
    new PipeSurroundingEnvironment("Wind", 10.0, 50.0);
    // 10°C ambient, h = 50 W/m²·K

Convection Coefficients

Still Air (Natural Convection): $$ h \approx 5 + 5\sqrt{T_{surface} - T_{ambient}} \quad \text{W/(m²·K)} $$

Seawater: $$ h \approx 150 + 70 \cdot v_{current}^{0.8} \quad \text{W/(m²·K)} $$

Soil (Buried Pipe): $$ h_{equiv} = \frac{k_{soil}}{r_o \cdot \ln(2H/r_o)} \quad \text{W/(m²·K)} $$

Where $H$ is burial depth and $r_o$ is outer radius.


Heat Transfer Calculations

Overall Heat Transfer Coefficient

The overall U-value combining all resistances:

$$ \frac{1}{U A} = \frac{1}{h_i A_i} + \sum \frac{\ln(r_{o}/r_i)}{2\pi k L} + \frac{1}{h_o A_o} $$

Based on outer surface area:

$$ U_o = \frac{1}{r_o \left(\frac{1}{h_i r_i} + \sum \frac{\ln(r_{i+1}/r_i)}{k_i} + \frac{1}{h_o}\right)} $$

Heat Transfer Rate

Heat flow per unit length:

$$ q' = U_o \cdot 2\pi r_o \cdot (T_{fluid} - T_{ambient}) \quad \text{W/m} $$

Total heat flow:

$$ Q = q' \cdot L = U_o \cdot A_o \cdot \Delta T \quad \text{W} $$

Temperature Profile

The fluid temperature along the pipe (steady-state):

$$ T(x) = T_{ambient} + (T_{inlet} - T_{ambient}) \exp\left(-\frac{U_o \cdot \pi D_o}{\dot{m} C_p} x\right) $$

Wall Temperature Distribution

Temperature at interface between layers $j$ and $j+1$:

$$ T_j = T_{fluid} - q' \cdot \left(\frac{1}{h_i \cdot 2\pi r_i} + \sum_{k=1}^{j} \frac{\ln(r_{k+1}/r_k)}{2\pi k_k}\right) $$


API Reference

PipeMaterial Enum

public enum PipeMaterial {
    CARBON_STEEL(50.0, 7850, 490),
    STAINLESS_316(16.3, 8000, 500),
    // ... more materials

    double getThermalConductivity();
    double getDensity();
    double getSpecificHeatCapacity();
    double getThermalDiffusivity();
}

MaterialLayer Class

public class MaterialLayer {
    // Constructors
    MaterialLayer(String name, double k, double rho, double cp, double t);
    MaterialLayer(PipeMaterial material, double thickness);

    // Properties
    double getThickness();
    double getThermalConductivity();
    double getDensity();
    double getSpecificHeatCapacity();

    // Calculations
    double getRadialResistance(double innerRadius);
    double getHeatCapacityPerLength(double innerRadius);
}

PipeWall Class

public class PipeWall {
    // Construction
    PipeWall(double innerRadius);
    void addLayer(MaterialLayer layer);

    // Properties
    double getInnerRadius();
    double getOuterRadius();
    double getTotalWallThickness();
    int getLayerCount();

    // Thermal calculations
    double getTotalResistancePerLength();
    double getUValuePerLength(double hInner, double hOuter);
    double getThermalMass();
}

PipeSurroundingEnvironment Class

public class PipeSurroundingEnvironment {
    // Factory methods
    static stillAir(double ambientTemp);
    static movingAir(double ambientTemp, double windSpeed);
    static seawater(double ambientTemp);
    static soil(double ambientTemp, double thermalConductivity);

    // Properties
    double getAmbientTemperature();
    double getConvectionCoefficient();
}

PipeWallBuilder Class

public class PipeWallBuilder {
    PipeWallBuilder innerRadius(double r);
    PipeWallBuilder innerDiameter(double d);
    PipeWallBuilder addLayer(MaterialLayer layer);
    PipeWallBuilder addPipeLayer(PipeMaterial material, double thickness);
    PipeWallBuilder addInsulationLayer(double k, double thickness);
    PipeWallBuilder addCoatingLayer(double k, double thickness);
    PipeWall build();
}

Examples

Example 1: Subsea Pipeline

// Create multi-layer subsea pipe wall
PipeWall subseaPipe = new PipeWallBuilder()
    .innerDiameter(0.254)  // 10" ID
    .addPipeLayer(PipeMaterial.DUPLEX_STEEL, 0.0127)
    .addInsulationLayer(0.15, 0.060)  // Syntactic foam
    .addCoatingLayer(0.22, 0.006)     // Polypropylene
    .build();

// Seawater environment at 4°C
PipeSurroundingEnvironment seawater = 
    PipeSurroundingEnvironment.seawater(4.0);

// Calculate overall U-value
double hInner = 500;   // W/m²·K (turbulent gas flow)
double hOuter = seawater.getConvectionCoefficient();
double U = subseaPipe.getUValuePerLength(hInner, hOuter);

System.out.printf("U-value: %.2f W/(m²·K)%n", U);

Example 2: Buried Gas Pipeline

// Create insulated buried pipeline
PipeWall buriedPipe = new PipeWallBuilder()
    .innerDiameter(0.508)  // 20" ID
    .addPipeLayer(PipeMaterial.CARBON_STEEL, 0.0127)
    .addCoatingLayer(0.22, 0.003)  // FBE coating
    .build();

// Soil at 12°C, buried 1.5m deep
PipeSurroundingEnvironment soil = 
    PipeSurroundingEnvironment.soil(12.0, 1.2);

// Print configuration
System.out.printf("Wall thickness: %.1f mm%n", 
    buriedPipe.getTotalWallThickness() * 1000);
System.out.printf("Total resistance: %.4f m·K/W%n", 
    buriedPipe.getTotalResistancePerLength());

Example 3: Temperature Profile Calculation

// Pipeline parameters
double length = 50000;     // 50 km
double mDot = 15.0;        // kg/s
double Cp = 2500;          // J/(kg·K) - gas
double Tinlet = 80;        // °C
double Tambient = 5;       // °C
double Uo = 2.5;           // W/(m²·K) - overall U-value
double Do = 0.32;          // m - outer diameter

// Calculate outlet temperature
double exponent = -Uo * Math.PI * Do * length / (mDot * Cp);
double Toutlet = Tambient + (Tinlet - Tambient) * Math.exp(exponent);

System.out.printf("Outlet temperature: %.1f °C%n", Toutlet);
System.out.printf("Heat loss: %.0f kW%n", mDot * Cp * (Tinlet - Toutlet) / 1000);

Example 4: Integration with Flow Simulation

// Create fluid
SystemInterface gas = new SystemSrkEos(323.15, 50e5);
gas.addComponent("methane", 0.9);
gas.addComponent("ethane", 0.07);
gas.addComponent("propane", 0.03);
gas.setMixingRule("classic");

// Create inlet stream
Stream inlet = new Stream("Inlet", gas);
inlet.setFlowRate(500000, "kg/hr");
inlet.run();

// Create pipeline with heat transfer
OnePhasePipeLine pipe = new OnePhasePipeLine("Export", inlet);
pipe.setNumberOfLegs(1);
pipe.setNumberOfNodesInLeg(100);
pipe.setPipeDiameters(new double[] {0.508, 0.508});
pipe.setLegPositions(new double[] {0.0, 50000.0});
pipe.setOuterTemperature(278.15);  // 5°C ambient

// Run steady-state
pipe.run();

// Get outlet conditions
System.out.printf("Outlet T: %.1f °C%n", 
    pipe.getOutStream().getTemperature("C"));
System.out.printf("Outlet P: %.1f bara%n", 
    pipe.getOutStream().getPressure("bara"));

References

  1. Incropera, F.P. & DeWitt, D.P. (2011). Fundamentals of Heat and Mass Transfer. Wiley.
  2. GPSA Engineering Data Book. Gas Processors Suppliers Association.
  3. API 5L - Specification for Line Pipe.
  4. DNVGL-ST-F101 - Submarine Pipeline Systems.

Interphase

Interphase Multicomponent Mass and Heat Transfer in Two-Phase Pipe Flow

Overview

This document provides a detailed description of the theoretical models and numerical methods used in NeqSim for calculating interphase mass and heat transfer in two-phase gas-liquid pipe flow. The approach is based on non-equilibrium thermodynamics where the gas and liquid phases are not assumed to be in thermodynamic equilibrium at the interface.

Key Concepts:


1. Non-Equilibrium vs Equilibrium Models

1.1 Equilibrium Model (Flash Calculation)

In the equilibrium approach, phases are assumed to be in complete thermodynamic equilibrium:

$$y_i = K_i(T, P) \cdot x_i \quad \text{for all components } i$$

$$T_G = T_L = T$$

This is computationally simple but fails when:

1.2 Non-Equilibrium Model

The non-equilibrium model accounts for finite-rate mass and heat transfer:

$$y_i^{bulk} \neq K_i \cdot x_i^{bulk}$$

$$T_G^{bulk} \neq T_L^{bulk}$$

Interface Equilibrium: $$y_i^{int} = K_i(T^{int}, P) \cdot x_i^{int}$$

The driving forces are:


2. Multicomponent Mass Transfer Theory

2.1 Maxwell-Stefan Equations

For multicomponent diffusion, the Maxwell-Stefan equations describe the relationship between fluxes and driving forces:

$$-\frac{x_i}{RT}\nabla\mu_i = \sum_{j=1, j\neq i}^{n} \frac{x_i N_j - x_j N_i}{c_t D_{ij}}$$

Symbol Description Units
$x_i$ Mole fraction of component $i$ [-]
$\mu_i$ Chemical potential of component $i$ [J/mol]
$N_i$ Molar flux of component $i$ [mol/(m²·s)]
$c_t$ Total molar concentration [mol/m³]
$D_{ij}$ Maxwell-Stefan diffusivity [m²/s]
$R$ Gas constant [J/(mol·K)]
$T$ Temperature [K]

📘 Diffusivity Models: The Maxwell-Stefan diffusivities $D_{ij}$ are calculated using correlations documented in mass_transfer.md. NeqSim provides multiple models:

See the Model Selection Guide for recommendations.

2.2 Matrix Formulation

The Maxwell-Stefan equations can be written in matrix form:

$$(\mathbf{J}) = -c_t [\mathbf{B}]^{-1} [\mathbf{\Gamma}] \nabla(\mathbf{x})$$

Where:

Elements of [B]:

$$B_{ii} = \frac{x_i}{D_{i,n}} + \sum_{k=1, k\neq i}^{n} \frac{x_k}{D_{ik}}$$

$$B_{ij} = -x_i \left(\frac{1}{D_{ij}} - \frac{1}{D_{i,n}}\right), \quad i \neq j$$

Thermodynamic Factor Matrix:

$$\Gamma_{ij} = \delta_{ij} + x_i \frac{\partial \ln \gamma_i}{\partial x_j}$$

Where $\gamma_i$ is the activity coefficient and $\delta_{ij}$ is the Kronecker delta.

2.3 Film Theory

Film theory assumes that mass transfer resistance is confined to a thin stagnant film at the interface:

     Bulk Gas     |  Gas Film  | Interface |  Liquid Film  |   Bulk Liquid
    ─────────────┼────────────┼───────────┼───────────────┼─────────────
    y_i^bulk      →  y_i^int    =    K_i    ·   x_i^int     ←   x_i^bulk
    T_G^bulk      →  T^int      =   T^int   =   T^int       ←   T_L^bulk

Film thickness:

2.4 Krishna-Standart Film Model

The Krishna-Standart model extends the Maxwell-Stefan equations to film theory for multicomponent systems:

$$(\mathbf{N}) = c_t \mathbf{k} + x_t^{avg} N_t$$

Where $[\mathbf{k}]$ is the matrix of mass transfer coefficients:

$$[\mathbf{k}] = [\mathbf{B}]^{-1} [\mathbf{\Xi}]$$

Bootstrap Matrix [Ξ]:

The bootstrap matrix $[\mathbf{\Xi}]$ accounts for the effect of finite mass transfer rates (high flux correction):

$$[\mathbf{\Xi}] = \mathbf{\Phi} [\exp(\mathbf{\Phi}) - \mathbf{I}]^{-1}$$

Where: $$\mathbf{\Phi} = [\mathbf{B}_0]^{-1} N_t / c_t$$

At low fluxes: $[\mathbf{\Xi}] \rightarrow \mathbf{I}$ (identity matrix)

2.5 Mass Transfer Coefficients

Gas-phase mass transfer coefficient:

$$k_G = \frac{Sh \cdot D_G}{D_h}$$

Liquid-phase mass transfer coefficient:

$$k_L = \frac{Sh \cdot D_L}{D_h}$$

Sherwood Number Correlations:

Flow Regime Correlation
Turbulent (Re > 10,000) $Sh = 0.023 \cdot Re^{0.83} \cdot Sc^{0.44}$
Transitional Interpolation
Laminar (Re < 2,300) $Sh = 3.66$ (constant wall)

2.6 Interface Composition Calculation

The interface compositions $(x_i^{int}, y_i^{int})$ are found by solving simultaneously:

  1. Flux continuity: $$N_i^G = N_i^L \quad \text{for each component}$$

  2. Interface equilibrium: $$y_i^{int} = K_i(T^{int}, P) \cdot x_i^{int}$$

  3. Summation constraints: $$\sum_{i=1}^n x_i^{int} = 1, \quad \sum_{i=1}^n y_i^{int} = 1$$

This requires iterative solution (Newton-Raphson method).


3. Interphase Heat Transfer Theory

3.1 Heat Transfer Resistances

Heat flows from bulk gas → interface → bulk liquid (or reverse):

$$q = h_G (T_G^{bulk} - T^{int}) = h_L (T^{int} - T_L^{bulk})$$

Overall heat transfer coefficient:

$$\frac{1}{h_{overall}} = \frac{1}{h_G} + \frac{1}{h_L}$$

3.2 Heat Transfer with Mass Transfer

When mass transfer occurs, the energy balance includes:

  1. Sensible heat transfer (conduction/convection)
  2. Latent heat of phase change
  3. Enthalpy carried by transferred mass

Total interfacial heat flux:

$$Q^{int} = h_{GL}(T_G - T_L) + \sum_{i=1}^n N_i \cdot \Delta H_{vap,i}$$

Where:

3.3 Interface Temperature

The interface temperature $T^{int}$ is found from the energy balance:

$$h_G (T_G^{bulk} - T^{int}) + \sum_{i=1}^n N_i H_i^G = h_L (T^{int} - T_L^{bulk}) + \sum_{i=1}^n N_i H_i^L$$

Rearranging:

$$T^{int} = \frac{h_G T_G^{bulk} + h_L T_L^{bulk} + \sum_i N_i (H_i^G - H_i^L)}{h_G + h_L}$$

3.4 Heat Transfer Coefficients

Dittus-Boelter Correlation (Turbulent):

$$Nu = 0.023 \cdot Re^{0.8} \cdot Pr^n$$

Where:

$$h = \frac{Nu \cdot k_{thermal}}{D_h}$$

Gnielinski Correlation (Transitional, 2300 < Re < 10,000):

$$Nu = \frac{(f/8)(Re - 1000)Pr}{1 + 12.7\sqrt{f/8}(Pr^{2/3} - 1)}$$

Laminar Flow (Re < 2,300):

$$Nu = 3.66 \quad \text{(constant wall temperature)}$$ $$Nu = 4.36 \quad \text{(constant heat flux)}$$

3.5 Chilton-Colburn Analogy

Heat and mass transfer are related through:

$$\frac{h}{c_p G} Pr^{2/3} = \frac{k_m}{u} Sc^{2/3} = \frac{f}{2}$$

This allows estimation of mass transfer coefficients from heat transfer data:

$$k_m = h \cdot \frac{1}{\rho c_p} \left(\frac{Sc}{Pr}\right)^{-2/3}$$

Or equivalently:

$$Sh = Nu \cdot \left(\frac{Sc}{Pr}\right)^{1/3}$$


4. Specific Interfacial Area

The interfacial area per unit volume depends on the flow pattern:

$$a = \frac{\text{Interface Area}}{\text{Pipe Volume}} \quad [m^2/m^3]$$

4.1 Stratified Flow

For stratified flow with liquid height $h_L$:

$$a = \frac{S_i}{A} = \frac{2\sqrt{h_L(D - h_L)}}{\frac{\pi D^2}{4}}$$

Where $S_i$ is the interfacial chord length.

4.2 Annular Flow

For annular flow with liquid film thickness $\delta$:

$$a = \frac{\pi (D - 2\delta)}{\frac{\pi D^2}{4}} = \frac{4(D - 2\delta)}{D^2}$$

For thin films: $a \approx \frac{4}{D}$

4.3 Bubble Flow

For spherical bubbles of diameter $d_b$:

$$a = \frac{6\alpha_G}{d_b}$$

Bubble size can be estimated from the Weber number:

$$d_b = \frac{We_{crit} \cdot \sigma}{\rho_L u_L^2}$$

4.4 Slug Flow

Slug flow has complex geometry. The effective interfacial area includes:

$$a_{slug} = \alpha_{Taylor} \cdot a_{Taylor} + \alpha_{dispersed} \cdot a_{dispersed}$$


5. Coupled Heat and Mass Transfer Solution

5.1 Solution Algorithm

The coupled heat and mass transfer problem requires iterative solution:

1. Initialize: Guess T^int, x_i^int, y_i^int

2. Calculate K-values:
   K_i = K_i(T^int, P)

3. Calculate diffusivities:
   D_ij^G, D_ij^L at current T^int

4. Calculate mass transfer coefficients:
   [k_G], [k_L] from correlations

5. Calculate component fluxes:
   N_i^G = c_G [k_G](y_i^bulk - y_i^int)
   N_i^L = c_L [k_L](x_i^int - x_i^bulk)

6. Check flux balance:
   If |N_i^G - N_i^L| > tolerance, update x_i^int, y_i^int

7. Calculate heat transfer coefficients:
   h_G, h_L from correlations

8. Calculate interface temperature:
   T^int from energy balance

9. Check convergence:
   If T^int, x_i^int, y_i^int converged, exit
   Else goto step 2

5.2 Newton-Raphson Solution

For efficiency, the interface conditions can be solved using Newton-Raphson:

$$\mathbf{F}(\mathbf{X}) = \mathbf{0}$$

Where: $$\mathbf{X} = [T^{int}, x_1^{int}, x_2^{int}, ..., x_{n-1}^{int}]^T$$

$$\mathbf{F} = \begin{bmatrix} Q^G - Q^L \ N_1^G - N_1^L \ N_2^G - N_2^L \ \vdots \ N_{n-1}^G - N_{n-1}^L \end{bmatrix}$$

Update: $\mathbf{X}^{new} = \mathbf{X}^{old} - [\mathbf{J}]^{-1} \mathbf{F}$

Where $[\mathbf{J}]$ is the Jacobian matrix.

5.3 Numerical Stability

Under-relaxation: To improve convergence stability:

$$\mathbf{X}^{new} = \omega \cdot \mathbf{X}^{calc} + (1-\omega) \cdot \mathbf{X}^{old}$$

Typical $\omega = 0.3$ to $0.7$.

Damping: Limit changes per iteration:

$$|\Delta T^{int}| < \Delta T_{max}$$ $$|\Delta x_i^{int}| < \Delta x_{max}$$


6. Condensation and Evaporation

6.1 Total Mass Transfer Rate

The total interphase mass transfer rate:

$$\Gamma = \sum_{i=1}^n M_i \cdot N_i \cdot a \quad [kg/(m^3 \cdot s)]$$

Where:

6.2 Condensation (Vapor → Liquid)

Condensation occurs when:

Heat released: $$Q_{cond} = \sum_i N_i \cdot \Delta H_{vap,i}$$

6.3 Evaporation (Liquid → Vapor)

Evaporation occurs when:

Heat absorbed: $$Q_{evap} = -\sum_i N_i \cdot \Delta H_{vap,i}$$

6.4 Component Selectivity

In multicomponent systems, components transfer at different rates based on:

Light components (high $K$) tend to evaporate preferentially. Heavy components (low $K$) tend to condense preferentially.


7. Physical Properties

7.1 Binary Diffusion Coefficients

Gas Phase (Chapman-Enskog):

$$D_{ij}^G = \frac{0.00266 T^{3/2}}{P M_{ij}^{1/2} \sigma_{ij}^2 \Omega_D}$$

Where:

Liquid Phase (Wilke-Chang):

$$D_{ij}^L = \frac{7.4 \times 10^{-8} (\phi M_j)^{1/2} T}{\mu_L V_i^{0.6}}$$

Where:

7.2 Thermal Conductivity

Gas Phase (Eucken correlation):

$$k_G = \mu_G \left(c_{p,G} + \frac{5R}{4M}\right)$$

Liquid Phase: From NeqSim thermodynamic model or correlations.

7.3 Heat of Vaporization

From equation of state:

$$\Delta H_{vap,i} = H_i^{vapor} - H_i^{liquid}$$

Or from Watson correlation for pure components:

$$\Delta H_{vap} = \Delta H_{vap,0} \left(\frac{1 - T_r}{1 - T_{r,0}}\right)^{0.38}$$


8. Implementation in NeqSim

8.1 Enabling Non-Equilibrium Transfer

TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.builder()
    .withFluid(fluid)
    .withDiameter(0.1, "m")
    .withLength(100, "m")
    .withNodes(50)
    .enableNonEquilibriumMassTransfer()  // Enable mass transfer calculation
    .enableNonEquilibriumHeatTransfer()  // Enable heat transfer calculation
    .build();

8.2 Accessing Mass Transfer Results

// Get mass transfer rates
double[] massTransferRate = pipe.getInterphaseMassTransferRate();

// Get component fluxes at each node
double[][] componentFluxes = pipe.getComponentFluxProfile();

// Get interfacial area profile
double[] interfacialArea = pipe.getInterfacialAreaProfile();

// Get mass transfer coefficients
double[] k_G = pipe.getGasMassTransferCoefficientProfile();
double[] k_L = pipe.getLiquidMassTransferCoefficientProfile();

8.3 Accessing Heat Transfer Results

// Get heat transfer coefficients
double[] h_G = pipe.getGasHeatTransferCoefficientProfile();
double[] h_L = pipe.getLiquidHeatTransferCoefficientProfile();
double[] h_overall = pipe.getOverallInterphaseHeatTransferCoefficientProfile();

// Get interface temperature
double[] T_interface = pipe.getInterfaceTemperatureProfile();

// Get heat flux
double[] q = pipe.getInterphaseHeatFluxProfile();

// Get total heat transferred
double totalHeat = pipe.getTotalInterphaseHeatTransfer();

8.4 Relevant Classes

Class Description
FluidBoundaryInterface Interface between phases, calculates mass/heat transfer
HeatTransferCoefficientCalculator Heat transfer coefficient correlations
InterphaseTwoPhase Interphase calculations for two-phase flow
FluidBoundaryInterfaceHMT Heat and mass transfer at interface

8.5 Complete Example

import neqsim.fluidmechanics.flowsystem.twophaseflowsystem.twophasepipeflowsystem.*;
import neqsim.thermo.system.*;

public class HeatMassTransferExample {
    public static void main(String[] args) {
        // Create multicomponent fluid
        SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
        fluid.addComponent("methane", 0.70, 0);
        fluid.addComponent("ethane", 0.15, 0);
        fluid.addComponent("propane", 0.05, 0);
        fluid.addComponent("water", 0.10, 1);
        fluid.createDatabase(true);
        fluid.setMixingRule(2);

        // Build pipe with heat/mass transfer using builder
        TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.builder()
            .withFluid(fluid)
            .withDiameter(0.1, "m")
            .withLength(500, "m")
            .withNodes(100)
            .withFlowPattern(FlowPattern.ANNULAR)
            .withConvectiveBoundary(278.15, "K", 15.0)  // Cold ambient
            .build();

        // Solve with heat and mass transfer, get structured results
        PipeFlowResult result = pipe.solveWithHeatAndMassTransfer();

        // Access results via PipeFlowResult container
        System.out.println("Temperature change: " + result.getTemperatureChange() + " K");
        System.out.println("Pressure drop: " + result.getTotalPressureDrop() + " bar");
        System.out.println("Total heat loss: " + result.getTotalHeatLoss() + " W");
        System.out.println(result);  // Formatted summary

        // Export profiles for analysis
        Map<String, double[]> profiles = result.toMap();
    }
}

9. Validation and Benchmarks

9.1 Comparison with Olga

The NeqSim two-phase pipe flow model has been validated against OLGA simulations for:

9.2 Literature Validation

Test Case Literature NeqSim Deviation
Dittus-Boelter (turbulent) Experimental +3.2% Within uncertainty
Lockhart-Martinelli Original data +8.5% Acceptable
Stratified flow transition Taitel-Dukler Good agreement -

10. References

  1. Krishna, R. and Standart, G.L. (1976). "A multicomponent film model incorporating a general matrix method of solution to the Maxwell-Stefan equations." AIChE Journal, 22(2), 383-389.

  2. Taylor, R. and Krishna, R. (1993). Multicomponent Mass Transfer. Wiley Series in Chemical Engineering.

  3. Bird, R.B., Stewart, W.E., and Lightfoot, E.N. (2002). Transport Phenomena, 2nd Edition. John Wiley & Sons.

  4. Chilton, T.H. and Colburn, A.P. (1934). "Mass Transfer (Absorption) Coefficients Prediction from Data on Heat Transfer and Fluid Friction." Industrial & Engineering Chemistry, 26(11), 1183-1187.

  5. Incropera, F.P. and DeWitt, D.P. (2002). Fundamentals of Heat and Mass Transfer, 5th Edition. John Wiley & Sons.

  6. Solbraa, E. (2002). "Measurement and Calculation of Two-Phase Flow in Pipes." PhD Thesis, Norwegian University of Science and Technology.

  7. Dittus, F.W. and Boelter, L.M.K. (1930). "Heat transfer in automobile radiators of the tubular type." University of California Publications in Engineering, 2, 443-461.

  8. Gnielinski, V. (1976). "New equations for heat and mass transfer in turbulent pipe and channel flow." International Chemical Engineering, 16(2), 359-368.


Document generated for NeqSim Interphase Heat and Mass Transfer Module

Mass Transfer

Mass Transfer Modeling in NeqSim

This document provides detailed documentation of the mass transfer models implemented in the NeqSim fluid mechanics package.

Table of Contents


Overview

NeqSim implements rigorous multicomponent mass transfer models based on non-equilibrium thermodynamics. The models are suitable for:

The theoretical foundation is described in:

Solbraa, E. (2002). Equilibrium and Non-Equilibrium Thermodynamics of Natural Gas Processing. Dr.ing. thesis, NTNU. Available at NVA


Theoretical Background

Fick's Law vs Maxwell-Stefan

For binary systems, Fick's law is adequate:

$$J_A = -D_{AB} \cdot c_t \cdot \nabla x_A$$

For multicomponent systems, NeqSim uses the Maxwell-Stefan equations:

$$-\frac{c_t}{RT} \nabla \mu_i = \sum_{j=1, j \neq i}^{n} \frac{x_i N_j - x_j N_i}{c_t D_{ij}}$$

Where:

Film Theory

The film theory assumes mass transfer occurs across a stagnant film of thickness $\delta$:

$$N_i = k_i \cdot c_t \cdot (x_{i,bulk} - x_{i,interface})$$

Where $k_i = D_i / \delta$ is the mass transfer coefficient.

Penetration Theory

For unsteady-state mass transfer (short contact times):

$$k_L = 2 \sqrt{\frac{D}{\pi t_c}}$$

Where $t_c$ is the contact time.


Single-Phase Mass Transfer

Wall Mass Transfer

Mass transfer from a flowing fluid to a solid wall (pipe wall, packing surface):

Dimensionless Numbers

Number Definition Physical Meaning
Sherwood (Sh) $k_c \cdot d / D$ Ratio of convective to diffusive mass transfer
Schmidt (Sc) $\nu / D$ Ratio of momentum to mass diffusivity
Reynolds (Re) $\rho v d / \mu$ Ratio of inertial to viscous forces

Correlations

Laminar Flow (Re < 2300): $$Sh = 3.66$$

Turbulent Flow (Re > 10000): $$Sh = 0.023 \cdot Re^{0.83} \cdot Sc^{0.33}$$

Transition Region (2300 < Re < 4000): Linear interpolation between laminar and turbulent values.

Diffusion Coefficients

Gas Phase

Chapman-Enskog theory for binary diffusion:

$$D_{AB} = \frac{0.00266 \cdot T^{3/2}}{P \cdot M_{AB}^{1/2} \cdot \sigma_{AB}^2 \cdot \Omega_D}$$

Where:

Liquid Phase

NeqSim provides multiple liquid-phase diffusivity models, each optimized for different applications:

1. Wilke-Chang Correlation (Default)

$$D_{AB} = 7.4 \times 10^{-8} \cdot \frac{(\phi M_B)^{0.5} \cdot T}{\mu_B \cdot V_A^{0.6}}$$

Where:

2. Siddiqi-Lucas Method

Uses group contribution based on molecular weight and solvent viscosity:

Best for: General aqueous and organic liquid systems at low to moderate pressures.

3. Hayduk-Minhas Method

Optimized for hydrocarbon systems (Hayduk & Minhas, 1982):

Best for: Hydrocarbon-hydrocarbon diffusion in oil/gas applications.

// Example: Using Hayduk-Minhas for oil system
PhysicalProperties physProps = system.getPhase(1).getPhysicalProperties();
Diffusivity diffModel = new HaydukMinhasDiffusivity(physProps);
diffModel.calcDiffusionCoefficients(0, 0);  // binaryMethod, multicomponentMethod
double Dij = diffModel.getMaxwellStefanBinaryDiffusionCoefficient(0, 1);
4. CO2-Water (Tamimi Correlation)

Specialized for CO2 diffusion in water, validated against experimental data:

$$D_{CO_2} = 2.35 \times 10^{-6} \cdot \exp\left(\frac{-2119}{T}\right)$$

Best for: Carbon capture applications, CO2 absorption/desorption studies.

5. High-Pressure Correction

For reservoir and deep-water conditions (>100 bar), apply Mathur-Thodos correction:

$$D_P = D_0 \cdot f(\rho_r)$$

The correction factor accounts for increased molecular crowding at high pressures and can reduce diffusivity by 10× at 400 bar.

// Example: High-pressure diffusivity
PhysicalProperties physProps = system.getPhase(1).getPhysicalProperties();
HighPressureDiffusivity hpModel = new HighPressureDiffusivity(physProps);
hpModel.calcDiffusionCoefficients(0, 0);  // applies HP correction automatically
double correctionFactor = hpModel.getPressureCorrectionFactor();

Model Selection Guide

Application Recommended Model Notes
General aqueous Siddiqi-Lucas (aqueous) Well-validated for dilute solutions
General organic Siddiqi-Lucas (non-aqueous) Good for organic solvents
Oil/gas hydrocarbons Hayduk-Minhas (paraffin) 2-3× higher than Siddiqi-Lucas, physically appropriate for oils
CO2 in water CO2-water (Tamimi) Best accuracy (±11% of literature)
Reservoir conditions High-pressure + Hayduk-Minhas Critical for P > 100 bar

Model Comparison Results

Based on validation testing at 300 K, 1 atm for CO2 in water (literature: 1.9×10⁻⁹ m²/s):

Model Predicted (m²/s) Error
Hayduk-Minhas (aqueous) 1.71×10⁻⁹ -10%
CO2-water (Tamimi) 2.12×10⁻⁹ +11%
Siddiqi-Lucas 1.39×10⁻⁹ -27%

For hydrocarbon systems, Hayduk-Minhas produces values 2-3.5× higher than Siddiqi-Lucas, which is consistent with the different physical basis of the correlations.

Automatic Model Selection

The DiffusivityModelSelector class can automatically choose the optimal model:

// Automatic model selection based on composition and conditions
PhaseInterface phase = system.getPhase(1);
PhysicalProperties physProps = phase.getPhysicalProperties();
DiffusivityModelSelector.DiffusivityModelType modelType = 
    DiffusivityModelSelector.selectOptimalModel(phase);
Diffusivity model = DiffusivityModelSelector.createModel(physProps, modelType);

// Or use auto-selection directly:
Diffusivity autoModel = DiffusivityModelSelector.createAutoSelectedModel(physProps);

Selection criteria:

Future Development Possibilities

  1. Concentration-dependent diffusivity (Vignes mixing rule): $$D_{AB,mix} = D_{AB}^{x_B} \cdot D_{BA}^{x_A}$$ Currently implemented but may cause numerical issues with very different diffusivities.

  2. Binary interaction parameters: Allow user-tuning for specific component pairs.

  3. Additional correlations:

    • Tyn-Calus for associated liquids
    • Scheibel for high-viscosity systems
    • He-Yu for supercritical fluids
  4. Temperature extrapolation warnings: Alert users when operating outside correlation validity ranges (typically 273-400 K).


Two-Phase Mass Transfer

Interphase Mass Transfer

At the gas-liquid interface, mass transfer occurs from both sides:

    Gas Bulk     |  Interface  |    Liquid Bulk
                 |             |
    x_i,G,bulk --|-- x_i,I ----|-- x_i,L,bulk
                 |             |
      k_G        |  Equilibrium|      k_L
                 |   K_i       |

Two-Resistance Model

The overall mass transfer coefficient combines gas and liquid resistances:

$$\frac{1}{K_{OG}} = \frac{1}{k_G} + \frac{m}{k_L}$$

$$\frac{1}{K_{OL}} = \frac{1}{k_L} + \frac{1}{m \cdot k_G}$$

Where $m = dy/dx$ is the slope of the equilibrium line.

Flow Regime Dependence

The mass transfer coefficients depend strongly on the flow regime:

Flow Regime Interfacial Area Gas-side $k_G$ Liquid-side $k_L$
Stratified $A_i = W \cdot L$ (flat interface) Smooth surface correlation Penetration theory
Annular $A_i = \pi d L$ (film on wall) Core flow correlation Film flow correlation
Droplet/Mist $A_i = 6\epsilon/d_p$ (droplet surface) External mass transfer Internal circulation
Bubble $A_i = 6\epsilon/d_b$ (bubble surface) External mass transfer Higbie penetration
Slug Combined film + slug Varies with position Varies with position

Stratified Flow

For stratified gas-liquid flow in pipes:

Gas-side (smooth interface): $$Sh_G = 0.023 \cdot Re_G^{0.83} \cdot Sc_G^{0.33}$$

Liquid-side (penetration theory): $$k_L = 2 \sqrt{\frac{D_L \cdot v_L}{\pi \cdot L}}$$

Annular Flow

For annular flow with liquid film:

Gas core: $$Sh_G = 0.023 \cdot Re_G^{0.8} \cdot Sc_G^{0.33} \cdot \left(1 + 0.1 \cdot (d/\delta)^{0.5}\right)$$

Liquid film: $$k_L = \frac{D_L}{\delta} \cdot f(Re_{film})$$


Multicomponent Mass Transfer

Krishna-Standart Model

NeqSim implements the Krishna-Standart multicomponent mass transfer model. For a system with $n$ components:

Binary Mass Transfer Coefficients

First, calculate binary coefficients from correlations:

for (int i = 0; i < nComponents; i++) {
    for (int j = 0; j < nComponents; j++) {
        // Schmidt number
        Sc[i][j] = kinematicViscosity / D[i][j];

        // Binary mass transfer coefficient
        k_binary[i][j] = calcInterphaseMassTransferCoefficient(phase, Sc[i][j], node);
    }
}

Mass Transfer Coefficient Matrix

Build the $(n-1) \times (n-1)$ matrix $[\mathbf{k}]$:

$$k_{ii} = \sum_{j \neq i} \frac{x_j}{k_{ij}} + \frac{x_i}{k_{in}}$$

$$k_{ij} = -x_i \left(\frac{1}{k_{ij}} - \frac{1}{k_{in}}\right) \quad (i \neq j)$$

Where component $n$ is the reference (typically the most abundant).

Flux Calculation

The molar flux vector:

$$\mathbf{N} = c_t [\mathbf{k}]^{-1} (\mathbf{x}_{bulk} - \mathbf{x}_{interface})$$

Corrections

Thermodynamic Correction

For non-ideal solutions, the driving force includes activity coefficient gradients:

$$[\Gamma] = [\delta_{ij} + x_i \frac{\partial \ln \gamma_i}{\partial x_j}]$$

The corrected flux: $$\mathbf{N} = c_t [\mathbf{k}][\Gamma]^{-1} (\mathbf{x}_{bulk} - \mathbf{x}_{interface})$$

Finite Flux Correction (Stefan Flow)

For high mass transfer rates, the film theory correction:

$$[\Xi] = \Phi^{-1}$$

Where $[\Phi]$ is the rate factor matrix.


Reactive Mass Transfer

Enhancement Factor

Chemical reactions in the liquid phase enhance mass transfer:

$$N_A = E \cdot k_L \cdot (C_{A,i} - C_{A,bulk})$$

Where $E \geq 1$ is the enhancement factor.

Hatta Number

The Hatta number characterizes the reaction regime:

$$Ha = \frac{\sqrt{k_{rxn} \cdot D_A}}{k_L}$$

Ha Range Regime Location of Reaction
Ha < 0.3 Slow Bulk liquid
0.3 < Ha < 3 Intermediate Film and bulk
Ha > 3 Fast Within film
Ha >> 3 Instantaneous At interface

Enhancement Factor Models

Pseudo-First Order (Fast Reaction)

$$E = \frac{Ha}{\tanh(Ha)}$$

Instantaneous Reaction

$$E_{\infty} = 1 + \frac{D_B \cdot C_{B,bulk}}{\nu_B \cdot D_A \cdot C_{A,i}}$$

Where $\nu_B$ is the stoichiometric coefficient.

General Case (Danckwerts)

$$E = \frac{\sqrt{1 + Ha^2 \cdot (E_{\infty} - 1)/E_{\infty}}}{1 + (E_{\infty} - 1)^{-1}}$$

CO₂-Amine Systems

NeqSim includes specific models for CO₂ absorption:

Reaction Mechanism (MDEA)

CO₂ + MDEA + H₂O ⇌ MDEAH⁺ + HCO₃⁻  (slow, base-catalyzed)
CO₂ + OH⁻ ⇌ HCO₃⁻                   (parallel)

Reaction Kinetics

$$r_{CO2} = k_2 \cdot [CO_2] \cdot [MDEA]$$

With Arrhenius temperature dependence:

$$k_2 = A \cdot \exp\left(-\frac{E_a}{RT}\right)$$

Amine A (m³/mol·s) Eₐ (kJ/mol) Source
MDEA 4.01×10⁸ 42.0 Rinker et al. (1995)
MEA 4.4×10¹¹ 50.5 Hikita et al. (1977)
DEA 1.3×10¹⁰ 47.5 Blauwhoff et al. (1984)

High-Pressure Effects

From the thesis work, high-pressure effects on CO₂ absorption include:

  1. Reduced CO₂ capacity at high total pressure (up to 40% reduction at 200 bar)
  2. Thermodynamic non-ideality must be modeled consistently
  3. Reaction kinetics relatively unaffected by pressure (with N₂ as inert)

Implementation Classes

Class Hierarchy

FluidBoundary (abstract)
├── EquilibriumFluidBoundary
└── NonEquilibriumFluidBoundary (abstract)
    └── KrishnaStandartFilmModel
        └── ReactiveKrishnaStandartFilmModel
            └── ReactiveFluidBoundary

Key Classes

Class Location Purpose
FluidBoundary flownode.fluidboundary.heatmasstransfercalc Base class for interphase calculations
NonEquilibriumFluidBoundary ...nonequilibriumfluidboundary Non-equilibrium base
KrishnaStandartFilmModel ...filmmodelboundary Multicomponent film model
ReactiveKrishnaStandartFilmModel ...reactivefilmmodel With chemical reactions
EnhancementFactor ...enhancementfactor Enhancement factor calculations

Key Methods

// FluidBoundary
public abstract void solve();
public double[] getMolarFlux();
public double[] getHeatFlux();

// KrishnaStandartFilmModel
public double calcBinarySchmidtNumbers(int phase);
public double calcBinaryMassTransferCoefficients(int phase);
public double calcMassTransferCoefficients(int phase);
public void calcPhiMatrix(int phase);  // Finite flux correction

Usage Examples

Basic Two-Phase Mass Transfer

import neqsim.fluidmechanics.flownode.twophasenode.twophasepipeflownode.AnnularFlow;
import neqsim.fluidmechanics.geometrydefinitions.pipe.PipeData;
import neqsim.thermo.system.SystemSrkEos;

// Create two-phase system
SystemSrkEos fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.90);
fluid.addComponent("CO2", 0.05);
fluid.addComponent("water", 0.05);
fluid.setMixingRule("classic");

// Create pipe geometry
PipeData pipe = new PipeData(0.1);  // 0.1 m diameter

// Create flow node
AnnularFlow node = new AnnularFlow(fluid, pipe);
node.init();
node.initFlowCalc();

// Enable mass transfer
node.getFluidBoundary().setMassTransferCalc(true);
node.getFluidBoundary().solve();

// Get results
double[] molarFlux = node.getFluidBoundary().getMolarFlux();
System.out.println("CO2 flux: " + molarFlux[1] + " mol/m²·s");

With Thermodynamic Corrections

// Enable activity coefficient corrections
node.getFluidBoundary().setThermodynamicCorrections(0, true);  // Gas
node.getFluidBoundary().setThermodynamicCorrections(1, true);  // Liquid

// Enable Stefan flow correction
node.getFluidBoundary().setFiniteFluxCorrection(0, true);
node.getFluidBoundary().setFiniteFluxCorrection(1, true);

node.getFluidBoundary().solve();

CO₂ Absorption with Reaction

import neqsim.thermo.system.SystemSrkCPAstatoil;

// Create system with amine
SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(313.15, 30.0);
fluid.addComponent("nitrogen", 0.85);
fluid.addComponent("CO2", 0.10);
fluid.addComponent("water", 0.04);
fluid.addComponent("MDEA", 0.01);
fluid.setMixingRule(10);  // CPA mixing rule

// Use reactive film model
// The enhancement factor is calculated automatically
// based on reaction kinetics and Hatta number

References

  1. Solbraa, E. (2002). Equilibrium and Non-Equilibrium Thermodynamics of Natural Gas Processing. Dr.ing. thesis, NTNU. NVA

  2. Krishna, R., Standart, G.L. (1976). Mass and energy transfer in multicomponent systems. Chem. Eng. Commun., 3(4-5), 201-275.

  3. Taylor, R., Krishna, R. (1993). Multicomponent Mass Transfer. Wiley.

  4. Danckwerts, P.V. (1970). Gas-Liquid Reactions. McGraw-Hill.

  5. Poling, B.E., Prausnitz, J.M., O'Connell, J.P. (2001). The Properties of Gases and Liquids. 5th ed. McGraw-Hill.

  6. Rinker, E.B., Ashour, S.S., Sandall, O.C. (1995). Kinetics and modelling of carbon dioxide absorption into aqueous solutions of N-methyldiethanolamine. Chem. Eng. Sci., 50(5), 755-768.


Chapter 27: Two-Phase & Multiphase Flow

Two-Phase Model

Two-Phase Pipe Flow Model with Heat and Mass Transfer

Overview

The NeqSim two-phase pipe flow model implements a non-equilibrium thermodynamic approach for simulating gas-liquid flow in pipes. This document describes the theoretical foundation, numerical methods, and practical usage in NeqSim.

Key Features:

Compatibility: Java 8 and above (no Java 9+ features used)


1. Governing Equations

1.1 Mass Conservation

For each phase $k$ (gas $G$ or liquid $L$):

$$\frac{\partial}{\partial t}(\alpha_k \rho_k) + \frac{\partial}{\partial z}(\alpha_k \rho_k u_k) = \Gamma_k$$

Symbol Description Units
$\alpha_k$ Volume fraction of phase $k$ [-]
$\rho_k$ Density of phase $k$ [kg/m³]
$u_k$ Velocity of phase $k$ [m/s]
$\Gamma_k$ Mass transfer rate to phase $k$ [kg/(m³·s)]
$z$ Axial position [m]

Constraint: $\alpha_G + \alpha_L = 1$

1.2 Momentum Conservation

$$\frac{\partial}{\partial t}(\alpha_k \rho_k u_k) + \frac{\partial}{\partial z}(\alpha_k \rho_k u_k^2) = -\alpha_k \frac{\partial P}{\partial z} - \tau_{wk}\frac{S_k}{A} \mp \tau_i\frac{S_i}{A} - \alpha_k \rho_k g \sin\theta$$

Symbol Description Units
$P$ Pressure [Pa]
$\tau_{wk}$ Wall shear stress for phase $k$ [Pa]
$\tau_i$ Interfacial shear stress [Pa]
$S_k$ Wetted perimeter of phase $k$ [m]
$S_i$ Interfacial perimeter [m]
$A$ Pipe cross-sectional area [m²]
$g$ Gravitational acceleration [m/s²]
$\theta$ Pipe inclination angle [rad]

1.3 Energy Conservation

$$\frac{\partial}{\partial t}(\alpha_k \rho_k H_k) + \frac{\partial}{\partial z}(\alpha_k \rho_k u_k H_k) = q_{ik} + q_{wk} + \Gamma_k H_k^{int}$$

Symbol Description Units
$H_k$ Specific enthalpy of phase $k$ [J/kg]
$q_{ik}$ Interphase heat flux to phase $k$ [W/m³]
$q_{wk}$ Wall heat flux to phase $k$ [W/m³]
$H_k^{int}$ Interface enthalpy [J/kg]

2. Heat Transfer Model

2.1 Interphase Heat Transfer

Heat transfer between gas and liquid phases at the interface:

$$q_{GL} = h_{GL} \cdot a \cdot (T_G - T_L)$$

Where:

2.2 Heat Transfer Coefficient Correlations

Dittus-Boelter (Turbulent Flow, Re > 10,000)

$$Nu = 0.023 \cdot Re^{0.8} \cdot Pr^n$$

Where $n = 0.4$ for heating, $n = 0.3$ for cooling.

$$h = \frac{Nu \cdot k}{D_h}$$

Gnielinski (Transitional Flow, 2300 < Re < 10,000)

$$Nu = \frac{(f/8)(Re - 1000)Pr}{1 + 12.7\sqrt{f/8}(Pr^{2/3} - 1)}$$

Laminar Flow (Re < 2300)

Boundary Condition Nusselt Number
Constant wall temperature $Nu = 3.66$
Constant heat flux $Nu = 4.36$

2.3 Wall Heat Transfer Models

Model Equation NeqSim Setting
Adiabatic $q_w = 0$ WallHeatTransferModel.ADIABATIC
Constant Wall Temp $q_w = h_w(T_w - T_{fluid})$ WallHeatTransferModel.CONSTANT_WALL_TEMPERATURE
Constant Heat Flux $q_w = q_0$ WallHeatTransferModel.CONSTANT_HEAT_FLUX
Convective Boundary $q_w = U(T_{ambient} - T_{fluid})$ WallHeatTransferModel.CONVECTIVE_BOUNDARY

2.4 Overall Heat Transfer Coefficient

For a pipe with wall and insulation:

$$\frac{1}{U} = \frac{1}{h_{inner}} + \frac{r_i \ln(r_o/r_i)}{k_{wall}} + \frac{r_i \ln(r_{ins}/r_o)}{k_{ins}} + \frac{r_i}{r_{ins} \cdot h_{outer}}$$


3. Mass Transfer Model

3.1 Krishna-Standart Film Theory

The model uses the Krishna-Standart film theory for multicomponent mass transfer:

$$N_i = k_{i,L} \cdot a \cdot (x_i^{int} - x_i^{bulk}) = k_{i,G} \cdot a \cdot (y_i^{bulk} - y_i^{int})$$

Symbol Description Units
$N_i$ Molar flux of component $i$ [mol/(m²·s)]
$k_{i,L}, k_{i,G}$ Mass transfer coefficients [m/s]
$a$ Specific interfacial area [m²/m³]
$x_i, y_i$ Mole fractions in liquid/gas [-]

3.2 Chilton-Colburn Analogy

Mass transfer coefficients are related to heat transfer via:

$$Sh = Nu \cdot \left(\frac{Sc}{Pr}\right)^{1/3}$$

Where:

3.3 Condensation/Evaporation Rate

$$\Gamma = \sum_{i=1}^{n} M_i \cdot N_i \cdot a$$

Interface equilibrium: $y_i^{int} = K_i(T^{int}, P) \cdot x_i^{int}$


4. Pressure Drop Calculation

4.1 Total Pressure Gradient

$$-\frac{dP}{dz} = \left(-\frac{dP}{dz}\right)_{friction} + \left(-\frac{dP}{dz}\right)_{gravity} + \left(-\frac{dP}{dz}\right)_{acceleration}$$

4.2 Lockhart-Martinelli Correlation

$$\left(-\frac{dP}{dz}\right)_{friction} = \phi_L^2 \cdot \left(-\frac{dP}{dz}\right)_{L,alone}$$

Two-phase multiplier: $$\phi_L^2 = 1 + \frac{C}{X} + \frac{1}{X^2}$$

Lockhart-Martinelli parameter: $$X = \sqrt{\frac{(dP/dz)_L}{(dP/dz)_G}}$$

Gas Flow Liquid Flow C Value
Turbulent Turbulent 20
Laminar Turbulent 12
Turbulent Laminar 10
Laminar Laminar 5

4.3 Gravitational Pressure Drop

$$\left(-\frac{dP}{dz}\right)_{gravity} = \rho_m \cdot g \cdot \sin\theta$$

Mixture density: $\rho_m = \alpha_G \rho_G + (1-\alpha_G) \rho_L$

4.4 Acceleration Pressure Drop

$$\left(-\frac{dP}{dz}\right)_{acc} = G^2 \frac{d}{dz}\left[\frac{x^2}{\rho_G \alpha_G} + \frac{(1-x)^2}{\rho_L \alpha_L}\right]$$


5. Numerical Discretization

5.1 Staggered Grid

The solver uses a staggered grid where:

    |----[i-1]----|-----[i]-----|----[i+1]----|
    |      ●      |      ●      |      ●      |   ← Scalars (P, T, ρ, α)
    |             ↑             ↑             |   ← Velocities (u_G, u_L)
         face i-½      face i+½

5.2 Finite Volume Method

General conservation equation: $$\frac{\partial \phi}{\partial t} + \frac{\partial (u\phi)}{\partial z} = S_\phi$$

Discretized form: $$\frac{\phi_i^{n+1} - \phi_i^n}{\Delta t} + \frac{(u\phi)_{i+½} - (u\phi)_{i-½}}{\Delta z} = S_{\phi,i}$$

5.3 TDMA Solver

The tri-diagonal system: $$a_i \phi_{i-1} + b_i \phi_i + c_i \phi_{i+1} = d_i$$

Forward Sweep: $$c'_1 = \frac{c_1}{b_1}, \quad d'_1 = \frac{d_1}{b_1}$$ $$c'_i = \frac{c_i}{b_i - a_i c'_{i-1}}, \quad d'_i = \frac{d_i - a_i d'_{i-1}}{b_i - a_i c'_{i-1}}$$

Back Substitution: $$\phi_n = d'_n, \quad \phi_i = d'_i - c'_i \phi_{i+1}$$

5.4 Iterative Solution Procedure

1. Initialize: Set initial P, T, u_G, u_L, α profiles
2. Solve Momentum: Update velocities from pressure gradient
3. Solve Pressure: Apply pressure correction
4. Solve Mass: Update phase fractions from continuity
5. Solve Energy: Update temperatures from energy equation
6. Update Properties: Recalculate ρ, μ, k, h from EOS
7. Check Convergence: If ||residual|| < tolerance, stop; else goto 2

5.5 Boundary Conditions

Location Variable Condition
Inlet (z=0) P, T, u, α Dirichlet (specified)
Outlet (z=L) P Dirichlet or extrapolated
Outlet (z=L) T, u, α Zero-gradient (Neumann)

6. Flow Pattern Models

6.1 Supported Flow Patterns

Pattern Description NeqSim Enum
Stratified Liquid bottom, gas top FlowPattern.STRATIFIED
Stratified-Wavy With interfacial waves FlowPattern.STRATIFIED_WAVY
Annular Liquid film, gas core FlowPattern.ANNULAR
Slug Alternating slugs/bubbles FlowPattern.SLUG
Bubble Gas bubbles in liquid FlowPattern.BUBBLE
Droplet/Mist Liquid drops in gas FlowPattern.DROPLET
Churn Chaotic oscillating FlowPattern.CHURN

6.2 Flow Pattern Detection Models

Model Description NeqSim Enum
Manual User-specified FlowPatternModel.MANUAL
Taitel-Dukler Horizontal/near-horizontal FlowPatternModel.TAITEL_DUKLER
Baker Chart General correlation FlowPatternModel.BAKER_CHART
Barnea Unified model FlowPatternModel.BARNEA

7. NeqSim Implementation

The simplified API provides static factory methods and a structured result container for the most common use cases:

// Create fluid system
SystemInterface fluid = new SystemSrkEos(293.15, 50.0);
fluid.addComponent("methane", 0.85, 0);
fluid.addComponent("water", 0.15, 1);
fluid.createDatabase(true);
fluid.setMixingRule(2);

// Create horizontal pipe using factory method
TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.horizontalPipe(fluid, 0.15, 500, 50);

// Solve and get structured results
PipeFlowResult result = pipe.solve();

// Access results via the container
System.out.println("Pressure drop: " + result.getTotalPressureDrop() + " bar");
System.out.println("Outlet temperature: " + result.getOutletTemperature() + " K");
System.out.println(result);  // Prints formatted summary

// Export profiles for plotting
Map<String, double[]> data = result.toMap();

Static Factory Methods

Method Description
horizontalPipe(fluid, diameter, length, nodes) Horizontal pipe with stratified flow
verticalPipe(fluid, diameter, length, nodes, upward) Vertical pipe (upward or downward flow)
inclinedPipe(fluid, diameter, length, nodes, angleDeg) Inclined pipe at specified angle
subseaPipe(fluid, diameter, length, nodes, seawaterTempC) Subsea pipeline with seawater cooling
buriedPipe(fluid, diameter, length, nodes, groundTempC) Buried onshore pipeline

Solve Methods

Method Description
solve() Solve and return PipeFlowResult
solveWithMassTransfer() Enable mass transfer, solve, return result
solveWithHeatAndMassTransfer() Enable heat & mass transfer, solve, return result

7.2 Builder Pattern (Full Control)

For advanced configurations, use the fluent builder pattern:

// Build pipe system with full control
TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.builder()
    .withFluid(fluid)
    .withDiameter(0.15, "m")
    .withLength(500, "m")
    .withNodes(50)
    .withFlowPattern(FlowPattern.STRATIFIED)
    .withConvectiveBoundary(278.15, "K", 10.0)  // Ambient temp, U-value
    .build();

// Solve using simplified API
PipeFlowResult result = pipe.solve();

7.3 Inclined Pipe Configuration

TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.inclinedPipe(fluid, 0.1, 1000, 100, 15.0);

// Or using builder for more control
TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.builder()
    .withFluid(fluid)
    .withDiameter(0.1, "m")
    .withLength(1000, "m")
    .withNodes(100)
    .withInclination(15, "degrees")  // 15° upward
    .withFlowPattern(FlowPattern.SLUG)
    .build();

// Check inclination
System.out.println("Inclination: " + pipe.getInclinationDegrees() + " degrees");
System.out.println("Is upward flow: " + pipe.isUpwardFlow());

7.4 Extracting Results

PipeFlowResult result = pipe.solve();

// Get summary values
double pressureDrop = result.getTotalPressureDrop();
double outletTemp = result.getOutletTemperature();
double tempChange = result.getTemperatureChange();

// Get profiles
double[] pressure = result.getPressureProfile();
double[] temperature = result.getTemperatureProfile();
double[] holdup = result.getLiquidHoldupProfile();
double[] gasVelocity = result.getGasVelocityProfile();
double[] liquidVelocity = result.getLiquidVelocityProfile();

// Export to map (for Jupyter/Python integration)
Map<String, double[]> data = result.toMap();
Map<String, Object> summary = result.getSummary();

// Print formatted summary
System.out.println(result);

Direct Access (Legacy)

// Get profiles
double[] temperature = pipe.getTemperatureProfile();
double[] pressure = pipe.getPressureProfile();
double[] gasVelocity = pipe.getVelocityProfile(0);
double[] liquidVelocity = pipe.getVelocityProfile(1);
double[] liquidHoldup = pipe.getLiquidHoldupProfile();

// Get pressure drop breakdown
double totalDP = pipe.getTotalPressureDrop();
double frictionalDP = pipe.getFrictionalPressureDrop();
double gravitationalDP = pipe.getGravitationalPressureDrop();

// Get heat transfer info
double[] htc = pipe.getOverallInterphaseHeatTransferCoefficientProfile();
double totalHeatLoss = pipe.getTotalHeatLoss();

// Export to CSV
pipe.exportToCSV("results.csv");

// Get summary report
System.out.println(pipe.getSummaryReport());

Pressure drop (steady-state): The steady-state solver updates the pressure profile from an integrated momentum balance (friction + gravity) after solving the phase momentum equations. This ensures a physically consistent pressure drop along the pipe instead of keeping pressure nearly constant due to a density-only correction.

7.5 Flow Pattern Detection

// Enable automatic flow pattern detection
pipe.setFlowPatternModel(FlowPatternModel.TAITEL_DUKLER);
pipe.detectFlowPatterns();

// Get flow pattern at each node
FlowPattern[] patterns = pipe.getFlowPatternProfile();
int transitions = pipe.getFlowPatternTransitionCount();

7.6 Non-Equilibrium Mode

// Using simplified API
TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.horizontalPipe(fluid, 0.1, 100, 50);
PipeFlowResult result = pipe.solveWithHeatAndMassTransfer();

// Or using builder
TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.builder()
    .withFluid(fluid)
    .withDiameter(0.1, "m")
    .withLength(100, "m")
    .withNodes(50)
    .enableNonEquilibriumMassTransfer()  // Enable mass transfer
    .enableNonEquilibriumHeatTransfer()  // Enable heat transfer
    .build();

PipeFlowResult result = pipe.solve();

8. Key Classes and Methods

TwoPhasePipeFlowSystem

Method Description
horizontalPipe(fluid, diam, len, nodes) Create horizontal pipe
verticalPipe(fluid, diam, len, nodes, up) Create vertical pipe
inclinedPipe(fluid, diam, len, nodes, angle) Create inclined pipe
subseaPipe(fluid, diam, len, nodes, seaTemp) Create subsea pipeline
buriedPipe(fluid, diam, len, nodes, groundTemp) Create buried pipeline

Solve Methods

Method Description
solve() Solve and return PipeFlowResult
solveWithMassTransfer() Enable mass transfer, solve
solveWithHeatAndMassTransfer() Enable heat & mass transfer, solve
solveSteadyState(UUID) Solve steady-state equations
solveTransient(int, UUID) Solve transient equations

Result Methods

Method Description
getTemperatureProfile() Temperature along pipe [K]
getPressureProfile() Pressure along pipe [bar]
getVelocityProfile(int phase) Phase velocity [m/s]
getLiquidHoldupProfile() Liquid holdup [-]
getTotalPressureDrop() Total ΔP [bar]
exportToCSV(String path) Export results to CSV
getSummaryReport() Formatted text summary

PipeFlowResult

Method Description
getTotalPressureDrop() Total pressure drop [bar]
getInletPressure() / getOutletPressure() Inlet/outlet pressure [bar]
getInletTemperature() / getOutletTemperature() Inlet/outlet temperature [K]
getTemperatureChange() Temperature change [K]
getPressureGradient() Pressure gradient [bar/m]
getPressureProfile() Pressure array [bar]
getTemperatureProfile() Temperature array [K]
getLiquidHoldupProfile() Liquid holdup array [-]
getGasVelocityProfile() / getLiquidVelocityProfile() Velocity arrays [m/s]
getFlowPatternProfile() Flow pattern array
toMap() Export profiles to Map (Jupyter-friendly)
getSummary() Export summary to Map

TwoPhasePipeFlowSystemBuilder

Method Description
withFluid(SystemInterface) Set thermodynamic system
withDiameter(double, String) Set pipe diameter
withLength(double, String) Set pipe length
withNodes(int) Set number of nodes
withInclination(double, String) Set pipe inclination
withFlowPattern(FlowPattern) Set initial flow pattern
withWallTemperature(double, String) Constant wall temp BC
withConvectiveBoundary(double, String, double) Convective BC
withAdiabaticWall() Adiabatic BC
build() Create the pipe system

9. References

  1. Solbraa, E. (2002). "Measurement and Calculation of Two-Phase Flow in Pipes." PhD Thesis, Norwegian University of Science and Technology.

  2. Taitel, Y., & Dukler, A.E. (1976). "A model for predicting flow regime transitions in horizontal and near horizontal gas-liquid flow." AIChE Journal, 22(1), 47-55.

  3. Lockhart, R.W. and Martinelli, R.C. (1949). "Proposed Correlation of Data for Isothermal Two-Phase, Two-Component Flow in Pipes." Chemical Engineering Progress, 45(1), 39-48.

  4. Krishna, R. and Standart, G.L. (1976). "A multicomponent film model incorporating a general matrix method of solution to the Maxwell-Stefan equations." AIChE Journal, 22(2), 383-389.

  5. Gnielinski, V. (1976). "New equations for heat and mass transfer in turbulent pipe and channel flow." International Chemical Engineering, 16(2), 359-368.


Document generated for NeqSim Two-Phase Pipe Flow Module

Two-Fluid Model

Two-Fluid Transient Multiphase Flow Model

This document describes the two-fluid model implementation in NeqSim for transient multiphase pipeline simulation.

Overview

The two-fluid model solves separate conservation equations for each phase (gas and liquid), providing more accurate predictions than drift-flux models for:

Package Structure

neqsim.process.equipment.pipeline.twophasepipe/
├── PipeSection.java              # Base section state container
├── TwoFluidSection.java          # Two-fluid section with conservative variables
├── TwoFluidConservationEquations.java  # PDE RHS calculation
├── ThreeFluidSection.java        # Extension for gas-oil-water systems
├── ThreeFluidConservationEquations.java  # Three-phase equations
├── ThermodynamicCoupling.java    # Flash calculation interface
├── FlashTable.java               # Pre-computed property interpolation
├── EntrainmentDeposition.java    # Droplet exchange model
├── FlowRegimeDetector.java       # Flow pattern determination
├── LiquidAccumulationTracker.java  # Low-point detection
├── SlugTracker.java              # Slug tracking and statistics
├── closure/
│   ├── GeometryCalculator.java   # Stratified geometry
│   ├── WallFriction.java         # Wall shear correlations
│   └── InterfacialFriction.java  # Interface shear correlations
└── numerics/
    ├── TimeIntegrator.java       # Runge-Kutta integration
    ├── AUSMPlusFluxCalculator.java  # Flux splitting scheme
    └── MUSCLReconstructor.java   # Higher-order reconstruction

Conservation Equations

The two-fluid model solves the following 1D PDEs:

Mass Conservation

Gas phase:

∂/∂t(αg·ρg·A) + ∂/∂x(αg·ρg·ug·A) = Γg

Liquid phase:

∂/∂t(αL·ρL·A) + ∂/∂x(αL·ρL·uL·A) = ΓL

Where:

Momentum Conservation

Gas phase:

∂/∂t(αg·ρg·ug·A) + ∂/∂x(αg·ρg·ug²·A + αg·P·A) = 
    -τwg·Swg - τi·Si + αg·ρg·g·sin(θ)·A

Liquid phase:

∂/∂t(αL·ρL·uL·A) + ∂/∂x(αL·ρL·uL²·A + αL·P·A) = 
    -τwL·SwL + τi·Si + αL·ρL·g·sin(θ)·A

Where:

Energy Conservation (Optional)

∂/∂t(E·A) + ∂/∂x((E + P)·um·A) = Q - W

Heat Transfer to Surroundings

The model supports configurable heat transfer from the pipe wall using Newton's law of cooling:

Q_wall = U × π × D × (T_surface - T_fluid)  [W/m]

Where:

API:

pipe.setSurfaceTemperature(5.0, "C");      // Seabed at 5°C
pipe.setHeatTransferCoefficient(25.0);     // 25 W/(m²·K)

Typical U-values:

Condition U [W/(m²·K)]
Insulated subsea 5-15
Uninsulated subsea 20-30
Buried onshore 2-5
Exposed onshore 50-100

Insulation Type Presets

Convenience method for setting heat transfer coefficient based on insulation type:

pipe.setInsulationType(TwoFluidPipe.InsulationType.PU_FOAM);  // 10 W/(m²·K)

Available presets:

InsulationType U [W/(m²·K)] Description
NONE 150 Bare steel in seawater
UNINSULATED_SUBSEA 25 Typical bare subsea pipe
PU_FOAM 10 Standard PU foam insulation
MULTI_LAYER 5 Multi-layer insulation
PIPE_IN_PIPE 2 Pipe-in-pipe system
VIT 0.5 Vacuum insulated tubing
BURIED_ONSHORE 3 Buried onshore pipeline
EXPOSED_ONSHORE 75 Wind-cooled exposed pipe

Variable Heat Transfer Profile

Support for different U-values along the pipe (e.g., buried vs exposed sections):

double[] htcProfile = new double[numSections];
for (int i = 0; i < numSections; i++) {
    htcProfile[i] = (i < 10) ? 5.0 : 50.0;  // First 1 km insulated, rest exposed
}
pipe.setHeatTransferProfile(htcProfile);

Soil Thermal Resistance

For buried pipelines, add soil thermal resistance:

pipe.setSoilThermalResistance(0.5);  // m²·K/W
// Effective U = 1 / (1/U + R_soil)

Joule-Thomson Effect

Temperature change from pressure drop (enabled by default):

pipe.setEnableJouleThomson(true);   // Enable J-T cooling
// dT = μ_JT × dP (typical: 0.4 K/bar for natural gas)

Pipe Wall Thermal Mass

For transient simulations, configure pipe wall properties:

pipe.setWallProperties(0.025, 7850.0, 500.0);  // 25mm steel wall
// Parameters: thickness [m], density [kg/m³], heat capacity [J/(kg·K)]

Hydrate and Wax Risk Monitoring

Monitor for flow assurance issues:

pipe.setHydrateFormationTemperature(10.0, "C");
pipe.setWaxAppearanceTemperature(25.0, "C");
pipe.run();

if (pipe.hasHydrateRisk()) {
    int section = pipe.getFirstHydrateRiskSection();
    double distance = pipe.getDistanceToHydrateRisk();
    System.out.println("Hydrate risk at " + distance + " m");
}

Temperature Profile with Units

Get temperature profile in different units:

double[] tempK = pipe.getTemperatureProfile("K");   // Kelvin
double[] tempC = pipe.getTemperatureProfile("C");   // Celsius
double[] tempF = pipe.getTemperatureProfile("F");   // Fahrenheit

Closure Relations

Flow Regime Detection

The FlowRegimeDetector uses Taitel-Dukler maps to identify:

Two detection methods are available:

FlowRegimeDetector detector = new FlowRegimeDetector();

// Default: Mechanistic approach (Taitel-Dukler, Barnea)
detector.setDetectionMethod(FlowRegimeDetector.DetectionMethod.MECHANISTIC);

// Alternative: Minimum slip criterion
detector.setDetectionMethod(FlowRegimeDetector.DetectionMethod.MINIMUM_SLIP);
// or
detector.setUseMinimumSlipCriterion(true);

The minimum slip criterion selects the flow regime that gives the minimum slip ratio (closest to 1.0), based on the principle that the system tends toward the flow pattern with minimum phase velocity difference.

Wall Friction

WallFriction calculates wall shear using:

Interfacial Friction

The InterfacialFriction class calculates the shear stress at the gas-liquid interface, which is a critical closure relation for the two-fluid model momentum equations. The interfacial friction affects the slip between phases and pressure drop distribution.

General Formulation

The interfacial shear stress follows the standard form:

τ_i = 0.5 × f_i × ρ_G × (v_G - v_L) × |v_G - v_L|

Where:

The force per unit length appearing in the momentum equations is:

F_i = τ_i × S_i

Where S_i is the interfacial perimeter/width per unit length.

Sign Convention

Positive interfacial shear acts to accelerate the liquid and decelerate the gas (when gas is faster than liquid). In the momentum equations:

Flow Regime-Specific Correlations

Flow Regime Correlation Reference Key Features
Stratified Smooth Taitel-Dukler (1976) Treats interface as smooth wall; Blasius for turbulent: f = 0.079/Re^0.25
Stratified Wavy Andritsos-Hanratty (1987) Wave roughness enhancement: f_i = f_smooth × (1 + 15√(h_L/D) × (v_G/v_G,t - 1))
Annular Wallis (1969) Film-core interaction: f_i = f_G × (1 + 300δ/D) where δ is film thickness
Slug Oliemans (1986) Bubble swarm approach with Ishii-Zuber drag coefficient
Bubble/Dispersed Schiller-Naumann - Drag on individual bubbles: C_D = (24/Re_b) × (1 + 0.15 × Re_b^0.687) for Re < 1000
Churn Enhanced Annular - Uses annular correlation with 1.5× enhancement factor

Stratified Smooth Flow (Taitel-Dukler 1976)

For smooth stratified flow, the interface is treated as a smooth wall with gas-side friction:

// Gas-side Reynolds number
Re_G = ρ_G × |v_slip| × D_G / μ_G

// Friction factor
if (Re_G < 2300):
    f_i = 16 / Re_G           // Laminar
else:
    f_i = 0.079 / Re_G^0.25   // Blasius (turbulent)

Stratified Wavy Flow (Andritsos-Hanratty 1987)

Accounts for wave-induced roughness at the interface:

// Transition gas velocity
v_G,t = 5.0 × (ρ_L / ρ_G)

// Enhancement factor (for v_G > v_G,t)
enhancement = 1.0 + 15 × (h_L/D) × (v_G/v_G,t - 1)
enhancement = min(enhancement, 20.0)  // Cap

f_i = f_smooth × enhancement

Annular Flow (Wallis 1969)

For gas-core / liquid-film interaction:

// Film thickness
δ = D/2 × (1 - (1 - α_L))

// Core diameter
D_core = D - 2δ

// Wallis enhancement
enhancement = 1.0 + 300 × δ/D
enhancement = min(enhancement, 50.0)  // Cap

f_i = f_G × enhancement

Bubble/Dispersed Flow (Schiller-Naumann)

For drag on individual bubbles in liquid continuum:

// Bubble diameter (Hinze correlation)
d_b = 2 × (0.725 × σ / ((ρ_L - ρ_G) × g))^0.5
d_b = min(d_b, D/5)

// Bubble Reynolds number
Re_b = ρ_L × |v_slip| × d_b / μ_L

// Drag coefficient
if (Re_b < 0.1):
    C_D = 240           // Stokes limit
else if (Re_b < 1000):
    C_D = 24/Re_b × (1 + 0.15 × Re_b^0.687)
else:
    C_D = 0.44          // Newton regime

// Friction factor
f_i = C_D × d_b / (4 × D)

Usage Example

InterfacialFriction interfacialFriction = new InterfacialFriction();

InterfacialFrictionResult result = interfacialFriction.calculate(
    FlowRegime.STRATIFIED_WAVY,
    gasVelocity,        // m/s
    liquidVelocity,     // m/s
    gasDensity,         // kg/m³
    liquidDensity,      // kg/m³
    gasViscosity,       // Pa·s
    liquidViscosity,    // Pa·s
    liquidHoldup,       // 0-1
    diameter,           // m
    surfaceTension      // N/m
);

double shearStress = result.interfacialShear;           // Pa
double frictionFactor = result.frictionFactor;          // dimensionless
double slipVelocity = result.slipVelocity;              // m/s
double interfacialArea = result.interfacialAreaPerLength;  // m²/m

Oil-Water Interfacial Friction (Three-Phase)

For three-phase gas-oil-water systems, the ThreeFluidConservationEquations uses a simplified Froude-based correlation for oil-water interfaces:

// Froude number based on relative velocity
Fr = |v_rel| / (g × D × |ρ_2 - ρ_1| / ρ_1)

// Simplified correlation
f_i = 0.01 × (1 + 10 × Fr²)    // capped at 0.1

This simplified approach is justified because:

The three-layer stratified geometry has two interfaces:

    ┌─────────────────┐
    │      Gas        │  ← τ_wall,G + τ_i,GO (gas-oil)
    ├─────────────────┤
    │      Oil        │  ← τ_wall,O + τ_i,GO + τ_i,OW
    ├─────────────────┤
    │     Water       │  ← τ_wall,W + τ_i,OW (oil-water)
    └─────────────────┘

Momentum exchange:

Stratified Geometry

GeometryCalculator computes for stratified flow:

Numerical Methods

Spatial Discretization

The AUSMPlusFluxCalculator implements AUSM+ flux splitting for:

Temporal Integration

TimeIntegrator supports:

Higher-Order Reconstruction

MUSCLReconstructor provides:

Thermodynamic Coupling

Flash Calculations

ThermodynamicCoupling interfaces with NeqSim's flash routines:

ThermodynamicCoupling coupling = new ThermodynamicCoupling(referenceFluid);
ThermoProperties props = coupling.flashPT(pressure, temperature);

Flash Tables

FlashTable provides fast property lookup via bilinear interpolation:

FlashTable table = new FlashTable();
table.build(fluid, pMin, pMax, nP, tMin, tMax, nT);
ThermoProperties props = table.interpolate(pressure, temperature);

Three-Phase Extension

For gas-oil-water systems, ThreeFluidSection and ThreeFluidConservationEquations extend the model to 7 equations:

Three-Layer Stratified Geometry

        ┌─────────────────┐
        │      Gas        │
        ├─────────────────┤  ← Gas-Oil Interface
        │      Oil        │
        ├─────────────────┤  ← Oil-Water Interface  
        │     Water       │
        └─────────────────┘

Simulation Modes: Steady-State vs Transient

The TwoFluidPipe supports two simulation modes: steady-state initialization via run() and incremental transient simulation via runTransient().

Steady-State Simulation: run()

The run() method performs a complete steady-state initialization of the pipeline. This is typically called once at the start to establish initial conditions before transient simulation.

What happens during run():

┌──────────────────────────────────────────────────────────────┐
│                      run(UUID id)                            │
├──────────────────────────────────────────────────────────────┤
│ 1. initializeSections()                                      │
│    ├─ Create pipe sections with uniform spacing (dx)         │
│    ├─ Flash inlet fluid to get phase properties              │
│    ├─ Initialize all sections with inlet conditions          │
│    ├─ Set elevation/inclination from terrain profile         │
│    ├─ Set outlet pressure boundary condition                 │
│    └─ Identify liquid accumulation zones                     │
│                                                              │
│ 2. runSteadyState()                                          │
│    ├─ Iterative solver (max 100 iterations)                  │
│    │   ├─ Update flow regimes for all sections               │
│    │   ├─ Calculate pressure gradient (momentum balance)     │
│    │   ├─ Update local holdups using drift-flux model        │
│    │   │   └─ Account for terrain effects (low points)       │
│    │   ├─ Update phase velocities from mass conservation     │
│    │   ├─ Update oil/water holdups for three-phase flow      │
│    │   └─ Update temperature profile (if heat transfer on)   │
│    └─ Converge when max change < tolerance (1e-4)            │
│                                                              │
│ 3. updateOutletStream()                                      │
│    ├─ Flash outlet fluid at outlet P, T                      │
│    ├─ Calculate outlet mass flow from section state          │
│    └─ Set outlet stream properties                           │
└──────────────────────────────────────────────────────────────┘

Key characteristics:

Example:

TwoFluidPipe pipe = new TwoFluidPipe("Pipeline", inletStream);
pipe.setLength(5000);
pipe.setDiameter(0.3);
pipe.setNumberOfSections(100);
pipe.run();  // Steady-state initialization

double[] pressures = pipe.getPressureProfile();
double[] holdups = pipe.getLiquidHoldupProfile();

Transient Simulation: runTransient(dt, id)

The runTransient() method advances the simulation by a specified time step. It solves the full time-dependent conservation equations and is called repeatedly in a loop.

What happens during runTransient(dt, id):

┌──────────────────────────────────────────────────────────────┐
│              runTransient(double dt, UUID id)                │
├──────────────────────────────────────────────────────────────┤
│ 1. Calculate stable time step                                │
│    ├─ dt_stable = CFL × dx / max(wave_speed)                 │
│    ├─ dt_actual = min(dt, dt_stable)                         │
│    └─ Determine number of sub-steps                          │
│                                                              │
│ 2. For each sub-step:                                        │
│    ├─ Update thermodynamics (every N steps)                  │
│    │   └─ PT flash at each section P, T                      │
│    │                                                         │
│    ├─ Store previous state U_prev                            │
│    │                                                         │
│    ├─ Time integration (RK4 by default)                      │
│    │   ├─ Calculate RHS of conservation equations            │
│    │   │   ├─ Mass fluxes: ∂(αρuA)/∂x                        │
│    │   │   ├─ Momentum sources: wall friction, interfacial   │
│    │   │   │   friction, gravity, pressure gradient          │
│    │   │   └─ Energy: heat transfer, work terms              │
│    │   └─ Advance state: U_new = U_prev + dt × RHS           │
│    │                                                         │
│    ├─ Validate and correct state                             │
│    │   ├─ Check for NaN/Inf → revert to previous             │
│    │   ├─ Ensure mass ≥ 0                                    │
│    │   └─ Limit rate of change (50% per sub-step max)        │
│    │                                                         │
│    ├─ Apply state to sections                                │
│    │                                                         │
│    ├─ Apply pressure gradient (semi-implicit)                │
│    │                                                         │
│    ├─ Apply boundary conditions                              │
│    │   ├─ Inlet: constant flow or constant pressure          │
│    │   └─ Outlet: constant pressure                          │
│    │                                                         │
│    ├─ Validate section states                                │
│    │   ├─ Fix invalid holdups (NaN, negative)                │
│    │   ├─ Ensure holdup consistency (αL = αO + αW)           │
│    │   └─ Ensure P, T are positive                           │
│    │                                                         │
│    ├─ Update accumulation tracking (if enabled)              │
│    │                                                         │
│    ├─ Update temperature (if heat transfer enabled)          │
│    │                                                         │
│    └─ Advance simulation time                                │
│                                                              │
│ 3. Update outlet stream                                      │
│                                                              │
│ 4. Update result arrays                                      │
└──────────────────────────────────────────────────────────────┘

Key characteristics:

Example:

// After steady-state initialization
pipe.run();

// Transient simulation loop
UUID simId = UUID.randomUUID();
for (int step = 0; step < 1000; step++) {
    // Change boundary conditions if needed
    if (step == 100) {
        inletStream.setFlowRate(15.0, "kg/sec");  // Flow increase
        inletStream.run();
    }

    pipe.runTransient(0.1, simId);  // Advance 0.1 seconds

    // Monitor results
    double outletFlow = pipe.getOutletMassFlow();
    double liquidInventory = pipe.getLiquidInventory("m3");
}

Integration with ProcessSystem

Both methods integrate seamlessly with ProcessSystem for coupled simulations:

ProcessSystem process = new ProcessSystem();
process.add(inletStream);
process.add(pipe);
process.add(separator);

// Steady-state initialization
process.run();

// Transient loop
for (int t = 0; t < 300; t++) {
    process.runTransient(1.0, UUID.randomUUID());
}

Comparison Summary

Aspect run() runTransient(dt, id)
Purpose Initialize steady-state Advance in time
Call frequency Once at start Repeatedly in loop
Time step N/A (iterative) User-specified + CFL limit
Solver Iterative relaxation Runge-Kutta (RK4)
Equations Simplified momentum balance Full conservation PDEs
Computation Moderate Higher (per call)
Use case Initial conditions Dynamic response

Usage Example

// Create two-phase fluid
SystemInterface fluid = new SystemSrkEos(300, 50);
fluid.addComponent("methane", 0.85);
fluid.addComponent("n-pentane", 0.15);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

// Create inlet stream
Stream inlet = new Stream("inlet", fluid);
inlet.setFlowRate(10, "kg/sec");
inlet.run();

// Create two-fluid pipe
TwoFluidPipe pipe = new TwoFluidPipe("Pipeline", inlet);
pipe.setLength(5000);        // 5 km
pipe.setDiameter(0.3);       // 300 mm
pipe.setNumberOfSections(100);

// Set terrain profile
double[] elevations = new double[100];
for (int i = 0; i < 100; i++) {
    elevations[i] = 50.0 * Math.sin(i * Math.PI / 50);
}
pipe.setElevationProfile(elevations);

// Optional: Configure heat transfer to surroundings
pipe.setSurfaceTemperature(5.0, "C");      // Seabed at 5°C
pipe.setHeatTransferCoefficient(25.0);     // 25 W/(m²·K)

// Run steady-state initialization
pipe.run();

// Transient simulation
UUID id = UUID.randomUUID();
for (int step = 0; step < 1000; step++) {
    pipe.runTransient(0.1, id);  // 0.1 second steps
}

// Get results
double[] pressures = pipe.getPressureProfile();
double[] holdups = pipe.getLiquidHoldupProfile();
double liquidInventory = pipe.getLiquidInventory("m3");

Terrain-Induced Slug Tracking

The TwoFluidPipe model includes a comprehensive terrain-induced slug tracking system that detects liquid accumulation at terrain low points and tracks the formation, propagation, and arrival of slugs at the outlet.

Enabling Slug Tracking

TwoFluidPipe pipe = new TwoFluidPipe("Pipeline", inlet);
pipe.setLength(20000);        // 20 km
pipe.setDiameter(0.3);        // 300 mm
pipe.setNumberOfSections(100);
pipe.setElevationProfile(terrain);

// Enable slug tracking
pipe.setEnableSlugTracking(true);

// Optional: Tune accumulation threshold (default 0.25)
pipe.getAccumulationTracker().setCriticalHoldup(0.35);

How Terrain Slugging Works

The slug tracking system consists of two components working together:

  1. LiquidAccumulationTracker: Identifies terrain low points and monitors liquid accumulation
  2. SlugTracker: Tracks individual slugs using Lagrangian tracking with Bendiksen velocity correlation
Terrain Profile with Slug Formation:

    Inlet ─────┐                ┌───── Outlet
               │    Valley      │
               └────────────────┘
                    ▲
                    │
            Liquid accumulates here
            When zone overflows → slug released

Accumulation Zone Detection

The tracker automatically identifies terrain low points where liquid accumulates:

// Get accumulation zones after running
var zones = pipe.getAccumulationTracker().getAccumulationZones();

for (var zone : zones) {
    System.out.println("Zone at position: " + zone.startPosition + " m");
    System.out.println("  Volume: " + zone.liquidVolume + " m³");
    System.out.println("  Max capacity: " + zone.maxVolume + " m³");
    System.out.println("  Fill fraction: " + (zone.liquidVolume / zone.maxVolume));
    System.out.println("  Is overflowing: " + zone.isOverflowing);
}

Slug Statistics

Access comprehensive slug statistics after simulation:

SlugTracker tracker = pipe.getSlugTracker();

// Summary statistics
System.out.println("Slugs generated: " + tracker.getTotalSlugsGenerated());
System.out.println("Slugs merged: " + tracker.getTotalSlugsMerged());
System.out.println("Active slugs: " + tracker.getSlugs().size());
System.out.println("Slugs at outlet: " + pipe.getOutletSlugCount());
System.out.println("Max slug length: " + tracker.getMaxSlugLength() + " m");
System.out.println("Avg slug length: " + tracker.getAverageSlugLength() + " m");
System.out.println("Slug frequency: " + tracker.getSlugFrequency() + " Hz");

// Detailed per-slug information
for (var slug : tracker.getSlugs()) {
    System.out.println("Slug #" + slug.id);
    System.out.println("  Position: " + slug.frontPosition + " m");
    System.out.println("  Length: " + slug.slugBodyLength + " m");
    System.out.println("  Volume: " + slug.liquidVolume + " m³");
    System.out.println("  Velocity: " + slug.frontVelocity + " m/s");
    System.out.println("  Age: " + slug.age + " s");
    System.out.println("  Terrain-induced: " + slug.isTerrainInduced);
}

Complete Slug Tracking Example

// Create gas-condensate fluid
SystemInterface fluid = new SystemSrkEos(288.15, 50);
fluid.addComponent("methane", 0.70);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.addComponent("n-pentane", 0.10);
fluid.addComponent("n-heptane", 0.05);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

Stream inlet = new Stream("inlet", fluid);
inlet.setFlowRate(15, "kg/sec");
inlet.setTemperature(15, "C");
inlet.setPressure(50, "bara");
inlet.run();

// Create terrain with valleys
double[] terrain = new double[100];
for (int i = 0; i < 100; i++) {
    double x = i * 200.0; // 20 km total
    double xNorm = x / 20000.0;
    terrain[i] = -20.0 * Math.exp(-Math.pow((xNorm - 0.4) / 0.1, 2));
}

TwoFluidPipe pipe = new TwoFluidPipe("SlugPipeline", inlet);
pipe.setLength(20000);
pipe.setDiameter(0.3);
pipe.setNumberOfSections(100);
pipe.setElevationProfile(terrain);
pipe.setEnableSlugTracking(true);
pipe.getAccumulationTracker().setCriticalHoldup(0.35);

// Steady-state initialization
pipe.run();

// Transient simulation (2 hours)
UUID id = UUID.randomUUID();
double simTime = 2 * 60 * 60; // 2 hours
double dt = 1.0;
int steps = (int)(simTime / dt);

for (int i = 0; i < steps; i++) {
    pipe.runTransient(dt, id);

    // Monitor progress every 15 minutes
    if (i % 900 == 0 && i > 0) {
        System.out.printf("Time: %.0f min, Slugs: %d, Outlet: %d%n",
            i / 60.0,
            pipe.getSlugTracker().getTotalSlugsGenerated(),
            pipe.getOutletSlugCount());
    }
}

// Final report
System.out.println(pipe.getSlugTrackingReport());

Comparison with Drift-Flux Model (TransientPipe)

Both TwoFluidPipe and TransientPipe use the same slug tracking infrastructure, but predict different slug frequencies due to their underlying physical models:

Aspect TwoFluidPipe TransientPipe
Physical Model 7-equation two-fluid 4-equation drift-flux
Holdup Prediction Lower (mechanistic) Higher (empirical slip)
Accumulation Rate Slower Faster
Slug Frequency Lower Higher (conservative)
Oil-Water Tracking Separate phases Combined liquid
Computation Time ~3x slower Faster

Typical behavior comparison:

Condition TwoFluidPipe TransientPipe
Avg liquid holdup 0.25-0.35 0.90-0.95
Zone fill rate 6-15%/hour 70-80% quickly
Time to first slug ~2 hours < 1 minute
Slug frequency 1 per 1-2 hours 2-3 per minute

When to use each:

Tuning Slug Tracking Parameters

// Lower critical holdup → earlier slug initiation
pipe.getAccumulationTracker().setCriticalHoldup(0.20);

// The overflow threshold in LiquidAccumulationTracker determines
// when accumulated liquid is released as a slug
// (set to 20% by default for terrain-induced slugging)

Integrated System Example: Slug Pipeline to Separator

A complete example demonstrating a slugging pipeline connected to a choke valve and separator with level control is available in:

Example file: examples/neqsim/process/pipeline/SlugPipelineToSeparatorExample.java

Test file: src/test/java/neqsim/process/equipment/pipeline/SlugPipelineToSeparatorTest.java

System Configuration

┌─────────────┐     ┌─────────────┐     ┌─────────┐     ┌───────────┐
│  Wellhead   │────▶│  Flowline   │────▶│  Choke  │────▶│ Separator │
│  (Const P)  │     │ TwoFluidPipe│     │  Valve  │     │ (Level    │
│  80 bara    │     │  3 km       │     │         │     │  Control) │
└─────────────┘     └─────────────┘     └─────────┘     └───────────┘
                          │                                    │
                     Low point                            Level
                     (Slugging)                          Controller

Boundary Conditions

Transient Behavior

The TwoFluidPipe model produces realistic transient dynamics:

Metric Observed Description
Outlet flow variation 577% Flow decreases from 8.0 to 0.46 kg/s during blowdown
Pressure range 55-58 bara Outlet pressure stabilizes to boundary value
Holdup variation 0.3 → 0.006 Pipeline drains liquid during transient

Key Code Snippet

// Create TwoFluidPipe with stream-connected inlet
TwoFluidPipe pipeline = new TwoFluidPipe("SubseaFlowline", pipeInlet);
pipeline.setLength(3000.0);  // 3 km
pipeline.setDiameter(0.254); // 10 inch
pipeline.setNumberOfSections(30);

// Set outlet pressure boundary condition
pipeline.setOutletPressure(60.0, "bara");

// Terrain with low point for liquid accumulation
double[] elevations = new double[30];
for (int i = 0; i < 30; i++) {
    double x = (i + 1.0) / 30.0;
    if (x < 0.5) {
        elevations[i] = -35.0 * x / 0.5;  // Downhill to low point
    } else {
        elevations[i] = -35.0 + 85.0 * (x - 0.5) / 0.5;  // Riser to +50m
    }
}
pipeline.setElevationProfile(elevations);

// Choke valve between pipeline and separator
ThrottlingValve choke = new ThrottlingValve("Choke", pipeline.getOutletStream());
choke.setOutletPressure(55.0);  // bara

// Separator with level control
Separator separator = new Separator("InletSeparator");
separator.addStream(choke.getOutletStream());
separator.setInternalDiameter(2.5);
separator.setSeparatorLength(8.0);

// Level controller on liquid outlet valve
ThrottlingValve liquidValve = new ThrottlingValve("LiquidValve", 
    separator.getLiquidOutStream());
LevelTransmitter levelTT = new LevelTransmitter("LT-100", separator);
ControllerDeviceBaseClass levelController = new ControllerDeviceBaseClass("LIC-100");
levelController.setTransmitter(levelTT);
levelController.setControllerSetPoint(0.50);  // 50% level
levelController.setControllerParameters(1.5, 180.0, 15.0);  // Kp, Ti, Td
liquidValve.setController(levelController);

// Build process system and run transient
ProcessSystem process = new ProcessSystem();
process.add(pipeInlet);
process.add(pipeline);
process.add(choke);
process.add(separator);
process.add(liquidValve);

process.run();  // Initial steady-state

// Transient simulation
UUID simId = UUID.randomUUID();
for (int step = 0; step < 150; step++) {
    process.runTransient(2.0, simId);

    double pipeOutFlow = pipeline.getOutletStream().getFlowRate("kg/sec");
    double level = separator.getLiquidLevel();
    // Track slug arrivals, level variations, etc.
}

Physical Scenario

The example simulates:

  1. Terrain-induced slugging: Liquid accumulates in the pipeline low point and periodically releases as slugs
  2. Transient blowdown: Pipeline drains from initial high holdup state to steady flow
  3. Choke valve operation: Reduces pressure from ~60 bara (pipeline outlet) to 55 bara (separator)
  4. Level control: PID controller adjusts liquid outlet valve to absorb flow variations
  5. Outlet flow dynamics: Mass flow at pipeline outlet varies as the system reaches equilibrium

Validation Against Published Data

The TwoFluidPipe model has been validated against established correlations and published experimental data to ensure physically correct pressure drop predictions.

Validation Test Suite

The validation test suite is implemented in TwoPhasePressureDropValidationTest.java and includes:

  1. Beggs & Brill (1973) Comparison - Validation against the widely-used empirical correlation
  2. Lockhart-Martinelli (1949) Consistency - Cross-check with the classic two-phase multiplier method
  3. Industrial-Scale Pipeline Tests - Verification for typical North Sea conditions
  4. Inclination Effects - Validation of gravity effects for uphill/downhill flow

Validation Results

Comparison of TwoFluidPipe against Beggs & Brill (1973) test cases:

Test Case D (mm) L (m) B&B ΔP (bar) TFP ΔP (bar) Ratio
Horizontal Segregated 100 500 0.844 0.587 0.70
Horizontal Intermittent 100 500 2.211 2.253 1.02
Horizontal Distributed 100 500 1.979 4.127 2.09
Uphill 10° 100 500 3.688 3.700 1.00
Downhill 10° 100 500 -0.850 -1.013 1.19

Key Observations:

The differences are expected because:

Physical Validation

The model correctly captures the following physical behaviors:

Physical Effect Expected Behavior Model Result
Pressure drop vs GLR Increases with gas-liquid ratio ✓ Verified
Uphill flow Higher ΔP (gravity opposes) ✓ Verified
Downhill flow Negative ΔP (pressure gain) ✓ Verified
Hydrostatic head Proportional to sin(θ) ✓ Verified
Friction loss Increases with velocity² ✓ Verified

Running Validation Tests

To run the validation tests:

# Run all two-phase pressure drop validation tests
./mvnw test -Dtest=TwoPhasePressureDropValidationTest

# Run specific validation test
./mvnw test -Dtest=TwoPhasePressureDropValidationTest#testTwoFluidPipeValidation

Comparison with PipeBeggsAndBrills

For applications where empirical accuracy is preferred over mechanistic modeling, NeqSim also provides PipeBeggsAndBrills which implements the original Beggs & Brill correlation with the Payne et al. (1979) corrections.

Feature TwoFluidPipe PipeBeggsAndBrills
Approach Mechanistic (conservation eqs) Empirical (correlations)
Flow regimes Computed from physics Correlated flow map
Transient capability Yes Steady-state only
Heat transfer Configurable Built-in
Terrain effects Elevation profile Single angle
Best for Transient, complex terrain Quick steady-state

References

  1. Bendiksen, K.H. et al. (1991) - "The Dynamic Two-Fluid Model OLGA: Theory and Application", SPE Production Engineering
  2. Taitel, Y. and Dukler, A.E. (1976) - "A Model for Predicting Flow Regime Transitions in Horizontal and Near Horizontal Gas-Liquid Flow", AIChE Journal
  3. Issa, R.I. and Kempf, M.H.W. (2003) - "Simulation of Slug Flow in Horizontal and Nearly Horizontal Pipes with the Two-Fluid Model", Int. J. Multiphase Flow
  4. Liou, M.S. (1996) - "A Sequel to AUSM: AUSM+", J. Computational Physics

Test Coverage

The model includes comprehensive unit tests:

Core Tests

Validation Tests

Integration Tests (SlugPipelineToSeparatorTest)

Temperature Comparison Tests (TemperatureDropComparisonTest)

Total: 160+ tests

Multiphase Transient

Multiphase 1D Transient Pipeline Model - Implementation Recommendations

Executive Summary

This document outlines recommendations for implementing a full multiphase 1D transient pipeline model in NeqSim, similar to commercial tools like OLGA and LedaFlow. The model would handle:

Current State in NeqSim

Existing Capabilities

Component Status Notes
PipeBeggsAndBrills Steady-state multiphase, quasi-transient advection
WaterHammerPipe Fast transients (MOC), single-phase
Thermodynamics Full EOS, flash calculations, physical properties
Flow regime maps Beggs & Brill flow pattern detection
Holdup correlations Beggs & Brill liquid holdup

Gaps for Full Transient Multiphase

Feature Current Required
Mass conservation Quasi-steady Full PDE
Momentum Steady friction Full transient + interfacial
Energy Optional heat loss Full enthalpy transport
Liquid accumulation Not tracked Dynamic holdup evolution
Slug tracking Not modeled Slug initiation, growth, dissipation
Phase slip Correlations Mechanistic drift-flux or two-fluid

The two-fluid model solves separate conservation equations for each phase:

Conservation Equations

Gas Mass: $$\frac{\partial}{\partial t}(\alpha_g \rho_g A) + \frac{\partial}{\partial x}(\alpha_g \rho_g v_g A) = \Gamma_g$$

Liquid Mass: $$\frac{\partial}{\partial t}(\alpha_L \rho_L A) + \frac{\partial}{\partial x}(\alpha_L \rho_L v_L A) = \Gamma_L$$

Gas Momentum: $$\frac{\partial}{\partial t}(\alpha_g \rho_g v_g A) + \frac{\partial}{\partial x}(\alpha_g \rho_g v_g^2 A) = -\alpha_g A \frac{\partial P}{\partial x} - \tau_{wg} S_g - \tau_i S_i - \alpha_g \rho_g g A \sin\theta$$

Liquid Momentum: $$\frac{\partial}{\partial t}(\alpha_L \rho_L v_L A) + \frac{\partial}{\partial x}(\alpha_L \rho_L v_L^2 A) = -\alpha_L A \frac{\partial P}{\partial x} - \tau_{wL} S_L + \tau_i S_i - \alpha_L \rho_L g A \sin\theta$$

Mixture Energy: $$\frac{\partial}{\partial t}(E_{mix} A) + \frac{\partial}{\partial x}((E_{mix} + P) v_{mix} A) = Q_{wall} + W_{friction}$$

Where:

Alternative: Drift-Flux Model (Simpler, Faster)

For less demanding applications, a drift-flux model combines phases:

Mixture Mass: $$\frac{\partial}{\partial t}(\rho_m A) + \frac{\partial}{\partial x}(\rho_m v_m A) = 0$$

Mixture Momentum: $$\frac{\partial}{\partial t}(\rho_m v_m A) + \frac{\partial}{\partial x}(\rho_m v_m^2 A + \Delta P_{slip}) = -A \frac{\partial P}{\partial x} - \tau_w S - \rho_m g A \sin\theta$$

Drift-Flux Relation: $$v_g = C_0 v_m + v_{drift}$$

Where $C_0$ is the distribution parameter and $v_{drift}$ is the drift velocity (from correlations).


Proposed Class Structure

src/main/java/neqsim/process/equipment/pipeline/
├── MultiphasePipe.java              # Main transient solver
├── MultiphasePipeSection.java       # Single pipe section state
├── transient/
│   ├── ConservationEquations.java   # PDE discretization
│   ├── FluxCalculator.java          # Numerical flux (AUSM, HLL, etc.)
│   ├── SourceTerms.java             # Friction, gravity, mass transfer
│   ├── SlugTracker.java             # Slug initiation and tracking
│   └── HoldupEvolution.java         # Liquid accumulation dynamics
├── closure/
│   ├── FlowRegimeMap.java           # Mechanistic flow regime
│   ├── InterfacialFriction.java     # τ_i correlations
│   ├── WallFriction.java            # τ_wg, τ_wL correlations
│   ├── DriftFluxParameters.java     # C_0, v_drift correlations
│   └── EntrainmentDeposition.java   # Droplet exchange
└── geometry/
    ├── PipeProfile.java             # Elevation, diameter profile
    └── PipeNetwork.java             # Junctions, branches

Key Implementation Components

1. State Vector and Grid

public class MultiphasePipeSection {
    // Primary variables (conservative)
    private double pressure;           // Pa
    private double gasHoldup;          // α_g (0-1)
    private double oilHoldup;          // α_o (0-1)
    private double waterHoldup;        // α_w (0-1)
    private double gasVelocity;        // m/s
    private double liquidVelocity;     // m/s (or separate oil/water)
    private double temperature;        // K

    // Derived quantities
    private double gasDensity;
    private double liquidDensity;
    private double mixtureVelocity;
    private double liquidLevel;        // For stratified flow
    private FlowRegime flowRegime;

    // Slug tracking
    private boolean isInSlug;
    private double slugFrontPosition;
    private double slugTailPosition;
    private double slugHoldup;

    // Geometry
    private double diameter;
    private double area;
    private double inclination;        // radians
    private double position;           // m from inlet
}

2. Flow Regime Detection (Mechanistic)

public enum FlowRegime {
    STRATIFIED_SMOOTH,
    STRATIFIED_WAVY,
    ANNULAR,
    SLUG,
    DISPERSED_BUBBLE,
    BUBBLE,
    CHURN,
    MIST
}

public class MechanisticFlowRegime {

    /**
     * Determine flow regime using Taitel-Dukler or similar mechanistic model.
     */
    public FlowRegime determine(double vsg, double vsl, double diameter,
                                 double inclination, PhaseProperties gas,
                                 PhaseProperties liquid) {

        // Calculate dimensionless groups
        double froude = calcFroudeNumber(vsg, vsl, diameter);
        double lockhart = calcLockhartMartinelli(gas, liquid, vsg, vsl);

        // Stratified stability (Kelvin-Helmholtz)
        double criticalGasVelocity = calcKelvinHelmholtzLimit(
            liquid.getDensity(), gas.getDensity(), 
            liquidLevel, diameter, inclination);

        if (vsg < criticalGasVelocity && inclination < Math.toRadians(10)) {
            // Check wavy vs smooth
            if (isWavyTransition(vsg, liquid)) {
                return FlowRegime.STRATIFIED_WAVY;
            }
            return FlowRegime.STRATIFIED_SMOOTH;
        }

        // Slug formation criterion
        if (isSlugCondition(vsg, vsl, liquidLevel, diameter)) {
            return FlowRegime.SLUG;
        }

        // Annular transition
        if (vsg > calcAnnularTransition(diameter, gas, liquid)) {
            return FlowRegime.ANNULAR;
        }

        // ... other transitions
        return FlowRegime.INTERMITTENT;
    }

    /**
     * Kelvin-Helmholtz instability criterion for stratified flow.
     */
    private double calcKelvinHelmholtzLimit(double rhoL, double rhoG,
                                             double hL, double D, double theta) {
        double g = 9.81;
        double aG = calcGasArea(hL, D);
        double aL = calcLiquidArea(hL, D);
        double dAL_dhL = calcDerivativeArea(hL, D);

        // Taitel-Dukler criterion
        double term1 = (rhoL - rhoG) * g * Math.cos(theta) * aG;
        double term2 = rhoG * aL * dAL_dhL;

        return Math.sqrt(term1 / term2);
    }
}

3. Slug Flow Model

public class SlugTracker {

    private List<Slug> activeSlugs = new ArrayList<>();
    private double minSlugLength;       // Minimum stable slug length
    private double slugInitiationVoid;  // α_g threshold for slug formation

    public class Slug {
        double frontPosition;    // m
        double tailPosition;     // m
        double velocity;         // m/s
        double holdup;           // liquid fraction in slug body
        double bubbleHoldup;     // gas fraction in slug bubble
        double frequency;        // slugs per unit time
        boolean isTerrainInduced;
    }

    /**
     * Check for slug initiation at each grid point.
     */
    public void checkSlugInitiation(MultiphasePipeSection[] sections, double dt) {
        for (int i = 1; i < sections.length - 1; i++) {
            MultiphasePipeSection sec = sections[i];

            // Terrain-induced slug: liquid accumulation at low point
            if (isLowPoint(sections, i) && sec.getLiquidHoldup() > slugInitiationVoid) {
                if (!sec.isInSlug() && sec.getGasVelocity() > getMinGasVelocityForSlug(sec)) {
                    initiateSlug(sections, i);
                }
            }

            // Hydrodynamic slug: wave growth in stratified-wavy
            if (sec.getFlowRegime() == FlowRegime.STRATIFIED_WAVY) {
                if (isWaveBlockage(sec)) {
                    initiateSlug(sections, i);
                }
            }
        }
    }

    /**
     * Propagate existing slugs.
     */
    public void propagateSlugs(MultiphasePipeSection[] sections, double dt) {
        Iterator<Slug> iter = activeSlugs.iterator();
        while (iter.hasNext()) {
            Slug slug = iter.next();

            // Slug front velocity (Bendiksen correlation)
            double vFront = calcSlugFrontVelocity(slug, sections);
            slug.frontPosition += vFront * dt;

            // Slug tail velocity
            double vTail = calcSlugTailVelocity(slug, sections);
            slug.tailPosition += vTail * dt;

            // Slug length
            double length = slug.frontPosition - slug.tailPosition;

            // Slug dissipation
            if (length < minSlugLength || slug.frontPosition > getPipeLength()) {
                iter.remove();
                dissipateSlug(slug, sections);
            }

            // Update holdup in slug region
            updateSlugHoldup(slug, sections);
        }
    }

    /**
     * Bendiksen (1984) slug front velocity.
     */
    private double calcSlugFrontVelocity(Slug slug, MultiphasePipeSection[] sections) {
        MultiphasePipeSection sec = getSectionAt(slug.frontPosition, sections);
        double vm = sec.getMixtureVelocity();
        double C = 1.2;  // Distribution parameter
        double vd = 0.35 * Math.sqrt(9.81 * sec.getDiameter());  // Drift velocity
        return C * vm + vd;
    }
}

4. Liquid Accumulation Model

public class LiquidAccumulation {

    /**
     * Track liquid inventory and low-point accumulation.
     */
    public void updateAccumulation(MultiphasePipeSection[] sections, 
                                    double dt, double inletLiquidRate) {

        // Identify low points in terrain profile
        List<Integer> lowPoints = findLowPoints(sections);

        for (int lowIdx : lowPoints) {
            MultiphasePipeSection low = sections[lowIdx];

            // Liquid drainage into low point
            double drainageIn = calcDrainageRate(sections, lowIdx, -1);  // From upstream
            drainageIn += calcDrainageRate(sections, lowIdx, +1);        // From downstream

            // Liquid carryover out of low point
            double carryover = calcCarryoverRate(low);

            // Net accumulation
            double netRate = drainageIn - carryover;

            // Update holdup
            double dHoldup = netRate * dt / (low.getArea() * getSegmentLength());
            low.setLiquidHoldup(low.getLiquidHoldup() + dHoldup);

            // Check for slug initiation if holdup exceeds critical
            if (low.getLiquidHoldup() > getCriticalHoldup(low)) {
                slugTracker.initiateSlug(sections, lowIdx);
            }
        }
    }

    /**
     * Calculate liquid drainage rate into low point.
     */
    private double calcDrainageRate(MultiphasePipeSection[] sections, 
                                     int lowIdx, int direction) {
        int neighborIdx = lowIdx + direction;
        if (neighborIdx < 0 || neighborIdx >= sections.length) return 0;

        MultiphasePipeSection neighbor = sections[neighborIdx];
        MultiphasePipeSection low = sections[lowIdx];

        // Height difference drives drainage
        double dz = neighbor.getElevation() - low.getElevation();
        if (dz <= 0) return 0;  // No drainage if neighbor is lower

        // Stratified film drainage (Wallis falling film)
        double holdup = neighbor.getLiquidHoldup();
        double filmThickness = calcFilmThickness(holdup, neighbor.getDiameter());
        double drainageVelocity = calcFilmDrainageVelocity(filmThickness, 
            neighbor.getInclination(), neighbor.getLiquidViscosity());

        return holdup * neighbor.getArea() * drainageVelocity;
    }

    /**
     * Calculate liquid carryover (entrainment by gas).
     */
    private double calcCarryoverRate(MultiphasePipeSection section) {
        // Critical gas velocity for liquid removal (Turner correlation)
        double vgCrit = calcCriticalGasVelocity(section);
        double vg = section.getGasVelocity();

        if (vg < vgCrit) return 0;

        // Carryover rate increases with excess gas velocity
        double excessVelocity = vg - vgCrit;
        double entrainmentFraction = calcEntrainmentFraction(excessVelocity, section);

        return entrainmentFraction * section.getLiquidHoldup() * 
               section.getArea() * section.getLiquidVelocity();
    }
}

5. Numerical Scheme

public class MultiphaseFluxCalculator {

    public enum FluxScheme {
        AUSM_PLUS,      // Advection Upstream Splitting Method
        HLL,            // Harten-Lax-van Leer
        ROE,            // Roe approximate Riemann solver
        UPWIND          // First-order upwind
    }

    /**
     * Calculate numerical flux at cell interface using AUSM+ scheme.
     * Good for multiphase flows with large density ratios.
     */
    public double[] calcFluxAUSMPlus(double[] UL, double[] UR, 
                                      PhaseProperties propsL, PhaseProperties propsR) {

        // Primitive variables
        double rhoL = UL[0], rhoR = UR[0];
        double vL = UL[1] / rhoL, vR = UR[1] / rhoR;
        double pL = propsL.getPressure(), pR = propsR.getPressure();
        double cL = propsL.getSoundSpeed(), cR = propsR.getSoundSpeed();

        // Interface speed of sound
        double cHalf = 0.5 * (cL + cR);

        // Mach numbers
        double ML = vL / cHalf;
        double MR = vR / cHalf;

        // Split Mach numbers (AUSM+ splitting functions)
        double Mplus = calcMachPlus(ML);
        double Mminus = calcMachMinus(MR);
        double Mhalf = Mplus + Mminus;

        // Split pressures
        double Pplus = calcPressurePlus(ML) * pL;
        double Pminus = calcPressureMinus(MR) * pR;
        double Phalf = Pplus + Pminus;

        // Convective flux
        double[] flux = new double[3];
        if (Mhalf >= 0) {
            flux[0] = cHalf * Mhalf * rhoL;
            flux[1] = cHalf * Mhalf * rhoL * vL + Phalf;
            flux[2] = cHalf * Mhalf * rhoL * propsL.getEnthalpy();
        } else {
            flux[0] = cHalf * Mhalf * rhoR;
            flux[1] = cHalf * Mhalf * rhoR * vR + Phalf;
            flux[2] = cHalf * Mhalf * rhoR * propsR.getEnthalpy();
        }

        return flux;
    }

    /**
     * MUSCL reconstruction for second-order accuracy.
     */
    public double[] reconstructMUSCL(double[] U, int i, double[] dx) {
        // Slope limiter (minmod, van Leer, etc.)
        double slope = slopeLimiter(U[i-1], U[i], U[i+1], dx[i-1], dx[i]);

        double[] UL = new double[U.length];
        double[] UR = new double[U.length];

        UL[i] = U[i] - 0.5 * slope * dx[i];
        UR[i] = U[i] + 0.5 * slope * dx[i];

        return new double[][] {UL, UR};
    }
}

6. Time Integration

public class MultiphaseTimeIntegrator {

    /**
     * Explicit Runge-Kutta time stepping with CFL control.
     */
    public void stepRK4(MultiphasePipeSection[] sections, double dt) {
        int n = sections.length;
        double[][] U = getConservativeVariables(sections);

        // RK4 stages
        double[][] k1 = calcRHS(sections, U);
        double[][] U1 = addArrays(U, scaleArray(k1, 0.5 * dt));

        double[][] k2 = calcRHS(sections, U1);
        double[][] U2 = addArrays(U, scaleArray(k2, 0.5 * dt));

        double[][] k3 = calcRHS(sections, U2);
        double[][] U3 = addArrays(U, scaleArray(k3, dt));

        double[][] k4 = calcRHS(sections, U3);

        // Combine stages
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < U[i].length; j++) {
                U[i][j] += dt / 6.0 * (k1[i][j] + 2*k2[i][j] + 2*k3[i][j] + k4[i][j]);
            }
        }

        setConservativeVariables(sections, U);
    }

    /**
     * Calculate stable time step based on CFL condition.
     */
    public double calcTimeStep(MultiphasePipeSection[] sections, double cflNumber) {
        double dtMin = Double.MAX_VALUE;

        for (MultiphasePipeSection sec : sections) {
            // Wave speeds
            double cGas = sec.getGasSoundSpeed();
            double cLiq = sec.getLiquidSoundSpeed();
            double vg = Math.abs(sec.getGasVelocity());
            double vl = Math.abs(sec.getLiquidVelocity());

            // Maximum characteristic speed
            double maxSpeed = Math.max(vg + cGas, vl + cLiq);

            // CFL condition
            double dt = cflNumber * sec.getSegmentLength() / maxSpeed;
            dtMin = Math.min(dtMin, dt);
        }

        return dtMin;
    }
}

7. Thermodynamic Coupling

public class ThermodynamicCoupling {

    /**
     * Update phase properties using NeqSim flash calculations.
     */
    public void updatePhaseProperties(MultiphasePipeSection section) {
        // Clone and update thermodynamic system
        SystemInterface system = section.getThermoSystem().clone();
        system.setPressure(section.getPressure() / 1e5);  // Convert to bar
        system.setTemperature(section.getTemperature());

        // Flash calculation
        ThermodynamicOperations ops = new ThermodynamicOperations(system);
        ops.TPflash();
        system.initPhysicalProperties();

        // Extract phase properties
        if (system.hasPhaseType("gas")) {
            PhaseInterface gas = system.getPhase("gas");
            section.setGasDensity(gas.getDensity("kg/m3"));
            section.setGasViscosity(gas.getViscosity("kg/msec"));
            section.setGasSoundSpeed(gas.getSoundSpeed());
            section.setGasEnthalpy(gas.getEnthalpy("J/mol"));
            section.setGasMoleFraction(system.getMoleFraction(gas.getPhaseIndex()));
        }

        if (system.hasPhaseType("oil")) {
            PhaseInterface oil = system.getPhase("oil");
            section.setOilDensity(oil.getDensity("kg/m3"));
            section.setOilViscosity(oil.getViscosity("kg/msec"));
            // ... other properties
        }

        if (system.hasPhaseType("aqueous")) {
            PhaseInterface water = system.getPhase("aqueous");
            section.setWaterDensity(water.getDensity("kg/m3"));
            // ... other properties
        }

        // Mass transfer rates (flashing/condensation)
        section.setMassTransferRate(calcMassTransferRate(section, system));
    }

    /**
     * Calculate mass transfer between phases (simplified).
     */
    private double calcMassTransferRate(MultiphasePipeSection section, 
                                         SystemInterface system) {
        // Departure from equilibrium
        double pBubble = system.getBubblePointPressure();
        double pActual = section.getPressure() / 1e5;

        if (pActual < pBubble) {
            // Flashing - liquid to gas
            double dP = pBubble - pActual;
            return section.getMassTransferCoefficient() * dP;
        } else {
            // Condensation - gas to liquid
            double dP = pActual - pBubble;
            return -section.getMassTransferCoefficient() * dP;
        }
    }
}

Main Solver Class

public class MultiphasePipe extends Pipeline {

    private MultiphasePipeSection[] sections;
    private SlugTracker slugTracker;
    private LiquidAccumulation liquidAccumulation;
    private MultiphaseFluxCalculator fluxCalculator;
    private MultiphaseTimeIntegrator timeIntegrator;
    private ThermodynamicCoupling thermoCoupling;

    private double cflNumber = 0.5;
    private int numberOfSections = 100;
    private double totalLength;
    private double[] elevationProfile;
    private double[] diameterProfile;

    public MultiphasePipe(String name, StreamInterface inStream) {
        super(name, inStream);
        slugTracker = new SlugTracker();
        liquidAccumulation = new LiquidAccumulation();
        fluxCalculator = new MultiphaseFluxCalculator();
        timeIntegrator = new MultiphaseTimeIntegrator();
        thermoCoupling = new ThermodynamicCoupling();
    }

    @Override
    public void run(UUID id) {
        // Initialize grid
        initializeSections();

        // Steady-state initialization
        runSteadyState();

        setCalculationIdentifier(id);
    }

    @Override
    public void runTransient(double dt, UUID id) {
        // Adaptive time stepping
        double dtStable = timeIntegrator.calcTimeStep(sections, cflNumber);
        double dtActual = Math.min(dt, dtStable);

        int subSteps = (int) Math.ceil(dt / dtActual);
        dtActual = dt / subSteps;

        for (int step = 0; step < subSteps; step++) {
            // 1. Update thermodynamic properties
            for (MultiphasePipeSection sec : sections) {
                thermoCoupling.updatePhaseProperties(sec);
            }

            // 2. Detect flow regimes
            for (MultiphasePipeSection sec : sections) {
                sec.setFlowRegime(flowRegimeMap.determine(sec));
            }

            // 3. Calculate fluxes and advance solution
            timeIntegrator.stepRK4(sections, dtActual);

            // 4. Track liquid accumulation
            liquidAccumulation.updateAccumulation(sections, dtActual, 
                getInletLiquidRate());

            // 5. Track and propagate slugs
            slugTracker.checkSlugInitiation(sections, dtActual);
            slugTracker.propagateSlugs(sections, dtActual);

            // 6. Apply boundary conditions
            applyBoundaryConditions();
        }

        // Update outlet stream
        updateOutletStream();

        setCalculationIdentifier(id);
    }

    /**
     * Get liquid inventory in the pipeline.
     */
    public double getLiquidInventory(String unit) {
        double volume = 0;
        for (MultiphasePipeSection sec : sections) {
            volume += sec.getLiquidHoldup() * sec.getArea() * sec.getSegmentLength();
        }

        switch (unit.toLowerCase()) {
            case "m3": return volume;
            case "bbl": return volume * 6.28981;
            case "l": return volume * 1000;
            default: return volume;
        }
    }

    /**
     * Get slug statistics.
     */
    public SlugStatistics getSlugStatistics() {
        return slugTracker.getStatistics();
    }

    /**
     * Get holdup profile.
     */
    public double[] getHoldupProfile() {
        double[] holdup = new double[sections.length];
        for (int i = 0; i < sections.length; i++) {
            holdup[i] = sections[i].getLiquidHoldup();
        }
        return holdup;
    }
}

Phased Implementation Plan

Phase 1: Drift-Flux Model (3-4 months)

Scope:

Deliverables:

Phase 2: Two-Fluid Model (4-6 months)

Scope:

Deliverables:

Phase 3: Advanced Features (3-4 months)

Scope:

Deliverables:


Closure Relations Required

Wall Friction

Flow Regime Correlation
Single-phase Haaland/Colebrook
Stratified Taitel-Dukler (separate phases)
Slug Slug body + film friction
Annular Core + film model

Interfacial Friction

Model Application
Taitel-Dukler Stratified flow
Andritsos-Hanratty Wavy stratified
Wallis Annular film
Oliemans Slug bubble zone

Holdup Correlations

Model Type
Taitel-Dukler Mechanistic stratified
Gregory Slug holdup
Beggs-Brill Empirical (backup)
Bendiksen Slug bubble holdup

Drift-Flux Parameters

Correlation C_0 v_drift
Zuber-Findlay (vertical) 1.2 1.53(gσΔρ/ρ_L²)^0.25
Bendiksen (horizontal) 1.05 0.35√(gD)
Ferschneider Variable Inclination-dependent

Validation Strategy

Unit Tests

  1. Shock tube - Verify numerical scheme captures discontinuities
  2. Steady-state - Match existing Beggs & Brill
  3. Single slug - Verify slug propagation velocity
  4. Low-point filling - Verify accumulation rate

Integration Tests

  1. Ramp-up scenario - Increasing rate, observe slug formation
  2. Ramp-down scenario - Decreasing rate, observe liquid fallback
  3. Pigging - Pig transit time and liquid delivery
  4. Blowdown - Depressurization with liquid holdup

Validation Cases (vs. Commercial Tools)

  1. Tordis flowline (Statoil benchmark)
  2. Sleipner riser (slug characteristics)
  3. Prudhoe Bay (cold restart)
  4. Academic data (Tulsa, SINTEF)

Performance Considerations

Computational Cost

Component Cost Optimization
Flash calculations High Tabulation, caching
Flow regime map Medium Skip if regime unchanged
Flux calculation Low Vectorization
Slug tracking Low Skip if no slugs
  1. Coarse grid for long-term (100-200 sections for 10 km)
  2. Flash tabulation - Pre-compute properties on P-T grid
  3. Adaptive time stepping - Large Δt when stable
  4. Parallel sections - OpenMP/SIMD for flux calculations

References

  1. Bendiksen, K.H. et al. (1991). "The Dynamic Two-Fluid Model OLGA: Theory and Application". SPE Production Engineering.
  2. Taitel, Y. & Dukler, A.E. (1976). "A Model for Predicting Flow Regime Transitions in Horizontal and Near Horizontal Gas-Liquid Flow". AIChE Journal.
  3. Issa, R.I. & Kempf, M.H.W. (2003). "Simulation of Slug Flow in Horizontal and Nearly Horizontal Pipes with the Two-Fluid Model". Int. J. Multiphase Flow.
  4. Kjeldby, T.K. et al. (2013). "Lagrangian Slug Flow Modeling and Sensitivity on Hydrodynamic Slug Initiation Methods in a Severe Slugging Case". Int. J. Multiphase Flow.
  5. Bonizzi, M. & Issa, R.I. (2003). "A Model for Simulating Gas Bubble Entrainment in Two-Phase Horizontal Slug Flow". Int. J. Multiphase Flow.

Conclusion

Implementing a full multiphase transient model is a significant undertaking but highly valuable for:

The recommended approach is:

  1. Start with drift-flux for faster development and validation
  2. Graduate to two-fluid for slug tracking accuracy
  3. Leverage NeqSim thermodynamics throughout
  4. Validate against commercial tools and field data

The existing NeqSim infrastructure (thermodynamics, physical properties, process equipment) provides an excellent foundation for this extension.

Transient Pipe Wiki

Transient Multiphase Pipe Model

Overview

The TransientPipe class provides a 1D transient multiphase (gas-liquid) flow simulator for pipelines. It uses the drift-flux formulation combined with mechanistic flow regime detection to model complex phenomena like terrain-induced slugging, liquid accumulation at low points, and transient pressure wave propagation.

The model supports:

Key Features

Physical Models

Drift-Flux Model

The core model uses the Zuber-Findlay drift-flux formulation:

v_G = C₀ · v_m + v_d

Where:

The distribution coefficient C₀ and drift velocity v_d depend on the flow regime:

Flow Regime C₀ Drift Velocity Correlation
Bubble 1.2 Harmathy (1960)
Slug 1.05-1.2 (Fr dependent) Bendiksen (1984)
Annular 1.0 Film drainage model
Stratified Calculated from momentum balance ~0

Three-Phase Flow Handling

When both oil and aqueous (water) liquid phases are present, the model calculates volume-weighted average liquid properties:

ρ_L,avg = (V_oil / V_total) × ρ_oil + (V_water / V_total) × ρ_water
μ_L,avg = (V_oil / V_total) × μ_oil + (V_water / V_total) × μ_water
H_L,avg = (V_oil / V_total) × H_oil + (V_water / V_total) × H_water
c_L,avg = (V_oil / V_total) × c_oil + (V_water / V_total) × c_water

Where:

This approach maintains the drift-flux framework while properly accounting for oil-water mixtures in the liquid phase. If only one liquid phase is present (oil OR water), that phase's properties are used directly.

Flow Regime Detection

The model supports two methods for flow regime detection:

Mechanistic Approach (Default)

Uses mechanistic criteria to determine the local flow pattern:

  1. Single-Phase Check: If liquid holdup < 0.001 → Gas; if gas holdup < 0.001 → Liquid
  2. Taitel-Dukler (1976) for horizontal/near-horizontal pipes:
    • Stratified-Slug transition based on Kelvin-Helmholtz stability
    • Uses Lockhart-Martinelli parameter and Froude numbers
  3. Barnea (1987) for inclined pipes:
    • Unified model covering all inclinations
    • Bubble-slug transition at void fraction ≈ 0.25
    • Annular transition at high gas velocities

Minimum Slip Criterion

An alternative approach that selects the flow regime with the minimum slip ratio (closest to 1.0, i.e., homogeneous flow). This is based on the principle that the physical system naturally tends toward the flow pattern with minimum phase velocity difference.

FlowRegimeDetector detector = new FlowRegimeDetector();

// Enable minimum slip criterion
detector.setUseMinimumSlipCriterion(true);

// Or use the detection method enum
detector.setDetectionMethod(FlowRegimeDetector.DetectionMethod.MINIMUM_SLIP);

// Detect flow regime
FlowRegime regime = detector.detectFlowRegime(section);

The minimum slip criterion evaluates orientation-appropriate candidate regimes:

Numerical Method

The model solves the conservation equations using:

Conservative variables:

U = [ρ_G·α_G, ρ_L·α_L, ρ_m·u, ρ_m·e]

Quick Start

Basic Horizontal Pipeline

import neqsim.process.equipment.pipeline.twophasepipe.TransientPipe;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create two-phase fluid
SystemInterface fluid = new SystemSrkEos(300, 50); // 300 K, 50 bar
fluid.addComponent("methane", 0.8);
fluid.addComponent("n-pentane", 0.2);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

// Create inlet stream
Stream inlet = new Stream("inlet", fluid);
inlet.setFlowRate(5, "kg/sec");
inlet.run();

// Create transient pipe
TransientPipe pipe = new TransientPipe("Pipeline", inlet);
pipe.setLength(1000);          // 1000 m
pipe.setDiameter(0.2);         // 200 mm
pipe.setRoughness(0.00005);    // 50 μm
pipe.setNumberOfSections(50);  // 50 cells
pipe.setMaxSimulationTime(60); // 60 seconds

// Run simulation
pipe.run();

// Get results
double[] pressures = pipe.getPressureProfile();
double[] holdups = pipe.getLiquidHoldupProfile();
double[] gasVel = pipe.getGasVelocityProfile();

Terrain Pipeline with Low Points

// Create pipe with terrain
TransientPipe pipe = new TransientPipe("TerrainPipe", inlet);
pipe.setLength(2000);
pipe.setDiameter(0.3);
pipe.setNumberOfSections(40);
pipe.setMaxSimulationTime(300);

// Define elevation profile with a low point
double[] elevations = new double[40];
for (int i = 0; i < 40; i++) {
    double x = i * 50.0; // Position along pipe
    if (x < 500) {
        elevations[i] = 0;
    } else if (x < 1000) {
        elevations[i] = -20 * (x - 500) / 500; // Downhill to -20m
    } else if (x < 1500) {
        elevations[i] = -20 + 20 * (x - 1000) / 500; // Uphill
    } else {
        elevations[i] = 0;
    }
}
pipe.setElevationProfile(elevations);

pipe.run();

// Check for liquid accumulation
var accumTracker = pipe.getAccumulationTracker();
for (var zone : accumTracker.getAccumulationZones()) {
    System.out.println("Accumulation at position: " + zone.getPosition());
    System.out.println("Accumulated volume: " + zone.getAccumulatedVolume() + " m³");
}

Vertical Riser

TransientPipe riser = new TransientPipe("Riser", inlet);
riser.setLength(200);
riser.setDiameter(0.15);
riser.setNumberOfSections(40);

// Vertical profile
double[] elevations = new double[40];
for (int i = 0; i < 40; i++) {
    elevations[i] = i * 5; // 5m per section
}
riser.setElevationProfile(elevations);

riser.run();

// Significant pressure drop due to hydrostatic head
double[] P = riser.getPressureProfile();

Three-Phase Gas-Oil-Water Flow

// Create three-phase fluid
SystemInterface fluid = new SystemSrkEos(300, 50);
fluid.addComponent("methane", 0.40);
fluid.addComponent("propane", 0.10);
fluid.addComponent("n-heptane", 0.20);
fluid.addComponent("n-octane", 0.10);
fluid.addComponent("water", 0.20);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

// Create stream and pipe
Stream inlet = new Stream("inlet", fluid);
inlet.setFlowRate(15, "kg/sec");
inlet.run();

TransientPipe pipe = new TransientPipe("ThreePhasePipe", inlet);
pipe.setLength(1000);
pipe.setDiameter(0.25);
pipe.setNumberOfSections(50);
pipe.run();

// The model automatically uses volume-weighted averaging for liquid properties
// when both oil and aqueous phases are present
double deltaP = P[0] - P[39];
System.out.println("Riser pressure drop: " + deltaP/1e5 + " bar");

Configuration Options

Geometry

Method Description Default
setLength(double) Total pipe length (m) -
setDiameter(double) Inner diameter (m) -
setRoughness(double) Wall roughness (m) 0.0001
setNumberOfSections(int) Discretization cells 50
setElevationProfile(double[]) Elevation at each node (m) null (horizontal)
setInclinationProfile(double[]) Inclination angles (rad) null

Simulation Control

Method Description Default
setMaxSimulationTime(double) Total simulation time (s) 3600
setCflNumber(double) CFL number (0.1-1.0) 0.5
setThermodynamicUpdateInterval(int) Flash update frequency 10
setUpdateThermodynamics(boolean) Enable/disable thermo updates true

Boundary Conditions

// Available boundary condition types
pipe.setInletBoundaryCondition(BoundaryCondition.CONSTANT_FLOW);
pipe.setOutletBoundaryCondition(BoundaryCondition.CONSTANT_PRESSURE);

// Set boundary values
pipe.setInletMassFlow(5.0);           // kg/s
pipe.setOutletPressure(30.0);         // bara
Type Description
CONSTANT_PRESSURE Fixed pressure boundary
CONSTANT_FLOW Fixed mass flow rate
CONSTANT_VELOCITY Fixed velocity
CLOSED No-flow wall

Output Results

Profile Data

// Spatial profiles at end of simulation
double[] pressure = pipe.getPressureProfile();      // Pa
double[] temperature = pipe.getTemperatureProfile(); // K
double[] liquidHoldup = pipe.getLiquidHoldupProfile(); // fraction
double[] gasVelocity = pipe.getGasVelocityProfile();   // m/s
double[] liquidVelocity = pipe.getLiquidVelocityProfile(); // m/s

Time History

// Pressure history at all locations
double[][] pressureHistory = pipe.getPressureHistory();
// pressureHistory[time_index][position_index]

Slug Statistics

SlugTracker slugTracker = pipe.getSlugTracker();

int activeSlugCount = slugTracker.getSlugCount();
int totalGenerated = slugTracker.getTotalSlugsGenerated();
double avgLength = slugTracker.getAverageSlugLength();
double frequency = slugTracker.getSlugFrequency();

// Detailed statistics
String stats = slugTracker.getStatisticsString();
System.out.println(stats);

Note: Both TransientPipe (drift-flux) and TwoFluidPipe (two-fluid) use the same SlugTracker and LiquidAccumulationTracker components, but may predict different slug frequencies due to their underlying holdup models. See the Two-Fluid Model documentation for a detailed comparison.

Accumulation Zones

LiquidAccumulationTracker tracker = pipe.getAccumulationTracker();

for (var zone : tracker.getAccumulationZones()) {
    System.out.println("Zone position: " + zone.getPosition() + " m");
    System.out.println("Accumulated volume: " + zone.getAccumulatedVolume() + " m³");
    System.out.println("Current holdup: " + zone.getCurrentHoldup());
    System.out.println("Is overflowing: " + zone.isOverflowing());
}

Advanced Usage

Custom Flow Regime Detection

PipeSection[] sections = pipe.getSections();
FlowRegimeDetector detector = new FlowRegimeDetector();

for (PipeSection section : sections) {
    FlowRegime regime = detector.detectFlowRegime(section);
    System.out.println("Position " + section.getPosition() + 
                       ": " + regime);
}

Drift-Flux Analysis

DriftFluxModel model = new DriftFluxModel();

for (PipeSection section : sections) {
    DriftFluxParameters params = model.calculateDriftFlux(section);

    System.out.println("C0 = " + params.C0);
    System.out.println("Drift velocity = " + params.driftVelocity + " m/s");
    System.out.println("Void fraction = " + params.voidFraction);
    System.out.println("Slip ratio = " + params.slipRatio);
}

Accessing Individual Sections

PipeSection[] sections = pipe.getSections();

for (int i = 0; i < sections.length; i++) {
    PipeSection s = sections[i];

    System.out.printf("Section %d (x=%.1f m):%n", i, s.getPosition());
    System.out.printf("  Pressure: %.2f bar%n", s.getPressure()/1e5);
    System.out.printf("  Temperature: %.1f K%n", s.getTemperature());
    System.out.printf("  Liquid holdup: %.3f%n", s.getLiquidHoldup());
    System.out.printf("  Gas velocity: %.2f m/s%n", s.getGasVelocity());
    System.out.printf("  Liquid velocity: %.2f m/s%n", s.getLiquidVelocity());
    System.out.printf("  Flow regime: %s%n", s.getFlowRegime());
    System.out.printf("  Is low point: %b%n", s.isLowPoint());
}

Integration with ProcessSystem

import neqsim.process.processmodel.ProcessSystem;

ProcessSystem process = new ProcessSystem();

// Add inlet stream
Stream inlet = new Stream("inlet", fluid);
inlet.setFlowRate(10, "kg/sec");
process.add(inlet);

// Add transient pipe
TransientPipe pipeline = new TransientPipe("MainPipeline", inlet);
pipeline.setLength(5000);
pipeline.setDiameter(0.4);
pipeline.setNumberOfSections(100);
pipeline.setMaxSimulationTime(600);
process.add(pipeline);

// Add downstream equipment
Stream outlet = pipeline.getOutletStream();
// ... add separators, compressors, etc.

process.run();

Performance Considerations

Spatial Resolution

Time Step

Thermodynamic Updates

Flow Regime Definitions

Regime Description Typical Conditions
SINGLE_PHASE_GAS Gas only α_L < 0.001
SINGLE_PHASE_LIQUID Liquid only α_G < 0.001
BUBBLE Discrete bubbles in liquid Low gas velocity, vertical
SLUG Alternating liquid slugs and gas bubbles Moderate velocities
STRATIFIED_SMOOTH Separated phases, smooth interface Low velocities, horizontal
STRATIFIED_WAVY Separated phases, wavy interface Moderate velocities, horizontal
ANNULAR Liquid film on wall, gas core High gas velocity
CHURN Chaotic, oscillating flow High velocities, vertical

Troubleshooting

Simulation Instability

Symptoms: NaN values, oscillations, crashes

Solutions:

  1. Reduce CFL number: pipe.setCflNumber(0.3)
  2. Increase number of sections
  3. Check for unrealistic boundary conditions
  4. Verify fluid properties are physical

Slow Performance

Solutions:

  1. Reduce number of sections
  2. Increase thermodynamic update interval
  3. Use simpler equation of state
  4. Reduce simulation time

No Slugs Detected

Possible causes:

  1. Flow is single-phase (check inlet conditions)
  2. Pipe too short for slug development
  3. Velocities outside slug regime
  4. No terrain variation (add elevation profile)

References

  1. Taitel, Y. and Dukler, A.E. (1976). "A Model for Predicting Flow Regime Transitions in Horizontal and Near Horizontal Gas-Liquid Flow." AIChE Journal, 22(1), 47-55.

  2. Barnea, D. (1987). "A Unified Model for Predicting Flow-Pattern Transitions for the Whole Range of Pipe Inclinations." Int. J. Multiphase Flow, 13(1), 1-12.

  3. Bendiksen, K.H. (1984). "An Experimental Investigation of the Motion of Long Bubbles in Inclined Tubes." Int. J. Multiphase Flow, 10(4), 467-483.

  4. Zuber, N. and Findlay, J.A. (1965). "Average Volumetric Concentration in Two-Phase Flow Systems." J. Heat Transfer, 87(4), 453-468.

  5. Harmathy, T.Z. (1960). "Velocity of Large Drops and Bubbles in Media of Infinite or Restricted Extent." AIChE Journal, 6(2), 281-288.

Comparison with Beggs and Brill Correlation

Overview

The TransientPipe model uses a different approach than empirical correlations like Beggs and Brill (1973). Understanding these differences helps in selecting the appropriate model for your application.

Model Comparison

Aspect TransientPipe Beggs and Brill
Approach Mechanistic drift-flux with AUSM+ scheme Empirical correlation from experiments
Basis Conservation equations + closure relations ~1500 experimental data points
Flow Regimes Taitel-Dukler, Barnea criteria Froude number based map
Transient Full transient capability Steady-state only
Terrain Section-by-section integration Overall correlation

Expected Differences

Comparison tests show significant differences between the models, which is expected given their fundamentally different approaches:

Flow Condition Typical Difference Explanation
Single-phase gas, horizontal 50-80% Different friction correlations
Multiphase horizontal 100-300% Different holdup/slip models
Uphill flow (+10°) 40-60% Hydrostatic term treatment
Downhill flow (-10°) 40-60% Liquid drainage models differ
High velocity gas 50-100% Compressibility effects

When to Use Each Model

Use TransientPipe when:

Use Beggs and Brill when:

Validation Approach

For critical applications, it is recommended to:

  1. Benchmark both models against field data or detailed CFD simulations
  2. Understand model assumptions - TransientPipe uses mechanistic closure relations that may need tuning for specific fluids
  3. Consider uncertainty bands - differences of 50-100% between models indicate the inherent uncertainty in multiphase flow predictions
  4. Use multiple models - consensus from different approaches increases confidence

Comparison Test Examples

NeqSim includes comparison tests in TransientPipeVsBeggsAndBrillsComparisonTest.java:

// Example: Comparing models for horizontal multiphase flow
SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.8);
fluid.addComponent("n-pentane", 0.2);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);

// Setup Beggs and Brill
Stream bbStream = new Stream("BB_inlet", fluid);
bbStream.setFlowRate(2.0, "kg/sec");
bbStream.run();

PipeBeggsAndBrills bb = new PipeBeggsAndBrills("BeggsAndBrill", bbStream);
bb.setDiameter(0.2);
bb.setLength(500);
bb.setAngle(0);
bb.run();
double dpBeggsBrill = bb.getPressureDrop();

// Setup TransientPipe
Stream tpStream = new Stream("TP_inlet", fluid.clone());
tpStream.setFlowRate(2.0, "kg/sec");
tpStream.run();

TransientPipe tp = new TransientPipe("TransientPipe", tpStream);
tp.setLength(500);
tp.setDiameter(0.2);
tp.setNumberOfSections(25);
tp.setMaxSimulationTime(60);
tp.run();
double[] pressures = tp.getPressureProfile();
double dpTransient = (pressures[0] - pressures[pressures.length - 1]) / 1e5;

System.out.println("Beggs & Brill: " + dpBeggsBrill + " bar");
System.out.println("TransientPipe: " + dpTransient + " bar");

References for Model Comparison

  1. Beggs, H.D. and Brill, J.P. (1973). "A Study of Two-Phase Flow in Inclined Pipes." Journal of Petroleum Technology, 25(5), 607-617.

  2. Ishii, M. and Hibiki, T. (2011). Thermo-Fluid Dynamics of Two-Phase Flow. 2nd ed. Springer.

See Also

Development Plan

Two-Phase Mass Transfer Pipeline Development Plan

Overview

This document outlines the development plan for enhancing the TwoPhasePipeFlowSystem to become a general-purpose two-phase mass and heat transfer pipeline simulation tool. The implementation is based on the non-equilibrium thermodynamics approach described in Solbraa (2002) and uses the Krishna-Standart film model for interphase mass transfer.

Current State

The TwoPhasePipeFlowSystem currently supports:

Development Tasks

1. Mass Transfer Models

1.1 Add Mass Transfer Model Selection

Priority: High

Allow users to select between different mass transfer models:

Model Description Best For
Krishna-Standart Film Multi-component diffusion with thermodynamic correction General purpose, current default
Penetration Theory Time-dependent diffusion into semi-infinite medium Short contact times
Surface Renewal Theory Statistical distribution of surface ages Turbulent interfaces

API Design:

public enum MassTransferModel {
    KRISHNA_STANDART_FILM,
    PENETRATION_THEORY,
    SURFACE_RENEWAL
}

pipe.setMassTransferModel(MassTransferModel.KRISHNA_STANDART_FILM);

1.2 Implement Mass Transfer Coefficient Correlations

Priority: High

Add flow pattern-specific Sherwood number correlations:

Flow Pattern Correlation Reference
Stratified Sh = f(Re, Sc, geometry) Solbraa (2002)
Annular Sh = f(Re_film, Sc, wave amplitude) Hewitt & Hall-Taylor
Slug Sh_bubble + Sh_slug weighted Fernandes et al.
Bubble Sh = 2 + 0.6 Re^0.5 Sc^0.33 Ranz-Marshall
Droplet Sh = 2 + 0.6 Re^0.5 Sc^0.33 Ranz-Marshall

1.3 Add Component Mass Transfer Tracking

Priority: Medium

Track mass transfer for each component along the pipe:

double[] methaneProfile = pipe.getMassTransferProfile("methane");
double waterMassBalance = pipe.getComponentMassBalance("water");

2. Interphase Area Models

2.1 Implement Interfacial Area Models

Priority: High

The interfacial area per unit volume (a) is critical for mass transfer calculations: $$\dot{n}_i = k_L \cdot a \cdot \Delta C_i$$

Flow Pattern Interfacial Area Model Key Parameters
Stratified Flat interface: $a = \frac{S_i}{A}$ where $S_i$ = interface chord length Liquid holdup, pipe diameter
Annular Film interface: $a = \frac{4}{D} \cdot \frac{1}{1-\sqrt{1-\alpha_L}}$ Film thickness, entrainment
Slug $a = a_{Taylor} \cdot f_{bubble} + a_{slug} \cdot (1-f_{bubble})$ Slug frequency, bubble length
Bubble $a = \frac{6\alpha_G}{d_{32}}$ (Sauter mean diameter) Bubble size distribution
Droplet $a = \frac{6\alpha_L}{d_{32}}$ from Weber number Droplet size, We number

API Design:

public enum InterfacialAreaModel {
    GEOMETRIC,           // Based on flow geometry
    EMPIRICAL_CORRELATION,  // Literature correlations
    USER_DEFINED         // Custom model
}

pipe.setInterfacialAreaModel(InterfacialAreaModel.GEOMETRIC);

Implementation Details:

For bubble flow, use Hinze theory for maximum stable bubble size: $$d_{max} = 0.725 \left(\frac{\sigma}{\rho_L}\right)^{0.6} \epsilon^{-0.4}$$

For droplet flow, use critical Weber number: $$d_{max} = \frac{We_{crit} \cdot \sigma}{\rho_G \cdot u_G^2}$$


3. Heat Transfer Models

3.1 Implement Wall Heat Transfer Models

Priority: High

Support different thermal boundary conditions:

Boundary Condition Description Use Case
Constant Wall Temperature $T_{wall} = T_{const}$ Isothermal pipes
Constant Heat Flux $q'' = q''_{const}$ Electric heating
Convective Boundary $q'' = U_{overall}(T_{ambient} - T_{fluid})$ Subsea pipelines

API Design:

public enum WallHeatTransferModel {
    CONSTANT_WALL_TEMPERATURE,
    CONSTANT_HEAT_FLUX,
    CONVECTIVE_BOUNDARY,
    ADIABATIC
}

pipe.setWallHeatTransferModel(WallHeatTransferModel.CONVECTIVE_BOUNDARY);
pipe.setOverallHeatTransferCoefficient(10.0); // W/(m²·K)

3.2 Add Heat Transfer Coefficient Correlations

Priority: Medium

Implement flow pattern-specific Nusselt number correlations:

Flow Pattern Correlation Notes
Stratified Separate gas/liquid Nu Geometry-dependent
Annular Nu = f(Re_film, Pr, roughness) Film heat transfer
Slug Weighted Nu_bubble + Nu_slug Time-averaged
Bubble Shah correlation Enhanced mixing
Droplet Dittus-Boelter for gas + droplet contribution Mist flow

3.3 Implement Energy Balance with Enthalpy

Priority: Medium

Use rigorous enthalpy-based energy balance:

$$\frac{\partial(\rho H)}{\partial t} + \nabla \cdot (\rho \mathbf{u} H) = -\nabla \cdot \mathbf{q} + \dot{Q}_{wall} + \dot{Q}_{interphase}$$

Include:


4. Flow Pattern Detection

4.1 Add Automatic Flow Pattern Detection

Priority: Medium

Implement flow pattern transition criteria:

Method Description Applicability
Baker Chart Empirical map based on G_L, G_G Horizontal pipes
Taitel-Dukler Mechanistic model Horizontal/inclined
Barnea Extended Taitel-Dukler All inclinations

Transition Criteria:

API Design:

pipe.enableAutomaticFlowPatternDetection(true);
pipe.setFlowPatternModel(FlowPatternModel.TAITEL_DUKLER);

5. Output and Results

5.1 Create Profile Output Methods

Priority: High

Add convenient methods for extracting simulation results:

// Get profiles along pipe
double[] temperatures = pipe.getTemperatureProfile();
double[] pressures = pipe.getPressureProfile(); // [bar]
double[] positions = pipe.getPositionProfile();

// Get composition profiles
double[][] gasComposition = pipe.getGasCompositionProfile();
double[][] liquidComposition = pipe.getLiquidCompositionProfile();

// Get flow properties
double[] gasVelocities = pipe.getVelocityProfile(0); // Gas phase
double[] liquidVelocities = pipe.getVelocityProfile(1); // Liquid phase
double[] voidFraction = pipe.getVoidFractionProfile();

// Export to CSV/JSON
pipe.exportResults("simulation_results.csv");

6. Advanced Features

6.1 Add Enhancement Factors for Reactive Systems

Priority: Low

Support chemical enhancement for reactive mass transfer:

$$N_A = E \cdot k_L \cdot (C_{A,i} - C_{A,bulk})$$

System Enhancement Factor Model
CO₂ + Amine Danckwerts surface renewal
H₂S removal Instantaneous reaction
SO₂ absorption Film theory with reaction

6.2 Pipe Insulation and Wall Heat Transfer Model

Priority: LowAlready Implemented

NeqSim already has a comprehensive pipe wall and insulation infrastructure in the geometrydefinitions.internalgeometry.wall and geometrydefinitions.surrounding packages.

Existing Infrastructure

MaterialLayer.java - Single layer with thermal properties:

MaterialLayer layer = new MaterialLayer(PipeMaterial.POLYURETHANE_FOAM, 0.05); // 50mm insulation
double k = layer.getThermalConductivity(); // W/(m·K)
double R = layer.getThermalResistance(innerRadius, outerRadius); // For cylindrical geometry

PipeMaterial.java - Enum with 30+ predefined materials:

PipeWall.java - Multi-layer cylindrical heat transfer:

PipeWall wall = new PipeWall(innerDiameter);
wall.addLayer(new MaterialLayer(PipeMaterial.CARBON_STEEL, 0.01));      // 10mm steel
wall.addLayer(new MaterialLayer(PipeMaterial.POLYURETHANE_FOAM, 0.05)); // 50mm insulation
wall.addLayer(new MaterialLayer(PipeMaterial.POLYPROPYLENE, 0.003));    // 3mm coating
double U_inner = wall.getOverallHeatTransferCoefficient(); // W/(m²·K) based on inner surface

PipeSurroundingEnvironment.java - Factory methods for external conditions:

// Subsea pipeline
PipeSurroundingEnvironment env = PipeSurroundingEnvironment.subseaPipe(seawaterTemp, depth);
double h_ext = env.getExternalHeatTransferCoefficient(); // ~500 W/(m²·K) for seawater

// Buried onshore
PipeSurroundingEnvironment env = PipeSurroundingEnvironment.buriedPipe(soilTemp, burialDepth, soilType);

// Exposed to air
PipeSurroundingEnvironment env = PipeSurroundingEnvironment.exposedToAir(airTemp, windSpeed);
Integration with TwoPhasePipeFlowSystem

The FlowSystem base class already supports:

// Set wall heat transfer coefficients per leg
pipe.setLegWallHeatTransferCoefficients(new double[] {50.0, 50.0}); // U values
pipe.setLegOuterHeatTransferCoefficients(new double[] {500.0, 500.0}); // h_ext values

For advanced wall modeling, use PipeWall to calculate overall U and pass to the flow system.


7. API Improvements

7.1 Create Simplified Builder API

Priority: Medium

Fluent builder pattern for easy setup:

TwoPhasePipeFlowSystem pipe = TwoPhasePipeFlowSystem.builder()
    .withFluid(thermoSystem)
    .withDiameter(0.1, "m")
    .withLength(1000, "m")
    .withNodes(100)
    .withFlowPattern("stratified")
    .withWallTemperature(278, "K")
    .enableNonEquilibriumMassTransfer()
    .enableNonEquilibriumHeatTransfer()
    .build();

pipe.solve();

8. Testing and Validation

8.1 Add Comprehensive Test Suite

Priority: High

Test Category Validation Data
Mass transfer coefficients Solbraa (2002) experimental data
Interfacial areas Azzopardi correlations
Heat transfer Gnielinski, Shah correlations
Pressure drop Lockhart-Martinelli, Friedel
Flow pattern transitions Taitel-Dukler maps

8.2 Write Documentation and Examples

Priority: Medium

Create Jupyter notebook examples:

  1. TEG Dehydration - Water removal from natural gas
  2. CO₂ Absorption - Amine scrubbing in pipelines
  3. Pipeline Cooling - Subsea pipeline temperature profiles
  4. Hydrate Prevention - MEG injection effectiveness
  5. Condensate Dropout - Retrograde condensation in pipelines

Implementation Priority

Phase 1: Core Functionality (High Priority)

  1. ✅ Basic non-equilibrium mass/heat transfer (completed)
  2. ✅ Interphase area models for all flow patterns (completed - InterfacialAreaCalculator with geometric models for stratified, annular, slug, bubble, droplet, churn)
  3. ✅ Mass transfer coefficient correlations (completed - MassTransferCoefficientCalculator with Dittus-Boelter, Ranz-Marshall)
  4. ✅ Profile output methods (completed - added getEnthalpyProfile, getTotalEnthalpyProfile, getHeatCapacityProfile, getFlowPatternProfile, getFlowPatternNameProfile)
  5. ✅ Comprehensive test suite (completed - 84+ tests for flow pattern detection, interfacial area, mass transfer, builder)

Phase 2: Enhanced Models (Medium Priority)

  1. ✅ Wall heat transfer models (completed - WallHeatTransferModel enum)
  2. ✅ Heat transfer coefficient correlations (completed - basic Dittus-Boelter for internal heat transfer)
  3. ✅ Automatic flow pattern detection (completed - FlowPatternDetector with Taitel-Dukler, Baker, Barnea, Beggs-Brill)
  4. ✅ Builder API (completed - TwoPhasePipeFlowSystemBuilder)
  5. ✅ Pipe wall/insulation model (already implemented in existing PipeWall, MaterialLayer, PipeMaterial, PipeSurroundingEnvironment classes)
  6. ✅ Energy balance improvements (completed - added Joule-Thomson effect, latent heat from phase change, proper wall heat transfer coefficients)

Phase 3: Advanced Features (Low Priority)

  1. [ ] Enhancement factors for reactive systems
  2. [ ] Mass transfer model selection (Krishna-Standart, Penetration Theory, Surface Renewal)
  3. ✅ Documentation and examples (completed - comprehensive documentation in docs/fluidmechanics/)

Known Limitations and Future Work

Some advanced test scenarios are currently disabled pending solver optimization:

Test Status Issue
testCompleteLiquidEvaporationIn1kmPipe Disabled Solver timeout - needs performance optimization
testTransientWaterDryingInGasPipeline Disabled Solver timeout - needs performance optimization
testSubseaGasOilPipelineWithElevationProfile Disabled Temperature calculation needs improvement
testGasWithCondensationAlongPipeline Disabled Phase transition solver needs optimization

These tests represent advanced use cases that require further solver development to handle:

The steady-state solver (type 2) calculates mass transfer fluxes correctly at each node, but accumulated composition changes downstream may require more iterations or transient simulation.


References

  1. Solbraa, E. (2002). "Equilibrium and Non-Equilibrium Thermodynamics of Natural Gas Processing." PhD Thesis, NTNU.
  2. Krishna, R., & Standart, G. L. (1976). "A multicomponent film model incorporating a general matrix method of solution to the Maxwell-Stefan equations." AIChE Journal, 22(2), 383-389.
  3. Taitel, Y., & Dukler, A. E. (1976). "A model for predicting flow regime transitions in horizontal and near horizontal gas-liquid flow." AIChE Journal, 22(1), 47-55.
  4. Hewitt, G. F., & Hall-Taylor, N. S. (1970). "Annular Two-Phase Flow." Pergamon Press.
  5. Barnea, D. (1987). "A unified model for predicting flow-pattern transitions for the whole range of pipe inclinations." International Journal of Multiphase Flow, 13(1), 1-12.

File Locations

Component File Path
Main class src/main/java/neqsim/fluidmechanics/flowsystem/twophaseflowsystem/twophasepipeflowsystem/TwoPhasePipeFlowSystem.java
Builder src/main/java/neqsim/fluidmechanics/flowsystem/twophaseflowsystem/twophasepipeflowsystem/TwoPhasePipeFlowSystemBuilder.java
Solver src/main/java/neqsim/fluidmechanics/flowsolver/twophaseflowsolver/twophasepipeflowsolver/TwoPhaseFixedStaggeredGridSolver.java
Flow nodes src/main/java/neqsim/fluidmechanics/flownode/twophasenode/twophasepipeflownode/
Flow pattern enum src/main/java/neqsim/fluidmechanics/flownode/FlowPattern.java
Flow pattern model src/main/java/neqsim/fluidmechanics/flownode/FlowPatternModel.java
Flow pattern detector src/main/java/neqsim/fluidmechanics/flownode/FlowPatternDetector.java
Wall heat transfer model src/main/java/neqsim/fluidmechanics/flownode/WallHeatTransferModel.java
Interfacial area model src/main/java/neqsim/fluidmechanics/flownode/InterfacialAreaModel.java
Interfacial area calculator src/main/java/neqsim/fluidmechanics/flownode/InterfacialAreaCalculator.java
Mass transfer calculator src/main/java/neqsim/fluidmechanics/flownode/MassTransferCoefficientCalculator.java
Fluid boundary src/main/java/neqsim/fluidmechanics/flownode/fluidboundary/
Tests src/test/java/neqsim/fluidmechanics/flowsystem/twophaseflowsystem/twophasepipeflowsystem/
Tests (flow node) src/test/java/neqsim/fluidmechanics/flownode/

Chapter 28: Transient Pipeline Simulation

Transient Simulation

Pipeline Transient Simulation in NeqSim

Overview

NeqSim supports dynamic (transient) simulation of pipelines using the PipeBeggsAndBrills class. This allows modeling of:

Steady-State vs Transient Modes

Steady-State Mode (Default)

In steady-state mode, calling run() calculates the equilibrium pressure profile along the pipe assuming constant inlet conditions:

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("pipe", feed);
pipe.setLength(1000);
pipe.setDiameter(0.2);
pipe.run();  // Steady-state calculation

// Two calculation modes:
// 1. Forward (default): Given flow rate → calculate outlet pressure
// 2. Reverse: Given outlet pressure → calculate flow rate
pipe.setOutletPressure(45.0);  // Switch to reverse mode
pipe.run();
double requiredFlow = pipe.getInletStream().getFlowRate("kg/hr");

Transient Mode

In transient mode, the pipe remembers its internal state between time steps, allowing simulation of dynamic behavior:

pipe.setCalculateSteadyState(false);  // Enable transient mode
for (int step = 0; step < 100; step++) {
    pipe.runTransient(1.0, id);  // 1 second time step
}

Physics Model

Governing Equations

The transient model solves simplified forms of the mass and momentum conservation equations using a finite-difference approach:

Mass Conservation: $$\frac{\partial \rho}{\partial t} + \frac{\partial (\rho v)}{\partial x} = 0$$

Momentum (simplified): $$\frac{\partial P}{\partial x} = -\frac{dP}{dx}_{friction} - \rho g \sin\theta$$

Numerical Method

The implementation uses a relaxation-based advection scheme:

  1. Pipe is divided into segments (nodes)
  2. Properties propagate from upstream to downstream
  3. Friction and hydrostatic losses are applied at each segment
  4. Relaxation factor based on transit time:

$$\alpha = \min\left(1, \frac{\Delta t}{\tau}\right)$$

Where $\tau = \Delta x / v$ is the segment transit time.

Wave Propagation

For a property $\phi$ (pressure, temperature, flow):

$$\phi_{i+1}^{n+1} = \phi_{i+1}^{n} + \alpha \cdot (\phi_i^{n+1} - \phi_{i+1}^{n}) - \Delta P_{losses}$$

This gives physically realistic propagation delays.

Usage

Basic Transient Setup

// Create and run steady-state first
SystemInterface gas = new SystemSrkEos(298.15, 50.0);
gas.addComponent("methane", 50000, "kg/hr");
gas.setMixingRule(2);

Stream feed = new Stream("feed", gas);
feed.run();

PipeBeggsAndBrills pipeline = new PipeBeggsAndBrills("pipe", feed);
pipeline.setLength(1000);
pipeline.setDiameter(0.2);
pipeline.setPipeWallRoughness(4.6e-5);
pipeline.setNumberOfIncrements(20);
pipeline.run();  // Initialize with steady-state

// Switch to transient mode
pipeline.setCalculateSteadyState(false);

// Run transient simulation
UUID id = UUID.randomUUID();
double dt = 1.0;  // 1 second time step

for (int step = 0; step < 100; step++) {
    pipeline.runTransient(dt, id);

    // Monitor outlet
    double outletPressure = pipeline.getOutletPressure();
    double outletFlow = pipeline.getOutletStream().getFlowRate("kg/hr");
}

Applying Disturbances

// After running for some time, apply inlet pressure change
SystemInterface newGas = new SystemSrkEos(298.15, 55.0);  // +5 bar
newGas.addComponent("methane", 50000, "kg/hr");
newGas.setMixingRule(2);

feed.setThermoSystem(newGas);
feed.run();

// Continue transient - disturbance will propagate
for (int step = 0; step < 200; step++) {
    pipeline.runTransient(dt, id);
    // ... monitor response
}

Integration with ProcessSystem

ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(valve);
process.add(pipeline);
process.add(separator);

// Initial steady-state
process.run();

// Configure for transient
valve.setCalculateSteadyState(false);
pipeline.setCalculateSteadyState(false);
process.setTimeStep(1.0);

// Run transient steps
for (int i = 0; i < 500; i++) {
    if (i == 50) {
        valve.setPercentValveOpening(80);  // Change valve at t=50s
    }
    process.runTransient();
}

Choke/Valve Closure Propagation

A common transient scenario is simulating the effect of closing a downstream valve or choke and observing how the pressure/flow disturbance propagates back through the pipeline.

Example: Downstream Valve Closure

// Setup: Source → Pipeline → Choke Valve → Separator
SystemInterface gas = new SystemSrkEos(298.15, 100.0);
gas.addComponent("methane", 1.0);
gas.setMixingRule("classic");
gas.setTotalFlowRate(50000, "kg/hr");

Stream source = new Stream("source", gas);
source.run();

// Pipeline
PipeBeggsAndBrills pipeline = new PipeBeggsAndBrills("pipeline", source);
pipeline.setLength(5000);        // 5 km
pipeline.setDiameter(0.2);       // 200 mm
pipeline.setNumberOfIncrements(50);
pipeline.run();

// Downstream choke valve
ThrottlingValve choke = new ThrottlingValve("choke", pipeline.getOutletStream());
choke.setOutletPressure(50.0);   // 50 bara downstream
choke.run();

// Build process
ProcessSystem process = new ProcessSystem();
process.add(source);
process.add(pipeline);
process.add(choke);
process.run();

System.out.println("Initial steady-state:");
System.out.println("  Choke inlet P: " + pipeline.getOutletPressure() + " bara");
System.out.println("  Choke opening: " + choke.getPercentValveOpening() + "%");
System.out.println("  Flow rate: " + source.getFlowRate("kg/hr") + " kg/hr");

// Switch to transient
pipeline.setCalculateSteadyState(false);
choke.setCalculateSteadyState(false);
process.setTimeStep(1.0);

UUID id = UUID.randomUUID();

// Run transient with valve closure event
for (int step = 0; step < 300; step++) {
    double t = step * 1.0;  // seconds

    // Gradual valve closure from t=50s to t=100s
    if (t >= 50 && t <= 100) {
        double closureFraction = (t - 50) / 50.0;  // 0 to 1
        double opening = 100.0 - closureFraction * 50.0;  // 100% → 50%
        choke.setPercentValveOpening(opening);
    }

    process.runTransient();

    // Monitor pressure wave propagation
    if (step % 10 == 0) {
        System.out.printf("t=%3.0fs: P_pipe_out=%.2f bara, Choke=%.0f%%, Flow=%.0f kg/hr%n",
            t, pipeline.getOutletPressure(), 
            choke.getPercentValveOpening(),
            choke.getOutletStream().getFlowRate("kg/hr"));
    }
}

Expected Behavior: Valve Closure

When a downstream valve closes:

  1. Immediate effect (t=0): Valve restriction increases, flow through valve decreases
  2. Pressure buildup: Pressure upstream of valve increases as flow backs up
  3. Wave propagation: Pressure increase travels upstream through pipeline
  4. New equilibrium: System reaches new steady-state with lower flow and higher upstream pressure
Time Pipeline Outlet Flow Rate Notes
0s 70 bara 50,000 kg/hr Initial steady-state
50s 70 bara 50,000 kg/hr Valve starts closing
75s 75 bara 40,000 kg/hr Pressure building
100s 82 bara 30,000 kg/hr Valve at 50% open
200s 85 bara 28,000 kg/hr New equilibrium

Example: Emergency Shutdown (ESD)

Simulate rapid valve closure for ESD scenario:

// Fast valve closure (slam shut in 5 seconds)
for (int step = 0; step < 500; step++) {
    double t = step * 0.1;  // 100 ms time step for fast transient

    // ESD triggered at t=10s
    if (t >= 10 && t <= 15) {
        double closureFraction = (t - 10) / 5.0;
        choke.setPercentValveOpening(100.0 * (1 - closureFraction));
    } else if (t > 15) {
        choke.setPercentValveOpening(0.0);  // Fully closed
    }

    process.runTransient();

    // Log high-frequency data for pressure surge analysis
    System.out.printf("%.1f, %.3f, %.1f%n", 
        t, pipeline.getOutletPressure(), 
        choke.getOutletStream().getFlowRate("kg/hr"));
}

Pressure Surge Considerations

For rapid valve closure (water hammer / pressure surge):

Closure Time Pressure Rise Model Accuracy
> 2×L/c Gradual Good
~ L/c Moderate surge Approximate
<< L/c Severe surge Not supported

Where L = pipe length, c = speed of sound (~400 m/s for gas, ~1200 m/s for liquid).

Note: The current transient model uses advection-based propagation (fluid velocity), not acoustic waves. For severe water hammer analysis, specialized transient software may be needed.

Water Hammer Limitations

What is Water Hammer?

Water hammer (hydraulic shock) occurs when a valve closes rapidly, causing pressure waves to travel at the speed of sound through the fluid. The pressure surge can be calculated using the Joukowsky equation:

$$\Delta P = \rho \cdot c \cdot \Delta v$$

Where:

Example Pressure Surge Calculation

For water at 10 m/s suddenly stopped:

Current Model Behavior vs Water Hammer

Aspect NeqSim Transient Model True Water Hammer
Wave speed Fluid velocity (1-20 m/s) Speed of sound (400-1400 m/s)
Pressure peak Gradual buildup Sharp spike
Wave reflection Not modeled Multiple reflections
Timing Minutes to equilibrate Milliseconds for surge

When Current Model is Adequate

The advection-based model is suitable for:

Slow valve operations (closure time > 2L/c)

Production rate changes - Gradual flow adjustments

Process upsets - Separator level changes, compressor trips

Quasi-steady analysis - New equilibrium after disturbance

When Water Hammer Analysis is Needed

Emergency shutdowns - Fast valve closure (<1 second)

Pump trips - Sudden flow stoppage

Check valve slam - Reverse flow closure

Pipe stress analysis - Peak pressure for mechanical design

Workaround: Estimate Surge Pressure

You can estimate the water hammer pressure surge separately:

// Calculate theoretical water hammer pressure rise
public static double joukowskyPressureSurge(double density, double soundSpeed, 
                                             double velocityChange) {
    return density * soundSpeed * velocityChange;  // Pa
}

// Example usage
double rho = 800;    // kg/m³ (oil)
double c = 1100;     // m/s (speed of sound in oil)
double dv = 5;       // m/s (velocity before closure)

double surgePressure = joukowskyPressureSurge(rho, c, dv);
System.out.println("Max surge: " + surgePressure/1e5 + " bar");
// Output: Max surge: 44.0 bar

// Add to steady-state operating pressure for peak
double operatingPressure = 50.0;  // bara
double peakPressure = operatingPressure + surgePressure/1e5;
System.out.println("Peak pressure: " + peakPressure + " bara");
// Output: Peak pressure: 94.0 bar

Speed of Sound Estimation

For gases, use NeqSim's thermodynamic properties:

SystemInterface gas = new SystemSrkEos(298.15, 50.0);
gas.addComponent("methane", 1.0);
gas.setMixingRule("classic");
gas.init(3);
gas.initPhysicalProperties();

double soundSpeed = gas.getSoundSpeed();  // m/s
System.out.println("Speed of sound: " + soundSpeed + " m/s");
// Typical: 400-450 m/s for natural gas

For detailed water hammer analysis, consider:

  1. Method of Characteristics (MOC) - Classical numerical method for transient pipe flow
  2. Specialized software: OLGA, PIPESIM, AFT Impulse, Synergi Pipeline Simulator
  3. CFD - For complex geometries or detailed surge vessel analysis

Future Enhancement

A proper water hammer model would require:

  1. Solving the full hyperbolic wave equations
  2. Method of Characteristics or finite volume scheme
  3. Acoustic wave speed (not fluid velocity)
  4. Wave reflection at boundaries
  5. Vapor cavity modeling for column separation

This is a potential future enhancement for NeqSim.

Choke Opening (Flow Increase)

The reverse scenario - opening a choke to increase flow:

// Start with choke partially closed
choke.setPercentValveOpening(30.0);
process.run();

// Switch to transient
pipeline.setCalculateSteadyState(false);
process.setTimeStep(1.0);

// Gradually open choke
for (int step = 0; step < 200; step++) {
    if (step >= 20 && step <= 70) {
        double opening = 30.0 + (step - 20) * 1.4;  // 30% → 100%
        choke.setPercentValveOpening(Math.min(opening, 100.0));
    }
    process.runTransient();
}

When opening a valve:

  1. Flow increases immediately at the valve
  2. Pressure drops upstream of valve (flow accelerating)
  3. Pressure drop propagates upstream
  4. System equilibrates at higher flow, lower upstream pressure

Time Step Selection

Guidelines

Pipe Length Velocity Transit Time Recommended Δt
100 m 10 m/s 10 s 0.5-2 s
1 km 10 m/s 100 s 1-5 s
10 km 10 m/s 1000 s 5-20 s

Stability Criteria

The time step should satisfy: $$\Delta t \leq \frac{\Delta x}{v}$$

Where $\Delta x = L / N_{increments}$

For fast transients (valve slam), use smaller time steps: $$\Delta t \leq \frac{\Delta x}{c}$$

Where $c$ is the speed of sound (~350-450 m/s for natural gas).

Propagation Timing

Expected Behavior

Based on validation tests:

Mechanism Propagation Speed Notes
Mass flow Fluid velocity ~10-20 m/s (gas)
Pressure wave ~0.4× transit time Model uses advection
Temperature Fluid velocity Advective transport

Example: 1000m Pipeline

For a 1000m pipe with 12.5 m/s gas velocity:

Accessing Transient Results

Profile Data

// Pressure profile along pipe
List<Double> pressures = pipeline.getPressureProfile();

// Temperature profile
List<Double> temperatures = pipeline.getTemperatureProfile();

// Pressure drop per segment
List<Double> dpProfile = pipeline.getPressureDropProfile();

// Get segment-specific values
int segment = 10;
double p = pipeline.getSegmentPressure(segment);
double T = pipeline.getSegmentTemperature(segment);
double holdup = pipeline.getSegmentLiquidHoldup(segment);

Outlet Stream

Stream outlet = pipeline.getOutletStream();
double outP = outlet.getPressure("bara");
double outT = outlet.getTemperature("C");
double outFlow = outlet.getFlowRate("kg/hr");

Physical Effects Included

1. Friction Losses

2. Hydrostatic Pressure

3. Mass Conservation

4. Density Updates

Limitations

  1. No acoustic effects: Pressure waves travel at fluid velocity, not speed of sound
  2. No liquid accumulation: Holdup is quasi-steady
  3. Simplified heat transfer: Optional, uses constant coefficient
  4. No phase change during transient: Composition remains constant

Best Practices

1. Initialize Properly

Always run steady-state first to establish baseline:

pipeline.run();  // Steady-state
pipeline.setCalculateSteadyState(false);  // Then switch

2. Use Sufficient Segments

More segments = better resolution of waves:

pipeline.setNumberOfIncrements(20);  // Minimum
pipeline.setNumberOfIncrements(50);  // Better for long pipes

3. Monitor Convergence

Check that outlet stabilizes after disturbances:

double prevP = 0;
for (int step = 0; step < 500; step++) {
    pipeline.runTransient(dt, id);
    double p = pipeline.getOutletPressure();
    if (Math.abs(p - prevP) < 0.001) {
        System.out.println("Converged at step " + step);
        break;
    }
    prevP = p;
}

4. Validate with Steady-State

Transient should converge to same result as steady-state:

double steadyDp = ...; // From steady-state run
double transientDp = ...; // After convergence
assertTrue(Math.abs(transientDp - steadyDp) / steadyDp < 0.15);

5. Transient Convergence Time

For a step change in inlet conditions, expect:

Example for 1000m pipe with 12.5 m/s velocity:

Troubleshooting

Problem: Pressure goes negative

Cause: Time step too large or flow rate too high Solution: Reduce time step or increase inlet pressure

Problem: No response at outlet

Cause: Not enough transient steps Solution: Run more steps (at least 2× transit time)

Problem: Oscillations

Cause: Time step too small relative to physics Solution: Increase time step or reduce number of segments

Future: Water Hammer Implementation

Water hammer simulation is now available in NeqSim through the WaterHammerPipe class, which uses the Method of Characteristics (MOC) to simulate fast pressure transients. The recommended approach for water hammer analysis is:

  1. Use WaterHammerPipe for fast transients - valve closures, pump trips, ESD events
  2. Leverage NeqSim thermodynamics - wave speed calculated from EOS
  3. Design for extensibility - supports reservoir, valve, and closed-end boundary conditions
  4. Validate against Joukowsky equation - built-in surge pressure calculation

The existing PipeBeggsAndBrills advection model remains valuable for slow transients, while WaterHammerPipe handles fast acoustic phenomena.

Quick Example

// Create water hammer pipe
WaterHammerPipe pipe = new WaterHammerPipe("pipe", feed);
pipe.setLength(1000);
pipe.setDiameter(0.2);
pipe.setNumberOfNodes(100);
pipe.setDownstreamBoundary(BoundaryType.VALVE);
pipe.run();

// Transient with valve closure
for (int step = 0; step < 1000; step++) {
    if (step == 100) pipe.setValveOpening(0.0);  // Slam shut
    pipe.runTransient(0.001, id);
}

// Get maximum pressure surge
double maxP = pipe.getMaxPressure("bar");

For detailed implementation, see Water Hammer Implementation Guide.

See Also

Model Recommendations

Pipeline Model Recommendations

Quick Reference Guide

Which Model Should I Use?

                         START
                           │
                           ▼
                 ┌───────────────────┐
                 │ Is flow single    │
                 │ phase (gas OR     │──── YES ───┐
                 │ liquid only)?     │            │
                 └────────┬──────────┘            │
                          │                       ▼
                         NO               ┌──────────────────┐
                          │               │ Is it primarily  │
                          ▼               │ gas?             │
                 ┌───────────────────┐    └────────┬─────────┘
                 │ PipeBeggsAndBrills│             │
                 │ (Multiphase)      │      YES ◄──┴──► NO
                 └───────────────────┘       │          │
                                             ▼          ▼
                                      AdiabaticPipe   PipeBeggsAndBrills
                                      (fast, accurate) (handles viscosity)

Detailed Recommendations

Scenario 1: High-Pressure Gas Transmission

Use: AdiabaticPipe

AdiabaticPipe pipe = new AdiabaticPipe("transmission", feed);
pipe.setLength(100000);    // 100 km
pipe.setDiameter(0.8);     // 32 inch
pipe.setInletPressure(80); // bara
pipe.run();

Why? Fastest computation, accounts for gas compressibility, well-validated.


Scenario 2: Oil Pipeline

Use: PipeBeggsAndBrills

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("oil", feed);
pipe.setLength(5000);
pipe.setDiameter(0.3);
pipe.setPipeWallRoughness(4.6e-5);
pipe.setNumberOfIncrements(20);
pipe.run();

Why? Handles liquid accurately, proper friction for viscous fluids.


Scenario 3: Well Tubing / Flowline

Use: PipeBeggsAndBrills

PipeBeggsAndBrills tubing = new PipeBeggsAndBrills("tubing", well);
tubing.setLength(3000);       // 3 km vertical
tubing.setElevation(2900);    // Almost vertical
tubing.setDiameter(0.0762);   // 3 inch
tubing.setNumberOfIncrements(30);
tubing.run();

System.out.println("Flow regime: " + tubing.getFlowRegime());
System.out.println("Liquid holdup: " + tubing.getSegmentLiquidHoldup(30));

Why? Only model that handles multiphase + elevation properly.


Scenario 4: Processing Plant Piping

Use: AdiabaticTwoPhasePipe (for quick estimates) or PipeBeggsAndBrills (for accuracy)

AdiabaticTwoPhasePipe pipe = new AdiabaticTwoPhasePipe("P-101", feed);
pipe.setLength(50);
pipe.setDiameter(0.1);
pipe.run();

Why? Fast, adequate accuracy for short pipes.


Scenario 5: Transient/Dynamic Simulation

Use: PipeBeggsAndBrills

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("dynamic", feed);
pipe.setLength(1000);
pipe.setDiameter(0.2);
pipe.setNumberOfIncrements(20);
pipe.run();

pipe.setCalculateSteadyState(false);
for (int t = 0; t < 300; t++) {
    pipe.runTransient(1.0, uuid);
}

Why? Only model with transient capability.


Scenario 6: Subsea Pipeline with Cooling

Use: PipeBeggsAndBrills with heat transfer

PipeBeggsAndBrills subsea = new PipeBeggsAndBrills("subsea", feed);
subsea.setLength(30000);
subsea.setDiameter(0.254);
subsea.setElevation(-500);
subsea.setRunAdiabatic(false);
subsea.setConstantSurfaceTemperature(277.15);
subsea.setHeatTransferCoefficient(5.0);
subsea.run();

Why? Handles elevation, multiphase, and heat transfer.


Performance Comparison

Model Relative Speed Memory Accuracy
AdiabaticPipe ★★★★★ Low Good for gas
AdiabaticTwoPhasePipe ★★★★☆ Low Moderate
PipeBeggsAndBrills ★★★☆☆ Medium Best

Common Mistakes

❌ Wrong: Using AdiabaticPipe for liquid

// Will give poor results for liquid
AdiabaticPipe pipe = new AdiabaticPipe("oil", liquidFeed);

✅ Correct: Use PipeBeggsAndBrills for liquid

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("oil", liquidFeed);

❌ Wrong: Ignoring elevation for wells

PipeBeggsAndBrills tubing = new PipeBeggsAndBrills("tubing", well);
tubing.setLength(3000);
// Missing: tubing.setElevation(3000);  // Vertical well!

✅ Correct: Set elevation

PipeBeggsAndBrills tubing = new PipeBeggsAndBrills("tubing", well);
tubing.setLength(3000);
tubing.setElevation(3000);  // Important!

❌ Wrong: Too few segments for long pipes

PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("long", feed);
pipe.setLength(50000);  // 50 km
pipe.setNumberOfIncrements(5);  // Only 5 segments for 50 km!

✅ Correct: Use adequate segments

pipe.setNumberOfIncrements(50);  // 1 km per segment

❌ Wrong: Roughness in wrong units

pipe.setPipeWallRoughness(0.046);  // This is 46 mm! Way too rough!

✅ Correct: Use meters

pipe.setPipeWallRoughness(4.6e-5);  // 0.046 mm = 4.6×10⁻⁵ m

Validation Checklist

Before trusting results, verify:

See Also

Water Hammer

Water Hammer Simulation in NeqSim

Overview

NeqSim provides water hammer (hydraulic transient) simulation through the WaterHammerPipe class. This model uses the Method of Characteristics (MOC) to simulate fast pressure transients caused by:

Unlike the advection-based transient model in PipeBeggsAndBrills, WaterHammerPipe propagates pressure waves at the speed of sound, enabling accurate simulation of pressure surges.

Quick Start

import neqsim.process.equipment.pipeline.WaterHammerPipe;
import neqsim.process.equipment.pipeline.WaterHammerPipe.BoundaryType;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

// Create fluid
SystemInterface water = new SystemSrkEos(298.15, 10.0);
water.addComponent("water", 1.0);
water.setMixingRule("classic");
water.setTotalFlowRate(100.0, "kg/hr");

Stream feed = new Stream("feed", water);
feed.run();

// Create water hammer pipe
WaterHammerPipe pipe = new WaterHammerPipe("pipeline", feed);
pipe.setLength(1000);              // 1 km
pipe.setDiameter(0.2);             // 200 mm
pipe.setNumberOfNodes(100);        // Grid resolution
pipe.setDownstreamBoundary(BoundaryType.VALVE);
pipe.run();                        // Initialize steady state

// Simulate valve closure
UUID id = UUID.randomUUID();
double dt = pipe.getMaxStableTimeStep();

for (int step = 0; step < 1000; step++) {
    double t = step * dt;

    // Close valve from t=0.1s to t=0.2s
    if (t >= 0.1 && t <= 0.2) {
        double tau = (t - 0.1) / 0.1;
        pipe.setValveOpening(1.0 - tau);  // 100% → 0%
    }

    pipe.runTransient(dt, id);
}

// Get maximum pressure surge
double maxPressure = pipe.getMaxPressure("bar");
System.out.println("Max surge pressure: " + maxPressure + " bar");

Physics Model

Method of Characteristics (MOC)

The MOC transforms the hyperbolic partial differential equations for 1D transient pipe flow into ordinary differential equations along characteristic lines.

Governing Equations:

Continuity: $$\frac{\partial H}{\partial t} + \frac{c^2}{gA}\frac{\partial Q}{\partial x} = 0$$

Momentum: $$\frac{\partial Q}{\partial t} + gA\frac{\partial H}{\partial x} + \frac{f}{2DA}Q|Q| = 0$$

Characteristic Lines:

Compatibility Equations:

Along C⁺: $H_P - H_A + B(Q_P - Q_A) + R \cdot Q_A|Q_A| = 0$

Along C⁻: $H_P - H_B - B(Q_P - Q_B) - R \cdot Q_B|Q_B| = 0$

Where:

Wave Speed Calculation

The wave speed includes pipe elasticity using the Korteweg-Joukowsky formula:

$$c = \frac{c_{fluid}}{\sqrt{1 + \frac{K \cdot D}{E \cdot e}}}$$

Where:

// Wave speed is automatically calculated, but can be overridden
pipe.setPipeElasticModulus(200e9);  // Steel
pipe.setWallThickness(0.01);        // 10 mm
double waveSpeed = pipe.getWaveSpeed();  // After run()

Joukowsky Pressure Surge

The theoretical pressure surge for instantaneous velocity change:

$$\Delta P = \rho \cdot c \cdot \Delta v$$

// Calculate theoretical surge
double surgePa = pipe.calcJoukowskyPressureSurge(velocityChange);
double surgeBar = pipe.calcJoukowskyPressureSurge(velocityChange, "bar");

Boundary Conditions

Available Types

Type Description Use Case
RESERVOIR Constant pressure head Upstream tank/reservoir
VALVE Variable opening (0-1) Downstream control valve
CLOSED_END No flow (Q=0) Dead end, closed valve
CONSTANT_FLOW Fixed flow rate Pump at constant speed

Setting Boundary Conditions

// Upstream: constant pressure reservoir
pipe.setUpstreamBoundary(BoundaryType.RESERVOIR);

// Downstream: valve that can be opened/closed
pipe.setDownstreamBoundary(BoundaryType.VALVE);
pipe.setValveOpening(1.0);  // Initially fully open

// During simulation, close the valve
pipe.setValveOpening(0.5);  // 50% open
pipe.setValveOpening(0.0);  // Fully closed

Time Step and Stability

Courant Condition

For numerical stability, the time step must satisfy:

$$\Delta t \leq \frac{\Delta x}{c}$$

Where Δx = length / (numberOfNodes - 1).

// Get maximum stable time step
double maxDt = pipe.getMaxStableTimeStep();

// Use smaller time step for safety
double dt = maxDt * 0.5;

Wave Round-Trip Time

The time for a pressure wave to travel the pipe length and back:

$$T_{round-trip} = \frac{2L}{c}$$

double roundTrip = pipe.getWaveRoundTripTime();
// For 1 km pipe with c=1000 m/s: roundTrip = 2 seconds

Output and Results

Pressure Profile

// Pressure along pipe (Pa)
double[] pressures = pipe.getPressureProfile();

// Pressure in bar
double[] pressuresBar = pipe.getPressureProfile("bar");

Velocity and Flow Profiles

double[] velocities = pipe.getVelocityProfile();  // m/s
double[] flows = pipe.getFlowProfile();           // m³/s
double[] heads = pipe.getHeadProfile();           // m

Pressure Envelopes

Track maximum and minimum pressures during simulation:

double[] maxEnvelope = pipe.getMaxPressureEnvelope();
double[] minEnvelope = pipe.getMinPressureEnvelope();

double overallMax = pipe.getMaxPressure("bar");
double overallMin = pipe.getMinPressure("bar");

// Reset envelopes (e.g., after reaching steady state)
pipe.resetEnvelopes();

Time History

List<Double> pressureHistory = pipe.getPressureHistory();  // At outlet
List<Double> timeHistory = pipe.getTimeHistory();
double currentTime = pipe.getCurrentTime();

Example: Emergency Shutdown

// Setup: 5 km oil pipeline
SystemInterface oil = new SystemSrkEos(298.15, 50.0);
oil.addComponent("nC10", 1.0);
oil.setMixingRule("classic");
oil.setTotalFlowRate(500000, "kg/hr");

Stream feed = new Stream("feed", oil);
feed.run();

WaterHammerPipe pipeline = new WaterHammerPipe("export pipeline", feed);
pipeline.setLength(5, "km");
pipeline.setDiameter(300, "mm");
pipeline.setNumberOfNodes(200);
pipeline.setDownstreamBoundary(BoundaryType.VALVE);
pipeline.run();

System.out.println("Wave speed: " + pipeline.getWaveSpeed() + " m/s");
System.out.println("Round-trip time: " + pipeline.getWaveRoundTripTime() + " s");

// Initial conditions
double initialPressure = pipeline.getMaxPressure("bar");
double velocity = pipeline.getVelocityProfile()[0];

// Simulate ESD - valve closes in 5 seconds
UUID id = UUID.randomUUID();
double dt = 0.01;  // 10 ms time step
double closureTime = 5.0;

for (double t = 0; t < 30; t += dt) {
    // Linear valve closure from t=0 to t=closureTime
    if (t <= closureTime) {
        pipeline.setValveOpening(1.0 - t / closureTime);
    }

    pipeline.runTransient(dt, id);

    if (t % 1.0 < dt) {
        System.out.printf("t=%.1fs: P_max=%.1f bar, valve=%.0f%%%n",
            t, pipeline.getMaxPressure("bar"), pipeline.getValveOpening() * 100);
    }
}

// Results
double maxSurge = pipeline.getMaxPressure("bar");
double minPressure = pipeline.getMinPressure("bar");

System.out.println("Initial pressure: " + initialPressure + " bar");
System.out.println("Maximum surge: " + maxSurge + " bar");
System.out.println("Minimum pressure: " + minPressure + " bar");
System.out.println("Surge increase: " + (maxSurge - initialPressure) + " bar");

// Compare with Joukowsky theoretical value
double joukowskySurge = pipeline.calcJoukowskyPressureSurge(velocity, "bar");
System.out.println("Joukowsky theoretical: " + joukowskySurge + " bar");

Example: Gas Pipeline

// Natural gas pipeline
SystemInterface gas = new SystemSrkEos(298.15, 70.0);
gas.addComponent("methane", 0.90);
gas.addComponent("ethane", 0.05);
gas.addComponent("propane", 0.03);
gas.addComponent("CO2", 0.02);
gas.setMixingRule("classic");
gas.setTotalFlowRate(1000000, "Sm3/day");

Stream gasFeed = new Stream("gas feed", gas);
gasFeed.run();

WaterHammerPipe gasPipe = new WaterHammerPipe("gas pipeline", gasFeed);
gasPipe.setLength(100, "km");
gasPipe.setDiameter(0.5);           // 500 mm
gasPipe.setNumberOfNodes(500);
gasPipe.setDownstreamBoundary(BoundaryType.VALVE);
gasPipe.run();

// Gas has lower wave speed (~400 m/s) and lower density
// So pressure surges are typically smaller than for liquids
System.out.println("Gas wave speed: " + gasPipe.getWaveSpeed() + " m/s");

API Reference

Constructor

WaterHammerPipe(String name)
WaterHammerPipe(String name, StreamInterface inStream)

Geometry Methods

Method Description
setLength(double meters) Pipe length in meters
setLength(double value, String unit) Length with unit ("m", "km", "ft")
setDiameter(double meters) Inside diameter in meters
setDiameter(double value, String unit) Diameter with unit ("m", "mm", "in")
setWallThickness(double meters) Pipe wall thickness
setRoughness(double meters) Surface roughness
setElevationChange(double meters) Outlet - inlet elevation
setNumberOfNodes(int nodes) Computational grid size

Material Properties

Method Description
setPipeElasticModulus(double Pa) Pipe material modulus (default: 200 GPa)
setWaveSpeed(double m_per_s) Override calculated wave speed

Boundary Conditions

Method Description
setUpstreamBoundary(BoundaryType) Set upstream BC type
setDownstreamBoundary(BoundaryType) Set downstream BC type
setValveOpening(double fraction) Valve opening 0-1
getValveOpening() Current valve opening

Simulation Control

Method Description
run(UUID id) Initialize steady state
runTransient(double dt, UUID id) Run one time step
getMaxStableTimeStep() Get Courant-limited time step
setCourantNumber(double cn) Set Courant number (default: 1.0)
reset() Reset to initial state
resetEnvelopes() Reset min/max tracking

Results

Method Description
getPressureProfile() Pressure array (Pa)
getPressureProfile(String unit) Pressure in unit ("bar", "psi")
getVelocityProfile() Velocity array (m/s)
getFlowProfile() Flow rate array (m³/s)
getHeadProfile() Piezometric head (m)
getMaxPressureEnvelope() Max pressure at each node
getMinPressureEnvelope() Min pressure at each node
getMaxPressure(String unit) Overall maximum pressure
getMinPressure(String unit) Overall minimum pressure
getPressureHistory() Outlet pressure vs time
getTimeHistory() Time values
getCurrentTime() Current simulation time

Calculations

Method Description
calcJoukowskyPressureSurge(double dv) Theoretical surge (Pa)
calcJoukowskyPressureSurge(double dv, String unit) Surge in unit
calcEffectiveWaveSpeed() Korteweg wave speed
getWaveSpeed() Current wave speed (m/s)
getWaveRoundTripTime() 2L/c in seconds

Comparison with PipeBeggsAndBrills

Aspect PipeBeggsAndBrills WaterHammerPipe
Wave speed Fluid velocity (~10-20 m/s) Speed of sound (400-1500 m/s)
Time scale Minutes to hours Milliseconds to seconds
Use case Slow transients, process upsets Fast transients, valve slam
Physics Advection Acoustic waves
Two-phase Full correlation Single-phase (liquid or gas)
Heat transfer Included Not included

When to use which:

Limitations

Current implementation limitations:

  1. Single-phase only - liquid or gas, no two-phase
  2. No heat transfer - isothermal assumption
  3. No column separation - vapor cavity modeling not included
  4. Simple friction - quasi-steady friction model
  5. No pipe networks - single pipe only

References

  1. Wylie, E.B. & Streeter, V.L. (1993). Fluid Transients in Systems. Prentice Hall.
  2. Chaudhry, M.H. (2014). Applied Hydraulic Transients. Springer.
  3. Ghidaoui, M.S. et al. (2005). "A Review of Water Hammer Theory and Practice". Applied Mechanics Reviews.

See Also

Chapter 29: Safety Overview

Safety Overview

Safety Systems Documentation

Documentation for safety-related features and systems in NeqSim.


Overview

This folder contains guides for implementing safety systems in process simulations, including Emergency Shutdown (ESD), High Integrity Pressure Protection Systems (HIPPS), blowdown analysis, and alarm management.


Documentation Index

Emergency Shutdown (ESD)

Document Description
ESD_BLOWDOWN_SYSTEM.md Complete ESD and blowdown system guide
PRESSURE_MONITORING_ESD.md Pressure monitoring for ESD

HIPPS (High Integrity Pressure Protection)

Document Description
HIPPS_SUMMARY.md HIPPS overview and summary
hipps_implementation.md HIPPS implementation details
hipps_safety_logic.md HIPPS safety logic programming

Safety Architecture

Document Description
INTEGRATED_SAFETY_SYSTEMS.md Integrated safety systems overview
layered_safety_architecture.md Layered safety architecture (defense in depth)
sis_logic_implementation.md Safety Instrumented Systems (SIS) logic
integration_safety_chain_tests.md Safety chain integration testing
SAFETY_SIMULATION_ROADMAP.md Safety simulation development roadmap

Fire and Thermal Protection

Document Description
fire_blowdown_capabilities.md Fire case blowdown simulation
fire_heat_transfer_enhancements.md Fire heat transfer modeling

Relief Systems

Document Description
psv_dynamic_sizing_example.md Pressure Safety Valve dynamic sizing
rupture_disk_dynamic_behavior.md Rupture disk dynamic behavior

Alarms

Document Description
alarm_system_guide.md Alarm system implementation guide
alarm_triggered_logic_example.md Alarm-triggered logic examples

Safety Roadmap

NeqSim Safety Simulation Roadmap

A comprehensive analysis of existing safety capabilities and a realistic implementation plan for enhancing NeqSim's safety simulation features.


Executive Summary

NeqSim already has substantial safety infrastructure that covers approximately 90-95% of the proposed roadmap.

Recently Implemented (2024):

Remaining Gaps:

  1. Dynamic PSV back-pressure modeling
  2. Reaction force calculations per API 520 Annex D
  3. Two-phase relief sizing (API 520 Section 4.6)

1. Current State Analysis

✅ 3.1 Native Safety Scenario Framework — LARGELY EXISTS

Feature Status Implementation
SafetyScenario API ✅ Exists ProcessSafetyScenario with Builder pattern
Initiating events ✅ Exists Blocked outlets, utility loss, controller overrides
Custom manipulators ✅ Exists Lambda-based equipment manipulation
Scenario execution ✅ Exists ProcessSafetyAnalyzer.analyzeScenario()
Load case definition ✅ Exists ProcessSafetyLoadCase
Result repository ✅ Exists ProcessSafetyResultRepository

Existing Code Location: neqsim.process.safety.*

// Current API
ProcessSafetyScenario scenario = ProcessSafetyScenario.builder()
    .name("Compressor blowdown")
    .blockedOutlet("V-101")
    .utilityLoss("Cooling-Water")
    .controllerSetPointOverride("PC-101", 50.0)
    .customManipulator("SEP-001", eq -> eq.setRegulatorOutSignal(0.0))
    .build();

ProcessSafetyAnalyzer analyzer = new ProcessSafetyAnalyzer(processSystem);
ProcessSafetyLoadCase result = analyzer.analyzeScenario(scenario);

Implemented Additions:

Remaining Gaps:


✅ 3.2 Dynamic Depressurization & Blowdown — FULLY IMPLEMENTED

Feature Status Implementation
Time-dependent pressure ✅ Complete VesselDepressurization.runTransient()
Time-dependent temperature ✅ Complete Energy balance, adiabatic, isothermal modes
Phase split evolution ✅ Complete Two-phase support with separate wall temps
Choked vs non-choked flow ✅ Complete Critical pressure ratio calculation
Real-gas speed of sound ✅ Complete NeqSim thermodynamics
MDMT prediction ✅ Complete getMinimumWallTemperatureReached()
Blowdown duration ✅ Complete getTimeToReachPressure()
Two-phase formation timing ✅ Complete hasLiquidRainout()
Valve/piping temperature ✅ Complete TransientWallHeatTransfer
Fire case (API 521) ✅ Complete setFireCase(), heat flux models
Valve dynamics ✅ Complete setValveOpeningTime()
Hydrate risk ✅ Complete getHydrateFormationTemperature(), hasHydrateRisk()
CO2 freezing risk ✅ Complete getCO2FreezingTemperature(), hasCO2FreezingRisk()
Export to CSV/JSON ✅ Complete exportResultsToCSV(), exportResultsToJSON()

Existing Code Location:

// Current API
VesselDepressurization vessel = new VesselDepressurization("Tank", feed);
vessel.setVolume(10.0);
vessel.setOrificeDiameter(0.03);
vessel.setCalculationType(CalculationType.ENERGY_BALANCE);
vessel.setFireCase(true, 100.0); // 100 kW/m² fire

vessel.runTransient(dt, uuid);

// Flow assurance
Map<String, String> risks = vessel.assessFlowAssuranceRisks();

Status: COMPLETE


⚠️ 3.3 Relief and Vent System Modeling — PARTIALLY EXISTS

Feature Status Implementation
SafetyValve class ✅ Exists SafetyValve extends ThrottlingValve
SafetyReliefValve ✅ Exists SafetyReliefValve with lift dynamics
Set pressure / blowdown ✅ Exists setPressureSpec(), setBlowdownPressure()
API 520 sizing ✅ Exists ReliefValveSizing.calculateRequiredArea()
API 521 fire heat input ✅ Exists calculateAPI521HeatInput()
Relieving scenarios ✅ Exists RelievingScenario class
Choked flow models ✅ Exists Critical flow calculations
Balanced-bellows support ✅ Exists isBalancedBellows parameter
Rupture disk combination ✅ Exists hasRuptureDisk parameter
Back pressure effects ⚠️ Partial Static calculation, needs dynamic
Reaction forces ❌ Missing Not implemented
Flare connection ⚠️ Partial Flare class exists, limited integration

Existing Code Location:

Gaps to Address:


✅ 3.4 Leak & Rupture Source Term Generator — IMPLEMENTED

Feature Status Implementation
Leak model class ✅ Complete LeakModel with Builder pattern
Hole diameter specification ✅ Complete holeDiameter(double, String unit)
Mass flow vs time ✅ Complete calculateSourceTerm() returns time series
Gas/liquid split ✅ Complete Vapor fraction tracked at each timestep
Jet momentum ✅ Complete calculateJetMomentum()
Release temperature ✅ Complete Temperature tracked with isentropic expansion
Droplet size estimate ✅ Complete SMD via modified Weber number correlation
Export to PHAST/FLACS/KFX/OpenFOAM ✅ Complete All export methods implemented

Implementation Location: neqsim.process.safety.release.*

// Example usage
LeakModel leak = LeakModel.builder()
    .fluid(system)
    .holeDiameter(25.0, "mm")
    .vesselVolume(10.0)
    .orientation(ReleaseOrientation.HORIZONTAL)
    .scenarioName("HP Separator Leak")
    .build();

SourceTermResult result = leak.calculateSourceTerm(600.0, 1.0); // 10 min, 1s step
result.exportToPHAST("leak_phast.csv");
result.exportToFLACS("leak_flacs.csv");
result.exportToKFX("leak_kfx.csv");
result.exportToOpenFOAM("/path/to/openfoam/case");

Original API design (now implemented):

// New package: neqsim.process.safety.release

public class LeakModel {
    private double holeDiameter;  // m
    private String location;
    private LeakOrientation orientation; // HORIZONTAL, VERTICAL_UP, VERTICAL_DOWN

    public SourceTermResult calculateSourceTerm(SystemInterface system);
}

public class SourceTermResult {
    private double[] time;           // s
    private double[] massFlowRate;   // kg/s
    private double[] temperature;    // K
    private double[] vaporFraction;  // mol/mol
    private double[] jetMomentum;    // N
    private double[] liquidDropletSize; // m (SMD)

    // Export methods
    public void exportToPHAST(String filename);
    public void exportToFLACS(String filename);
    public void exportToKFX(String filename);
    public void exportToOpenFOAM(String directory);
}

✅ 3.5 Probabilistic & Risk Integration — IMPLEMENTED

Feature Status Implementation
Monte Carlo analysis ✅ Complete RiskModel.runMonteCarloAnalysis(iterations)
Failure frequencies ✅ Complete RiskEvent with frequency and error factors
Conditional probabilities ✅ Complete RiskEvent with parent events and conditional probability
Event tree logic ✅ Complete RiskEvent.parentEvent() for event tree branching
Sensitivity analysis ✅ Complete RiskModel.runSensitivityAnalysis() with tornado diagrams
Risk quantification ✅ Complete RiskResult with category frequencies and F-N curves
Consequence categories ✅ Complete ConsequenceCategory enum (NEGLIGIBLE to CATASTROPHIC)
Export to CSV/JSON ✅ Complete RiskResult.exportToCSV(), exportToJSON()

Implementation Location: neqsim.process.safety.risk.*

// Example usage
RiskModel model = new RiskModel("HP Separator Study");
model.setRandomSeed(42);

// Add initiating events with frequencies (per year)
model.addInitiatingEvent("Small Leak", 1e-3, ConsequenceCategory.MINOR);
model.addInitiatingEvent("Medium Leak", 1e-4, ConsequenceCategory.MODERATE);
model.addInitiatingEvent("Large Rupture", 1e-5, ConsequenceCategory.MAJOR);

// Or use builder pattern for event trees
RiskEvent fireEvent = RiskEvent.builder()
    .name("Fire on Leak")
    .parentEvent(leakEvent)
    .conditionalProbability(0.1)
    .consequenceCategory(ConsequenceCategory.MAJOR)
    .build();
model.addEvent(fireEvent);

// Run Monte Carlo analysis
RiskResult result = model.runMonteCarloAnalysis(10000);
result.exportToCSV("risk_results.csv");

// Run sensitivity analysis (tornado diagram)
SensitivityResult sensitivity = model.runSensitivityAnalysis(0.1, 10.0);
sensitivity.exportToCSV("sensitivity.csv");

✅ 3.6 Safety Envelopes & Operating Limits — IMPLEMENTED

Feature Status Implementation
Hydrate envelope ✅ Complete SafetyEnvelopeCalculator.calculateHydrateEnvelope()
Wax appearance ✅ Complete SafetyEnvelopeCalculator.calculateWaxEnvelope()
MDMT/Brittle fracture ✅ Complete SafetyEnvelopeCalculator.calculateMDMTEnvelope()
CO2 solid formation ✅ Complete SafetyEnvelopeCalculator.calculateCO2FreezingEnvelope()
Phase envelope ✅ Complete SafetyEnvelopeCalculator.calculatePhaseEnvelope()
Temperature interpolation ✅ Complete SafetyEnvelope.getTemperatureAtPressure()
Operating point check ✅ Complete SafetyEnvelope.isOperatingPointSafe()
Safety margin calc ✅ Complete SafetyEnvelope.calculateMarginToLimit()
Export to CSV/JSON ✅ Complete SafetyEnvelope.exportToCSV(), exportToJSON()
Export to PI/Seeq ✅ Complete SafetyEnvelope.exportToPIFormat(), exportToSeeq()

Implementation Location: neqsim.process.safety.envelope.*

// Example usage
SystemInterface naturalGas = new SystemSrkEos(300.0, 50.0);
naturalGas.addComponent("methane", 0.85);
naturalGas.addComponent("ethane", 0.10);
naturalGas.addComponent("propane", 0.05);
naturalGas.setMixingRule("classic");

SafetyEnvelopeCalculator calc = new SafetyEnvelopeCalculator(naturalGas);

// Calculate safety envelopes
SafetyEnvelope hydrateEnv = calc.calculateHydrateEnvelope(1.0, 100.0, 20);
SafetyEnvelope co2Env = calc.calculateCO2FreezingEnvelope(10.0, 100.0, 10);
SafetyEnvelope mdmtEnv = calc.calculateMDMTEnvelope(1.0, 100.0, 300.0, 10);

// Check operating point safety
boolean safe = hydrateEnv.isOperatingPointSafe(50.0, 280.0);
double margin = hydrateEnv.calculateMarginToLimit(50.0, 280.0);

// Export for DCS/historian integration
hydrateEnv.exportToCSV("hydrate_envelope.csv");
hydrateEnv.exportToPIFormat("hydrate_pi.csv");
hydrateEnv.exportToSeeq("hydrate_seeq.json");

// Calculate all envelopes at once
SafetyEnvelope[] allEnvelopes = calc.calculateAllEnvelopes(1.0, 100.0, 20);

2. Implementation Priority Matrix

Priority Feature Effort Impact Status
1 Leak/Release Source Term (3.4) High Critical ❌ Not started
2 Safety Envelope Calculator (3.6) Medium High ❌ Not started
3 Risk Model Framework (3.5) High High ❌ Not started
4 PSV Back Pressure Dynamics (3.3) Medium Medium ⚠️ Partial
5 Initiating Event Enum (3.1) Low Medium ❌ Not started
6 Reaction Force Calculation (3.3) Low Low ❌ Not started

Phase 1: Complete the Release Model (Priority 1)

Timeline: 2-3 weeks

Create neqsim.process.safety.release package with:

  1. LeakModel - Hole/rupture specification
  2. SourceTermResult - Time-series release data
  3. ReleaseExporter - PHAST/FLACS/KFX/OpenFOAM export
  4. Integration with VesselDepressurization for inventory tracking

Phase 2: Safety Envelope Calculator (Priority 2)

Timeline: 1-2 weeks

Create neqsim.process.safety.envelope package with:

  1. SafetyEnvelopeCalculator - Compute P-T envelopes
  2. SafetyEnvelope - Data container with export
  3. Integration with existing hydrate/CO2/MDMT calculations

Phase 3: Risk Framework (Priority 3)

Timeline: 3-4 weeks

Create neqsim.process.safety.risk package with:

  1. RiskModel - Event tree / fault tree basics
  2. RiskEvent - Frequencies and probabilities
  3. MonteCarloRiskAnalysis - Uncertainty propagation
  4. Integration with ProcessSafetyScenario

Phase 4: Enhanced PSV Modeling (Priority 4)

Timeline: 1-2 weeks

Enhance existing SafetyValve with:

  1. Dynamic back pressure during relief
  2. Two-phase relief flow
  3. Reaction force calculation

4. Existing Infrastructure Summary

neqsim.process.safety/
├── ProcessSafetyScenario.java      ✅ Scenario definition
├── ProcessSafetyAnalyzer.java      ✅ Scenario execution
├── ProcessSafetyLoadCase.java      ✅ Results container
├── ProcessSafetyResultRepository.java  ✅ Results storage
└── ProcessSafetyAnalysisSummary.java   ✅ Summary report

neqsim.process.logic/
├── sis/
│   ├── SafetyInstrumentedFunction.java  ✅ SIF with voting
│   ├── Detector.java                    ✅ Fire/gas detectors
│   └── VotingLogic.java                 ✅ 1oo1, 2oo3, etc.
├── hipps/                               ✅ HIPPS logic
├── esd/                                 ✅ ESD sequences
├── shutdown/                            ✅ Shutdown logic
└── voting/                              ✅ Voting patterns

neqsim.process.equipment.valve/
├── SafetyValve.java                ✅ PSV with hysteresis
├── SafetyReliefValve.java          ✅ Relief valve dynamics
└── RelievingScenario.java          ✅ Scenario definitions

neqsim.process.util.fire/
├── ReliefValveSizing.java          ✅ API 520/521 sizing
├── FireHeatLoadCalculator.java     ✅ API 521 heat input
├── VesselRuptureCalculator.java    ✅ Von Mises stress
├── SeparatorFireExposure.java      ✅ Fire case wrapper
├── TransientWallHeatTransfer.java  ✅ Wall temperature
└── VesselHeatTransferCalculator.java  ✅ Heat transfer

neqsim.process.equipment.tank/
└── VesselDepressurization.java     ✅ Full blowdown (~3000 lines)
    ├── 5 calculation types
    ├── Two-phase support
    ├── Fire case (API 521)
    ├── Valve dynamics
    ├── Hydrate/CO2 risk
    ├── MDMT monitoring
    ├── Flare integration
    └── CSV/JSON export

neqsim.process.equipment.flare/
└── Flare.java                      ✅ Flare equipment

5. Quick Wins (Can Implement Now)

5.1 Add InitiatingEvent Enum

public enum InitiatingEvent {
    ESD("Emergency Shutdown"),
    PSV_LIFT("Pressure Safety Valve Lift"),
    RUPTURE("Vessel/Pipe Rupture"),
    LEAK_SMALL("Small Leak (< 10mm)"),
    LEAK_MEDIUM("Medium Leak (10-50mm)"),
    LEAK_LARGE("Large Leak (> 50mm)"),
    BLOCKED_OUTLET("Blocked Outlet"),
    UTILITY_LOSS("Loss of Utility"),
    FIRE_EXPOSURE("Fire Exposure"),
    RUNAWAY_REACTION("Runaway Reaction");

    private final String description;
    // ...
}

5.2 Add BoundaryConditions Class

public class BoundaryConditions implements Serializable {
    private double ambientTemperature = 288.15; // K
    private double windSpeed = 5.0;             // m/s
    private double relativeHumidity = 0.6;      // fraction
    private double solarRadiation = 0.0;        // W/m²

    // Builder pattern...
}

5.3 Hydrate Envelope Method (Add to ThermodynamicOperations)

public double[][] calculateHydrateEnvelope(double pMin, double pMax, int points) {
    double[][] envelope = new double[2][points];
    double pStep = (pMax - pMin) / (points - 1);

    for (int i = 0; i < points; i++) {
        double p = pMin + i * pStep;
        system.setPressure(p);
        hydrateFormationTemperature();
        envelope[0][i] = p;
        envelope[1][i] = system.getTemperature();
    }
    return envelope;
}

6. Conclusion

NeqSim's safety simulation capabilities are more mature than initially apparent. The main gaps are:

  1. Source Term Generation — Critical for QRA, needs new package
  2. Safety Envelopes — Straightforward extension of existing calculations
  3. Risk Framework — Foundational work needed for probabilistic analysis

The dynamic blowdown module (3.2) is fully complete with the VesselDepressurization class, including all requested features plus hydrate/CO2 risk assessment.


References

Layered Architecture

Layered Safety System Architecture

Overview

NeqSim now implements a comprehensive defense-in-depth safety architecture with multiple independent protection layers. This document describes how HIPPS, fire/gas detection, and ESD systems work together to provide robust safety protection.

Safety Layer Hierarchy

The Onion Model

Safety protection follows the "onion model" with multiple layers:

┌─────────────────────────────────────────────────┐
│  1. Process Control System (PCS)                │  ← Normal operation
│     • Pressure controllers: 80-85% MAOP         │
│     • Temperature controllers                    │
│     • Level controllers                          │
└──────────────────┬──────────────────────────────┘
                   │ If PCS fails ↓
┌─────────────────────────────────────────────────┐
│  2. Basic Process Control Alarms (BPCS)         │  ← Operator intervention
│     • High pressure alarm: 90% MAOP             │
│     • High temperature alarm                     │
│     • Manual operator actions                    │
└──────────────────┬──────────────────────────────┘
                   │ If operator fails ↓
┌─────────────────────────────────────────────────┐
│  3. HIPPS (High Integrity Pressure Protection)  │  ← First SIS layer
│     • Activation: 90-95% MAOP                   │  
│     • SIL: 2 or 3                               │
│     • Response: <2 seconds                      │
│     • Action: Close isolation valve             │
└──────────────────┬──────────────────────────────┘
                   │ If HIPPS fails ↓
┌─────────────────────────────────────────────────┐
│  4. Fire & Gas Detection SIS                    │  ← Hazard detection
│     • Fire detectors: 2oo3 voting               │
│     • Gas detectors: 2oo3 voting                │
│     • SIL: 2 or 3                               │
│     • Action: Activate ESD                      │
└──────────────────┬──────────────────────────────┘
                   │ If hazard detected ↓
┌─────────────────────────────────────────────────┐
│  5. ESD (Emergency Shutdown)                    │  ← Emergency response
│     • Activation: 98% MAOP or hazard            │
│     • SIL: 1 or 2                               │
│     • Response: 2-10 seconds                    │
│     • Action: Full/partial shutdown             │
└──────────────────┬──────────────────────────────┘
                   │ If ESD fails ↓
┌─────────────────────────────────────────────────┐
│  6. Pressure Relief (PSV/Rupture Disk)          │  ← Passive protection
│     • Activation: 100-110% MAOP                 │
│     • Mechanical device (fail-safe)             │
│     • Action: Vent to flare/atmosphere          │
└─────────────────────────────────────────────────┘

Implemented Safety Systems

1. HIPPS (High Integrity Pressure Protection System)

Purpose: Prevent overpressure before PSV activation

Key Features:

When It Activates:

Normal: 50 bara → Upset: 96 bara → HIPPS trips at 95 bara
Result: Isolation valve closes, prevents PSV lifting

Code Example:

HIPPSLogic hipps = new HIPPSLogic("HIPPS-101", VotingLogic.TWO_OUT_OF_THREE);
hipps.addPressureSensor(pt1);
hipps.addPressureSensor(pt2);
hipps.addPressureSensor(pt3);
hipps.setIsolationValve(isolationValve);
hipps.linkToEscalationLogic(esdLogic, 5.0);

Benefits:


2. Fire & Gas Detection SIS

Purpose: Detect hazardous conditions and initiate safe shutdown

Key Features:

When It Activates:

Fire: 2 of 3 detectors above 60°C → Fire SIF trips → ESD activated
Gas: 2 of 3 detectors above 25% LEL → Gas SIF trips → ESD activated

Code Example:

SafetyInstrumentedFunction fireSIF = 
    new SafetyInstrumentedFunction("Fire Detection", VotingLogic.TWO_OUT_OF_THREE);
fireSIF.addDetector(fireDetector1);
fireSIF.addDetector(fireDetector2);
fireSIF.addDetector(fireDetector3);
fireSIF.linkToLogic(esdLogic);

Benefits:


3. ESD (Emergency Shutdown)

Purpose: Emergency shutdown of process facilities

Key Features:

ESD Levels:

ESD Level 1: Partial shutdown (specific area)
ESD Level 2: Full shutdown (entire facility)
ESD Level 3: Total evacuation + shutdown

Code Example:

ESDLogic esdLogic = new ESDLogic("ESD Level 1");
esdLogic.addAction(new TripValveAction(esdValve), 0.0);      // Immediate
esdLogic.addAction(new ActivateBlowdownAction(bdValve), 0.5); // After 0.5s
esdLogic.addAction(new SetSplitterAction(splitter, [...]), 0.5); // After 1.0s

Benefits:


Integration Examples

Example 1: HIPPS with ESD Escalation

Scenario: Pressure protection with backup

// Create HIPPS (first line of defense)
HIPPSLogic hipps = new HIPPSLogic("HIPPS-101", VotingLogic.TWO_OUT_OF_THREE);
hipps.addPressureSensor(pt1); // 95 bara setpoint
hipps.addPressureSensor(pt2);
hipps.addPressureSensor(pt3);
hipps.setIsolationValve(hippsValve);

// Create ESD (backup)
ESDLogic esd = new ESDLogic("ESD Level 1");
esd.addAction(new TripValveAction(esdValve), 0.0);

// Link HIPPS to escalate to ESD after 5 seconds if pressure remains high
hipps.linkToEscalationLogic(esd, 5.0);

// Simulation
hipps.update(pressure, pressure, pressure);
hipps.execute(timeStep);

// Check status
if (hipps.isTripped() && !hipps.hasEscalated()) {
    System.out.println("HIPPS controlling pressure");
} else if (hipps.hasEscalated()) {
    System.out.println("HIPPS failed - ESD activated");
}

Flow:

t=0s:   Pressure rises to 96 bara
t=0s:   HIPPS trips (2/3 sensors above 95 bara)
t=0s:   Isolation valve closes rapidly
t=0-5s: HIPPS monitors pressure
t=5s:   If pressure still high → Escalate to ESD
t=5s:   ESD valve trips → Full shutdown

Example 2: Fire Detection Activating ESD

Scenario: Fire detected in process area

// Create fire detection SIF
SafetyInstrumentedFunction fireSIF = 
    new SafetyInstrumentedFunction("Fire Detection", VotingLogic.TWO_OUT_OF_THREE);
fireSIF.addDetector(new Detector("FD-101", DetectorType.FIRE, AlarmLevel.HIGH, 60.0, "°C"));
fireSIF.addDetector(new Detector("FD-102", DetectorType.FIRE, AlarmLevel.HIGH, 60.0, "°C"));
fireSIF.addDetector(new Detector("FD-103", DetectorType.FIRE, AlarmLevel.HIGH, 60.0, "°C"));

// Create ESD logic
ESDLogic esd = new ESDLogic("Fire ESD");
esd.addAction(new TripValveAction(esdValve), 0.0);
esd.addAction(new ActivateBlowdownAction(blowdownValve), 0.5);

// Link fire SIF to ESD
fireSIF.linkToLogic(esd);

// Simulation
fireSIF.update(temp1, temp2, temp3);

// Check status
if (fireSIF.isTripped()) {
    System.out.println("Fire detected - ESD activated");
}

Flow:

t=0s:  Temperatures: FD-101=55°C, FD-102=65°C, FD-103=70°C
t=0s:  Fire SIF evaluates: 2/3 detectors above 60°C → TRIP
t=0s:  Fire SIF activates linked ESD logic
t=0s:  ESD action 1: Trip ESD valve
t=0.5s: ESD action 2: Activate blowdown

Example 3: Complete Layered System

Scenario: All safety layers configured

// Layer 1: HIPPS for pressure protection
HIPPSLogic hipps = new HIPPSLogic("HIPPS-101", VotingLogic.TWO_OUT_OF_THREE);
hipps.addPressureSensor(pt1); // 95 bara
hipps.addPressureSensor(pt2);
hipps.addPressureSensor(pt3);
hipps.setIsolationValve(hippsValve);

// Layer 2: Fire detection
SafetyInstrumentedFunction fireSIF = 
    new SafetyInstrumentedFunction("Fire SIF", VotingLogic.TWO_OUT_OF_THREE);
fireSIF.addDetector(fd1); // 60°C
fireSIF.addDetector(fd2);
fireSIF.addDetector(fd3);

// Layer 3: Gas detection
SafetyInstrumentedFunction gasSIF = 
    new SafetyInstrumentedFunction("Gas SIF", VotingLogic.TWO_OUT_OF_THREE);
gasSIF.addDetector(gd1); // 25% LEL
gasSIF.addDetector(gd2);
gasSIF.addDetector(gd3);

// Layer 4: ESD (final layer)
ESDLogic esd = new ESDLogic("ESD Level 1");
esd.addAction(new TripValveAction(esdValve), 0.0);
esd.addAction(new ActivateBlowdownAction(blowdownValve), 0.5);

// Integrate layers
hipps.linkToEscalationLogic(esd, 5.0);
fireSIF.linkToLogic(esd);
gasSIF.linkToLogic(esd);

// Simulation
hipps.update(pressure, pressure, pressure);
fireSIF.update(temp1, temp2, temp3);
gasSIF.update(gas1, gas2, gas3);
hipps.execute(timeStep);

// Any layer can trigger ESD
if (hipps.hasEscalated() || fireSIF.isTripped() || gasSIF.isTripped()) {
    System.out.println("ESD activated from safety layer");
}

Activation Matrix

Condition HIPPS Fire SIF Gas SIF ESD Result
Normal operation All systems idle
Pressure 96 bara HIPPS isolates, ESD standby
Fire detected Fire SIF triggers ESD
Gas leak Gas SIF triggers ESD
HIPPS fails HIPPS escalates to ESD
Fire + Gas Both SIFs trigger ESD
All hazards Multiple layers activated

Configuration Best Practices

Setpoint Selection

Process Control: 80-85% MAOP (PID controller setpoint)
BPCS High Alarm: 90% MAOP (operator warning)
HIPPS Activation: 95% MAOP (first SIS layer)
ESD Activation:   98% MAOP (backup SIS layer)
PSV Set Pressure: 100% MAOP (passive protection)

Example for 100 bara MAOP:

Voting Logic Selection

Application Criticality Availability Need Recommended Voting
HIPPS High High 2oo3 (SIL 3)
Fire Detection High Medium 2oo3 (SIL 2/3)
Gas Detection High Medium 2oo3 (SIL 2/3)
ESD Medium Medium 1oo2 or 2oo3 (SIL 1/2)

Response Time Targets

System Target Response Typical
HIPPS <2 seconds 1-2 seconds
Fire Detection <5 seconds 2-5 seconds
Gas Detection <10 seconds 5-10 seconds
ESD <10 seconds 5-10 seconds

Maintenance and Testing

Proof Testing Schedule

Component Test Type Frequency Bypass Allowed
Pressure transmitters Calibration Annual Yes (1 at a time)
Fire detectors Functional test 6 months Yes (1 at a time)
Gas detectors Calibration 6 months Yes (1 at a time)
HIPPS valve Partial stroke Quarterly No
HIPPS valve Full stroke Annual Yes (with backup)
ESD valve Partial stroke Quarterly No
ESD valve Full stroke Annual Yes (with backup)

Bypass Management

// Bypass detector for maintenance (max 1 at a time)
Detector pt1 = hipps.getPressureSensor(0);
pt1.setBypass(true);

// Check bypass status
for (Detector sensor : hipps.getPressureSensors()) {
    if (sensor.isBypassed()) {
        System.out.println("WARNING: " + sensor.getName() + " bypassed");
    }
}

// Verify bypass constraint
if (bypassCount > maxAllowed) {
    System.out.println("ERROR: Too many sensors bypassed");
}

Standards Compliance

IEC 61511 (Process Industry SIS)

All implemented safety systems comply with IEC 61511:

IEC 61508 (Functional Safety)

ISA-84 / ANSI/ISA-84.00.01


Benefits of Layered Approach

Safety Benefits

  1. Defense in Depth: Multiple independent barriers
  2. Redundancy: Backup if primary layer fails
  3. High Reliability: 2oo3 voting reduces false trips and dangerous failures
  4. Fail-Safe Design: All systems fail to safe state

Operational Benefits

  1. Reduced Flaring: HIPPS prevents PSV lifting
  2. Faster Recovery: HIPPS trips are easier to reset than full ESD
  3. Maintenance Flexibility: Bypass capability without compromising safety
  4. Production Continuity: Lower spurious trip rate

Compliance Benefits

  1. Standards Adherence: IEC 61511, IEC 61508, ISA-84
  2. SIL Achievement: Meets SIL 2/3 requirements
  3. Audit Trail: Complete activation and reset history
  4. Documentation: Comprehensive test and maintenance records

Future Enhancements

Planned Features

  1. PFD Calculation: Automatic probability of failure on demand
  2. Proof Test Tracking: Integrated test scheduling and reporting
  3. Performance Dashboards: Real-time safety system KPIs
  4. Alarm Management: Integration with process alarms
  5. Event Logging: Comprehensive audit trail

Advanced Capabilities


See Also

Process Safety

Safety Systems Package

Documentation for safety systems modeling in NeqSim.

Table of Contents


Overview

Location: neqsim.process.equipment.safety, neqsim.process.safety

NeqSim provides equipment and logic for modeling process safety systems:


Safety Equipment

Pressure Safety Valve (PSV)

import neqsim.process.equipment.valve.SafetyValve;

SafetyValve psv = new SafetyValve("PSV-100", vessel);
psv.setOpeningPressure(95.0, "barg");  // Set pressure
psv.setFullOpenPressure(100.0, "barg"); // Overpressure
psv.setBlowdownPressure(85.0, "barg");  // Reseating pressure

Rupture Disk

import neqsim.process.equipment.valve.RuptureDisk;

RuptureDisk disk = new RuptureDisk("RD-100", vessel);
disk.setBurstPressure(110.0, "barg");
disk.setDiameter(150.0, "mm");

Emergency Shutdown (ESD)

ESD Logic

import neqsim.process.safety.ESDController;

ESDController esd = new ESDController("ESD-1");

// Add trip conditions
esd.addHighPressureTrip(separator, 100.0, "barg");
esd.addLowPressureTrip(separator, 5.0, "barg");
esd.addHighLevelTrip(separator, 0.9);
esd.addLowLevelTrip(separator, 0.1);

// Add shutdown actions
esd.addShutdownValve(inletValve);
esd.addShutdownValve(outletValve);

ESD Levels

Level Description Actions
ESD-0 Total shutdown Full plant shutdown
ESD-1 Process shutdown Process area isolation
ESD-2 Unit shutdown Single unit isolation
ESD-3 Equipment shutdown Single equipment stop

Blowdown Systems

Depressuring Calculation

import neqsim.process.equipment.valve.BlowdownValve;

BlowdownValve blowdown = new BlowdownValve("BDV-100", vessel);
blowdown.setDownstreamPressure(1.0, "barg");  // Flare pressure
blowdown.setOrificeSize(100.0, "mm");

// Run depressuring transient
for (double t = 0; t < 900; t += 1.0) {
    blowdown.runTransient();

    double P = vessel.getPressure("barg");
    double T = vessel.getTemperature("C");

    if (P < 7.0) {  // 15 minute rule target
        System.out.println("Reached target at " + t + " seconds");
        break;
    }
}

Fire Case Calculation

// Calculate heat input from fire
double wettedArea = 50.0;  // m²
double Q = 43200 * Math.pow(wettedArea, 0.82);  // API 521 formula

vessel.setHeatInput(Q, "W");

Pressure Safety Valves

PSV Sizing

// Calculate required relief rate
double reliefRate = psv.getReliefRate("kg/hr");

// API 520 sizing
double area = psv.getRequiredOrificeArea("mm2");
String orifice = psv.getAPIOrificeLetter();

System.out.println("Required area: " + area + " mm²");
System.out.println("API orifice: " + orifice);

Multiple Relief Scenarios

Scenario Description
Blocked outlet Outlet valve closed
Fire case External fire exposure
Tube rupture Heat exchanger tube failure
Power failure Loss of cooling/control
Thermal relief Liquid expansion

HIPPS

High Integrity Pressure Protection System.

import neqsim.process.safety.HIPPS;

HIPPS hipps = new HIPPS("HIPPS-1");

// Add sensors (2oo3 voting)
hipps.addPressureSensor(pt1);
hipps.addPressureSensor(pt2);
hipps.addPressureSensor(pt3);

// Set trip point
hipps.setTripPressure(95.0, "barg");

// Add final elements
hipps.addIsolationValve(sdv1);
hipps.addIsolationValve(sdv2);

// Set voting logic
hipps.setVotingLogic("2oo3");  // 2 out of 3

Example: Complete Safety System

ProcessSystem process = new ProcessSystem();

// Process equipment
Stream feed = new Stream("Feed", feedFluid);
process.add(feed);

Separator separator = new Separator("V-100", feed);
separator.setVolume(10.0, "m3");
process.add(separator);

// PSV on separator
SafetyValve psv = new SafetyValve("PSV-100", separator);
psv.setOpeningPressure(100.0, "barg");
psv.setDischargeStream(flareStream);
process.add(psv);

// Blowdown valve
BlowdownValve bdv = new BlowdownValve("BDV-100", separator);
bdv.setDownstreamPressure(1.0, "barg");
process.add(bdv);

// ESD controller
ESDController esd = new ESDController("ESD");
esd.addHighPressureTrip(separator, 95.0, "barg");
esd.addShutdownAction(() -> {
    inletValve.close();
    bdv.open();
});
process.add(esd);

// Run simulation
process.run();

// Simulate fire scenario
separator.setHeatInput(500.0, "kW");

for (double t = 0; t < 3600; t += 1.0) {
    process.runTransient();

    // Check ESD status
    if (esd.isTripped()) {
        System.out.println("ESD activated at " + t + " s");
    }
}

Chapter 30: Alarm Systems

Alarm System Guide

NeqSim Alarm System Configuration Guide

Overview

The NeqSim alarm system provides a comprehensive framework for monitoring process variables and managing alarm states throughout the lifecycle of process operations. This guide demonstrates how to configure and handle alarms in a consistent and easy way.

Key Components

1. AlarmConfig (Builder Pattern)

Configure alarms using the fluent builder pattern:

AlarmConfig pressureAlarmConfig = AlarmConfig.builder()
    .lowLowLimit(10.0)        // LOLO alarm threshold
    .lowLimit(20.0)           // LO alarm threshold
    .highLimit(80.0)          // HI alarm threshold
    .highHighLimit(90.0)      // HIHI alarm threshold
    .deadband(2.0)            // Deadband to prevent chattering
    .delay(3.0)               // Time delay before activation (seconds)
    .unit("bara")             // Engineering unit
    .build();

2. Alarm Levels

Four standard alarm levels aligned with ISA-18.2:

3. ProcessAlarmManager

Centralized coordinator for all process alarms:

ProcessAlarmManager alarmManager = new ProcessAlarmManager();

// Register measurement devices
alarmManager.register(pressureTransmitter);
alarmManager.register(temperatureTransmitter);
alarmManager.register(flowTransmitter);

// Evaluate alarms during simulation
double measuredValue = transmitter.getMeasuredValue();
List<AlarmEvent> events = alarmManager.evaluateMeasurement(
    transmitter, measuredValue, dt, currentTime);

// Acknowledge all active alarms
List<AlarmEvent> ackEvents = alarmManager.acknowledgeAll(currentTime);

// Get active alarms
List<AlarmStatusSnapshot> activeAlarms = alarmManager.getActiveAlarms();

// Get complete alarm history
List<AlarmEvent> history = alarmManager.getHistory();

Alarm Configuration Examples

Pressure Alarms

PressureTransmitter pt = new PressureTransmitter("PT-101", stream);

AlarmConfig pressureAlarms = AlarmConfig.builder()
    .highLimit(55.0)          // HI: 55 bara
    .highHighLimit(58.0)      // HIHI: 58 bara
    .deadband(1.0)            // 1 bara deadband
    .delay(2.0)               // 2 second delay
    .unit("bara")
    .build();

pt.setAlarmConfig(pressureAlarms);
alarmManager.register(pt);

Temperature Alarms

TemperatureTransmitter tt = new TemperatureTransmitter("TT-101", stream);

AlarmConfig tempAlarms = AlarmConfig.builder()
    .highLimit(45.0)          // HI: 45°C
    .highHighLimit(60.0)      // HIHI: 60°C
    .deadband(2.0)            // 2°C deadband
    .delay(5.0)               // 5 second delay (slower response)
    .unit("C")
    .build();

tt.setAlarmConfig(tempAlarms);
alarmManager.register(tt);

Flow Alarms (with Low Limits)

FlowTransmitter ft = new FlowTransmitter("FT-201", stream);

AlarmConfig flowAlarms = AlarmConfig.builder()
    .lowLowLimit(100.0)       // LOLO: 100 kg/hr
    .lowLimit(500.0)          // LO: 500 kg/hr
    .highLimit(20000.0)       // HI: 20000 kg/hr
    .deadband(50.0)           // 50 kg/hr deadband
    .delay(3.0)               // 3 second delay
    .unit("kg/hr")
    .build();

ft.setAlarmConfig(flowAlarms);
alarmManager.register(ft);

Level Alarms (Full Range)

LevelTransmitter lt = new LevelTransmitter("LT-101", separator);

AlarmConfig levelAlarms = AlarmConfig.builder()
    .lowLowLimit(15.0)        // LOLO: 15%
    .lowLimit(30.0)           // LO: 30%
    .highLimit(75.0)          // HI: 75%
    .highHighLimit(90.0)      // HIHI: 90%
    .deadband(2.0)            // 2% deadband
    .delay(4.0)               // 4 second delay
    .unit("%")
    .build();

lt.setAlarmConfig(levelAlarms);
alarmManager.register(lt);

Alarm Event Handling

Event Types

Three types of alarm events:

  1. ACTIVATED: Alarm becomes active
  2. CLEARED: Alarm returns to normal
  3. ACKNOWLEDGED: Operator acknowledges the alarm

Processing Alarm Events

List<AlarmEvent> events = alarmManager.evaluateMeasurement(
    transmitter, measuredValue, dt, time);

for (AlarmEvent event : events) {
    switch (event.getType()) {
        case ACTIVATED:
            System.out.println("⚠ ALARM: " + event.getSource() + 
                              " " + event.getLevel() + 
                              " at " + event.getValue());
            // Trigger operator notification
            // Log to SCADA system
            break;

        case CLEARED:
            System.out.println("✓ CLEARED: " + event.getSource() + 
                              " " + event.getLevel());
            // Log clearance
            break;

        case ACKNOWLEDGED:
            System.out.println("✋ ACKNOWLEDGED: " + event.getSource());
            // Update alarm display
            break;
    }
}

Alarm Status Monitoring

Get Active Alarms

List<AlarmStatusSnapshot> activeAlarms = alarmManager.getActiveAlarms();

System.out.println("Active Alarms: " + activeAlarms.size());
for (AlarmStatusSnapshot alarm : activeAlarms) {
    String status = alarm.isAcknowledged() ? "[ACK]" : "[NEW]";
    System.out.println(status + " " + 
                      alarm.getLevel() + " - " + 
                      alarm.getSource() + ": " + 
                      alarm.getValue());
}

Alarm History

List<AlarmEvent> history = alarmManager.getHistory();

// Count events by type
long activations = history.stream()
    .filter(e -> e.getType() == AlarmEventType.ACTIVATED)
    .count();

long clearances = history.stream()
    .filter(e -> e.getType() == AlarmEventType.CLEARED)
    .count();

long acknowledged = history.stream()
    .filter(e -> e.getType() == AlarmEventType.ACKNOWLEDGED)
    .count();

Integration with Safety Logic

Alarm-Triggered ESD

Alarms can be integrated with ESD logic to trigger automatic actions:

// Monitor pressure alarms
List<AlarmEvent> events = alarmManager.evaluateMeasurement(
    pressureTransmitter, pressure, dt, time);

// Check for HIHI alarm activation
for (AlarmEvent event : events) {
    if (event.getType() == AlarmEventType.ACTIVATED && 
        event.getLevel() == AlarmLevel.HIHI) {
        // Trigger ESD logic
        esdLogic.activate();
        System.out.println("ESD triggered by HIHI pressure alarm");
    }
}

Best Practices

1. Alarm Configuration

2. Alarm Priorities

3. Alarm Management

4. Safety Integration

Example Application

See ProcessLogicWithAlarmsExample.java for a complete demonstration showing:

Architecture Diagram

┌─────────────────────────────────────────────────────────────┐
│                 ProcessAlarmManager                         │
│  - Centralized alarm coordination                           │
│  - Alarm history tracking                                   │
│  - Active alarm monitoring                                  │
└────────────────────┬────────────────────────────────────────┘
                     │
        ┌────────────┼────────────┬────────────┐
        │            │            │            │
┌───────▼──────┐ ┌──▼──────┐ ┌──▼──────┐ ┌──▼──────┐
│ Pressure TX  │ │ Temp TX │ │ Flow TX │ │Level TX │
├──────────────┤ ├─────────┤ ├─────────┤ ├─────────┤
│ AlarmConfig  │ │AlarmCfg │ │AlarmCfg │ │AlarmCfg │
│ - HI: 55     │ │- HI: 45 │ │- LO:500 │ │- HI: 75 │
│ - HIHI: 58   │ │-HIHI:60 │ │-LOLO:100│ │-HIHI:90 │
│ - Deadband:1 │ │-Dband:2 │ │-HI:20000│ │- LO: 30 │
│ - Delay: 2s  │ │-Delay:5s│ │-Dband:50│ │-LOLO:15 │
└──────────────┘ └─────────┘ └─────────┘ └─────────┘
        │            │            │            │
        └────────────┴────────────┴────────────┘
                     │
        ┌────────────▼────────────────┐
        │     AlarmEvent Stream       │
        │  - ACTIVATED                │
        │  - CLEARED                  │
        │  - ACKNOWLEDGED             │
        └────────────┬────────────────┘
                     │
        ┌────────────▼────────────────┐
        │  Integration Points         │
        │  - ESD Logic Triggers       │
        │  - SCADA Display            │
        │  - Historian Logging        │
        │  - Operator Notifications   │
        └─────────────────────────────┘

Summary

The NeqSim alarm system provides:

Consistent Configuration: Builder pattern for all alarm types
Flexible Limits: Support for LOLO, LO, HI, HIHI levels
Smart Behavior: Deadband and delay to prevent nuisance alarms
Centralized Management: ProcessAlarmManager for system-wide coordination
Event Tracking: Complete lifecycle from activation to acknowledgement
Safety Integration: Seamless connection to ESD and control logic

This framework enables reliable process monitoring with minimal code complexity while maintaining industrial alarm management best practices.

Alarm Logic Example

Alarm-Triggered Process Logic Integration Example

Overview

ProcessLogicAlarmIntegratedExample.java demonstrates a complete, production-ready integration of the NeqSim alarm system with process control and safety logic. This example shows how alarms can trigger automatic control actions, safety responses, and emergency shutdown sequences in a layered protection architecture.

Key Features

1. Layered Safety Architecture with Alarm Integration

The example implements a comprehensive 5-layer safety system:

Layer 1 (Alarms - SIL-0):
  ├─ HI/LO Alarms → Operator notification and manual intervention
  │
Layer 2 (Alarms + Control - SIL-1):
  ├─ HIHI/LOLO Alarms → Automatic control responses (valve throttling, etc.)
  │
Layer 3 (HIPPS - SIL-2):
  ├─ Independent fast-acting pressure protection
  ├─ 2oo3 voting logic on pressure transmitters
  ├─ Triggered by PT-HIPPS HIHI alarms (59 bara)
  │
Layer 4 (ESD - SIL-2):
  ├─ Emergency shutdown system
  ├─ Triggered by PT-ESD-001 HIHI alarm (60 bara) or manual button
  ├─ Full isolation and blowdown sequence
  │
Layer 5 (PSV - Mechanical):
  └─ Pressure safety valve (65 bara set pressure)

2. Multi-Variable Alarm Monitoring

Pressure Monitoring (PT-101)

AlarmConfig pressureAlarmConfig = AlarmConfig.builder()
    .highLimit(53.0)          // HI: Operator notification
    .highHighLimit(56.0)      // HIHI: Auto throttle valve
    .deadband(0.5)
    .delay(1.0)
    .unit("bara")
    .build();

Alarm Actions:

Temperature Monitoring (TT-101)

AlarmConfig temperatureAlarmConfig = AlarmConfig.builder()
    .highLimit(40.0)          // HI: Operator notification
    .highHighLimit(55.0)      // HIHI: Trigger cooling
    .deadband(2.0)
    .delay(3.0)
    .unit("C")
    .build();

Alarm Actions:

Flow Monitoring (FT-201)

AlarmConfig flowAlarmConfig = AlarmConfig.builder()
    .lowLimit(100.0)          // LO: Operator notification
    .lowLowLimit(50.0)        // LOLO: Trigger shutdown
    .highLimit(2000.0)        // HI: High flow warning
    .deadband(10.0)
    .delay(5.0)
    .unit("m3/hr")
    .build();

Alarm Actions:

Level Monitoring (LT-101)

AlarmConfig levelAlarmConfig = AlarmConfig.builder()
    .lowLowLimit(20.0)        // LOLO: Emergency shutdown
    .lowLimit(30.0)           // LO: Operator notification
    .highLimit(70.0)          // HI: High level warning
    .highHighLimit(85.0)      // HIHI: Critical high level
    .deadband(2.0)
    .delay(2.0)
    .unit("%")
    .build();

Alarm Actions:

3. Safety-Critical Alarm Configuration

HIPPS Protection (PT-HIPPS-1/2/3)

AlarmConfig hippsAlarmConfig = AlarmConfig.builder()
    .highHighLimit(59.0)      // Immediate HIPPS closure
    .deadband(0.2)            // Minimal deadband
    .delay(0.0)               // No delay - safety critical
    .unit("bara")
    .build();

2oo3 Voting Logic:

ESD Trigger (PT-ESD-001)

AlarmConfig esdAlarmConfig = AlarmConfig.builder()
    .highHighLimit(60.0)      // Full ESD sequence
    .deadband(0.5)
    .delay(0.0)               // Immediate ESD trigger
    .unit("bara")
    .build();

Demonstration Scenarios

The example runs six comprehensive scenarios demonstrating alarm-triggered logic:

Scenario 1: Normal Operation

Scenario 2: HI Alarm - Operator Notification

Scenario 3: HIHI Alarm - Automatic Control

Scenario 4: HIPPS Activation

Scenario 5: ESD Triggered by Alarm

Scenario 6: Low Level Emergency Shutdown

Code Structure

Main Components

// 1. Build process system
ProcessSystem processSystem = buildProcessSystem();

// 2. Create alarm manager
ProcessAlarmManager alarmManager = new ProcessAlarmManager();

// 3. Setup instrumentation with alarms
InstrumentationSetup instruments = 
    setupInstrumentationWithAlarms(processSystem, alarmManager);

// 4. Setup process logic
ProcessLogicSetup logicSetup = setupProcessLogic(processSystem, instruments);

// 5. Run scenarios
runAlarmTriggeredScenarios(runner, alarmManager, instruments, 
                          logicSetup, processSystem);

Alarm Evaluation Loop

private static List<AlarmEvent> evaluateAndDisplayAlarms(
    ProcessAlarmManager alarmManager,
    InstrumentationSetup instruments, 
    ProcessSystem system, 
    double dt) {

    List<AlarmEvent> allEvents = new ArrayList<>();

    // Run process to get current values
    system.run();

    // Evaluate each measurement device
    double sepPressure = instruments.separatorPT.getMeasuredValue();
    allEvents.addAll(alarmManager.evaluateMeasurement(
        instruments.separatorPT, sepPressure, dt, simulationTime));

    // ... evaluate other transmitters ...

    return allEvents;
}

Alarm-Triggered Control Actions

private static void handlePressureHIHIAlarm(List<AlarmEvent> events, 
                                           ProcessSystem system,
                                           ProcessAlarmManager alarmManager) {

    for (AlarmEvent event : events) {
        if (event.getType() == AlarmEventType.ACTIVATED && 
            event.getLevel() == AlarmLevel.HIHI &&
            event.getSource().equals("PT-101")) {

            // Automatic control response
            ControlValve inletValve = 
                (ControlValve) system.getUnit("Inlet Control Valve");
            inletValve.setPercentValveOpening(50.0);

            // Run system with new valve position
            system.run();

            // Acknowledge alarm after action
            alarmManager.acknowledgeAll(simulationTime);
        }
    }
}

Alarm-Triggered Safety Logic

private static void handleHIPPSAlarm(List<AlarmEvent> events, 
                                    ESDLogic hippsLogic,
                                    ProcessAlarmManager alarmManager) {

    for (AlarmEvent event : events) {
        if (event.getType() == AlarmEventType.ACTIVATED && 
            event.getLevel() == AlarmLevel.HIHI &&
            event.getSource().startsWith("PT-HIPPS")) {

            // Activate HIPPS logic
            hippsLogic.activate();

            alarmManager.acknowledgeAll(simulationTime);
            break; // Only need one HIPPS transmitter to trigger
        }
    }
}

Output Reports

The example generates comprehensive reports:

1. Alarm Status Display

Shows currently active alarms with acknowledgement status:

┌─────────────────────────────────────────────────────────┐
│ ALARM STATUS: After HIHI Alarm + Auto Control          │
├─────────────────────────────────────────────────────────┤
│ Active Alarms: 1                                        │
├─────────────────────────────────────────────────────────┤
│ [ACK] HIHI - PT-101        : 57.00                     │
└─────────────────────────────────────────────────────────┘

2. Alarm History Report

Shows all alarm events with timestamps:

╔════════════════════════════════════════════════════════════════╗
║                    ALARM HISTORY REPORT                        ║
╠════════════════════════════════════════════════════════════════╣
║  Total Events: 12                                              ║
╠════════════════════════════════════════════════════════════════╣
║  Recent Events (last 10):                                      ║
║  ⚠ 30.0s ACTIVATED  PT-101          HI   53.50                ║
║  ⚠ 35.0s ACTIVATED  PT-101          HIHI 57.00                ║
║  ✋ 35.5s ACKNOWLEDGED PT-101       HIHI 57.00                ║
║  ...                                                           ║
╚════════════════════════════════════════════════════════════════╝

3. Alarm Statistics

Aggregated statistics by type and level:

╔════════════════════════════════════════════════════════════════╗
║                    ALARM STATISTICS                            ║
╠════════════════════════════════════════════════════════════════╣
║  Total Activations:     8                                      ║
║  Total Clearances:      3                                      ║
║  Total Acknowledgements: 5                                     ║
║                                                                ║
║  By Level:                                                     ║
║    HIHI (Critical High): 4                                     ║
║    HI (High):            2                                     ║
║    LO (Low):             1                                     ║
║    LOLO (Critical Low):  1                                     ║
╚════════════════════════════════════════════════════════════════╝

Integration Patterns

Pattern 1: Alarm-Triggered Control Adjustment

// Monitor for HIHI alarm
if (alarm.getLevel() == AlarmLevel.HIHI) {
    // Implement automatic control response
    valve.setPercentValveOpening(safeValue);
    system.run();
    alarmManager.acknowledgeAll(time);
}

Pattern 2: Alarm-Triggered Safety Logic

// Monitor for safety-critical alarm
if (alarm.getLevel() == AlarmLevel.HIHI && 
    alarm.getSource().equals("PT-ESD-001")) {
    // Activate safety logic
    esdLogic.activate();
}

Pattern 3: Alarm Acknowledgement Workflow

// Evaluate alarms
List<AlarmEvent> events = evaluateAlarms();

// Process events
for (AlarmEvent event : events) {
    if (event.getType() == AlarmEventType.ACTIVATED) {
        // Log alarm activation
        logger.logAlarm(event);

        // Notify operator
        operatorPanel.displayAlarm(event);
    }
}

// Acknowledge after operator review or automatic action
alarmManager.acknowledgeAll(currentTime);

Best Practices Demonstrated

  1. Layered Protection: Multiple independent protection layers from alarms to mechanical safety devices

  2. Appropriate Delays:

    • Safety-critical alarms: 0 seconds (immediate)
    • Process alarms: 1-5 seconds (avoid nuisance trips)
  3. Deadband Configuration:

    • Safety alarms: Minimal (0.2-0.5)
    • Process alarms: Moderate (1-2% of range)
  4. Alarm Actions:

    • HI/LO: Operator notification
    • HIHI/LOLO: Automatic control or shutdown
  5. Acknowledgement:

    • Acknowledge after automatic actions
    • Track acknowledgement status
  6. Comprehensive Logging:

    • All alarm events recorded
    • Statistics tracked by type and level
    • History available for analysis

Running the Example

# Compile
mvn compile

# Run
mvn exec:java -Dexec.mainClass="neqsim.process.util.example.ProcessLogicAlarmIntegratedExample"

Key Takeaways

Consistent Framework: All alarms configured using the same AlarmConfig builder pattern

Flexible Triggering: Alarms can trigger operator notifications, control actions, or safety logic

Centralized Management: ProcessAlarmManager coordinates all process alarms

Safety Integration: Seamless connection between alarms and SIL-rated safety systems

Production-Ready: Complete with logging, statistics, and acknowledgement workflows

ISA-18.2 Aligned: Four standard alarm levels (LOLO, LO, HI, HIHI)

This example provides a complete template for implementing alarm-triggered process control and safety logic in industrial applications using NeqSim.

ESD Fire Alarm

ESD Fire Alarm System with Voting Logic

Overview

This implementation demonstrates a comprehensive Emergency Shutdown (ESD) system with fire alarm voting logic in NeqSim. The system showcases how multiple fire detectors can be used in a voting configuration to prevent spurious trips while ensuring safety when multiple alarms confirm a fire event.

Key Features

1. FireDetector Measurement Device

Location: src/main/java/neqsim/process/measurementdevice/FireDetector.java

A new binary sensor for fire detection with features:

Example Usage:

FireDetector fireDetector = new FireDetector("FD-101", "Separator Area - North");
fireDetector.setDetectionThreshold(0.5);
fireDetector.setDetectionDelay(1.0);

// Configure alarm
AlarmConfig alarmConfig = AlarmConfig.builder()
    .highLimit(0.5)
    .delay(1.0)
    .unit("binary")
    .build();
fireDetector.setAlarmConfig(alarmConfig);

// Detect fire
fireDetector.detectFire();

// Check status
if (fireDetector.isFireDetected()) {
    System.out.println("Fire detected!");
}

2. Voting Logic Patterns

2-out-of-2 Voting

Requires both fire detectors to activate before triggering ESD. Provides:

2-out-of-3 Voting

Any two of three detectors trigger ESD. Provides:

Implementation:

// Count active alarms
int activeAlarms = (fireDetector1.isFireDetected() ? 1 : 0)
                 + (fireDetector2.isFireDetected() ? 1 : 0)
                 + (fireDetector3.isFireDetected() ? 1 : 0);

// Apply voting logic
boolean esdShouldActivate = (activeAlarms >= 2);

if (esdShouldActivate && !bdValve.isActivated()) {
    bdValve.activate();
    gasSplitter.setSplitFactors(new double[] {0.0, 1.0}); // Redirect to blowdown
}

3. Complete ESD Blowdown Sequence

The test demonstrates a realistic ESD scenario:

Phase 1: Normal Operation (t=0-5s)

Phase 2: First Fire Alarm (t=5s)

Phase 3: Second Fire Alarm (t=10s)

Phase 4: Blowdown with Emissions Tracking (t=10-20s)

Test Results

Test: testESDWithTwoFireAlarmVoting

System Configuration:

Simulation Results (20-second blowdown):

Time (s) | FD-101 | FD-102 | Alarms | BD Open (%) | BD Flow (kg/hr) | Flare Heat (MW) | CO2 Rate (kg/s) | Cumul Heat (GJ) | Cumul CO2 (kg)
---------|--------|--------|--------|-------------|-----------------|-----------------|-----------------|-----------------|----------------
     0.0 |   FIRE |   FIRE |      2 |         0.0 |        817,553  |        11,657   |         626.4   |           11.66 |          626.4
     2.0 |   FIRE |   FIRE |      2 |         0.0 |        814,203  |        11,610   |         623.8   |           34.90 |         1875.3
    10.0 |   FIRE |   FIRE |      2 |        20.0 |        803,959  |        11,464   |         616.0   |          127.08 |         6828.3
    14.0 |   FIRE |   FIRE |      2 |       100.0 |        800,259  |        11,411   |         613.1   |          172.80 |         9285.0
    20.0 |   FIRE |   FIRE |      2 |       100.0 |        795,957  |        11,349   |         609.8   |          241.04 |        12951.8

Final Summary:

Test: testESDWith2OutOf3FireAlarmVoting

Tests voting combinations:

  1. No alarms → ESD: NO
  2. One alarm (FD-101) → ESD: NO
  3. Two alarms (FD-101 + FD-102) → ESD: YES
  4. Three alarms (all active) → ESD: YES
  5. Reset one detector (FD-103) → ESD maintained with 2 remaining
  6. Reset another (FD-102) → Only 1 active, but BD valve stays latched

Key Safety Feature: BD valve remains activated even when alarms clear, requiring manual reset to prevent automatic system restoration during emergency.

Architecture

┌──────────────────────────────────────────────────────────────┐
│                    ESD FIRE ALARM SYSTEM                     │
└──────────────────────────────────────────────────────────────┘

Fire Detectors:                  Voting Logic:
┌────────────┐                  ┌─────────────────┐
│  FD-101    │─────┐           │ Count Active    │
│  (North)   │     │           │ Alarms >= 2?    │──► ESD Trigger
└────────────┘     ├──────────►│                 │
                   │           └─────────────────┘
┌────────────┐     │
│  FD-102    │─────┤
│  (South)   │     │
└────────────┘     │
                   │
┌────────────┐     │
│  FD-103    │─────┘
│  (East)    │ [Optional - for 2-out-of-3]
└────────────┘

Process Flow:
┌────────────┐     ┌──────────┐     ┌─────────────┐
│ Separator  │────►│ Splitter │────►│  To Process │
│  50 bara   │     └──────────┘     └─────────────┘
└────────────┘          │
                        │ (ESD redirects flow)
                        ▼
                  ┌─────────────┐
                  │ BD Valve    │
                  │ (opens 5s)  │
                  └─────────────┘
                        │
                        ▼
                  ┌─────────────┐
                  │  Orifice    │ (flow control)
                  └─────────────┘
                        │
                        ▼
                  ┌─────────────┐
                  │   Flare     │ ◄── Heat & CO2
                  │  1.5 bara   │     Calculations
                  └─────────────┘

Emissions Calculations

The flare tracks cumulative values during blowdown:

Heat Release:

CO2 Emissions:

Gas Burned:

Test Execution

Run all ESD fire alarm tests:

mvnw test -Dtest=ESDFireAlarmSystemTest

Run specific test:

mvnw test -Dtest=ESDFireAlarmSystemTest#testESDWithTwoFireAlarmVoting

Integration Points

With Existing NeqSim Components

Compatible Equipment:

Alarm System Integration:

Safety Instrumented Systems (SIS) Applications

This implementation demonstrates concepts used in:

Best Practices Demonstrated

  1. Voting Logic: Prevents spurious trips while maintaining safety
  2. Safety Latching: BD valve stays activated until manual reset
  3. Alarm Confirmation: Requires multiple independent sensors
  4. Dynamic Simulation: Realistic transient behavior during blowdown
  5. Emissions Tracking: Cumulative tracking for regulatory compliance
  6. Comprehensive Testing: Both unit tests and integration scenarios

Future Enhancements

Potential additions:

References

Files Created/Modified

New Files:

  1. src/main/java/neqsim/process/measurementdevice/FireDetector.java
  2. src/test/java/neqsim/process/equipment/valve/ESDFireAlarmSystemTest.java
  3. docs/wiki/esd_fire_alarm_system.md

Key Dependencies:

Example: Implementing Custom Voting Logic

/**
 * Custom voting logic class for ESD systems.
 */
public class ESDVotingLogic {
    private final List<FireDetector> detectors;
    private final int requiredAlarms;
    private final BlowdownValve bdValve;

    public ESDVotingLogic(BlowdownValve bdValve, int requiredAlarms, 
                          FireDetector... detectors) {
        this.bdValve = bdValve;
        this.requiredAlarms = requiredAlarms;
        this.detectors = Arrays.asList(detectors);
    }

    public void evaluate() {
        int activeAlarms = (int) detectors.stream()
                                          .filter(FireDetector::isFireDetected)
                                          .count();

        if (activeAlarms >= requiredAlarms && !bdValve.isActivated()) {
            bdValve.activate();
            System.out.println("ESD ACTIVATED: " + activeAlarms + 
                             " of " + detectors.size() + " alarms active");
        }
    }

    public boolean isESDActive() {
        return bdValve.isActivated();
    }
}

// Usage:
ESDVotingLogic esdLogic = new ESDVotingLogic(bdValve, 2, 
                                             fireDetector1, 
                                             fireDetector2, 
                                             fireDetector3);

// In simulation loop:
esdLogic.evaluate();

Author: ESOL
Date: November 2025
Version: 1.0

Chapter 31: Pressure Relief Systems

PSV Dynamic Sizing Wiki

Pressure Safety Valve (PSV) Dynamic Sizing Example

Overview

This example demonstrates how to perform a dynamic safety calculation for sizing a pressure safety valve (PSV) using NeqSim's transient simulation capabilities. The scenario simulates a blocked outlet condition where a pressure control valve suddenly closes, causing pressure to rise in a separator until the PSV opens to prevent overpressure.

Process Description

Equipment Configuration

  1. Separator: High-pressure separator receiving gas feed at 50 bara
  2. Splitter: Splits the gas outlet into two streams
    • Stream 1 (99.9%): Goes to the pressure control valve (PCV) for normal operation
    • Stream 2 (0.1%): Goes to the pressure safety valve (PSV) for overpressure protection
  3. Pressure Control Valve (PCV-001): Controls normal outlet pressure (5 bara)
  4. Pressure Safety Valve (PSV-001): Protects against overpressure
    • Set pressure: 55 bara
    • Full open pressure: 60.5 bara (110% of set pressure)

Safety Scenario

The simulation models a sudden blocked outlet scenario:

Implementation

Key Code Sections

1. System Setup

// Create gas system
SystemInterface feedFluid = new SystemSrkEos(273.15 + 40.0, 50.0);
feedFluid.addComponent("nitrogen", 1.0);
feedFluid.addComponent("methane", 85.0);
// ... additional components

// Create separator
Separator separator = new Separator("HP Separator", feedStream);
separator.setCalculateSteadyState(false); // Enable dynamic mode

2. Splitter Configuration

// Split gas to PCV and PSV
Splitter gasSplitter = new Splitter("Gas Splitter", separator.getGasOutStream(), 2);
gasSplitter.setSplitFactors(new double[] {0.999, 0.001});
gasSplitter.setCalculateSteadyState(false);

3. PSV Automatic Opening

The SafetyValve class now automatically controls its opening based on inlet pressure during dynamic simulations. When runTransient() is called, the valve:

// PSV configured with set and full open pressures
SafetyValve pressureSafetyValve = new SafetyValve("PSV-001", stream);
pressureSafetyValve.setPressureSpec(55.0);  // Set pressure
pressureSafetyValve.setFullOpenPressure(60.5);  // Full open pressure
pressureSafetyValve.setCalculateSteadyState(false);  // Enable dynamic mode

// PSV automatically calculates opening in runTransient()
// No manual opening calculation needed!

4. Transient Simulation Loop

double dt = 0.5; // Time step in seconds
for (int i = 0; i < numSteps; i++) {
    currentTime = i * dt;

    // Simulate blocked outlet at t=50s
    if (currentTime >= 50.0 && currentTime < 51.0) {
        pressureControlValve.setPercentValveOpening(1.0);
    }

    // Run transient calculations
    // PSV automatically adjusts its opening based on inlet pressure
    separator.runTransient(dt, id);
    gasSplitter.runTransient(dt, id);
    pressureControlValve.runTransient(dt, id);
    pressureSafetyValve.runTransient(dt, id);  // Automatic PSV control
}

Results

Typical Simulation Results

Key Observations

  1. Pressure Response: When the PCV closes at t=50s, the separator pressure begins to rise steadily
  2. PSV Activation: PSV starts opening at ~140s when pressure reaches 55 bara
  3. Pressure Control: PSV successfully limits maximum pressure to 58.69 bara (6.7% above set pressure)
  4. Flow Distribution: PSV relieves approximately 86% of the feed flow rate at maximum opening

PSV Sizing Validation

The test validates several critical aspects:

  1. ✓ PSV remains closed during normal operation
  2. ✓ PSV opens at the set pressure
  3. ✓ Maximum pressure stays within acceptable limits (< 130% of full open pressure)
  4. ✓ PSV relief capacity exceeds minimum requirements (> 80% of feed rate)
  5. ✓ Pressure rise occurs after PCV blockage
  6. ✓ Overall pressure limited to within 35% of set pressure

Usage

Running the Example

The example is implemented as a JUnit test:

mvnw test -Dtest=SafetyValveDynamicSizingTest

Customization

You can modify the following parameters to study different scenarios:

Best Practices for PSV Sizing

  1. Set Pressure: Typically 10% above maximum allowable working pressure (MAWP)
  2. Accumulation: Full opening at 110% of set pressure (10% accumulation)
  3. Relief Capacity: PSV must handle the maximum credible flow rate
  4. Dynamic Simulation: Validates PSV response and pressure dynamics
  5. Time Step: Use 0.5-1.0 second time steps for valve dynamics
  6. Validation: Compare with API 520/521 or equivalent standards

References

PSV Dynamic Sizing

PSV Dynamic Sizing Example

This example demonstrates how to perform a dynamic safety calculation for a pressure safety valve (PSV) sizing using NeqSim's transient simulation capabilities.

Scenario

A high-pressure separator operates at ~50 bara with gas output flowing through a splitter:

Dynamic Event Sequence

  1. Normal operation (t=0-50s): Separator operates normally, PCV at 50% opening, PSV closed
  2. Blocked outlet (t=50s): PCV suddenly closes to 1% (simulating blocked outlet)
  3. Pressure rise (t=50-130s): Separator pressure increases from ~32 bara to 55 bara
  4. PSV opens (t=130s): PSV starts opening when pressure exceeds set pressure (55 bara)
  5. Relief phase (t=130-200s): PSV relieves gas to control pressure at ~58.7 bara
  6. Recovery (t=200s): PCV reopens to 50%, allowing normal flow path
  7. Pressure drops (t=200-260s): System pressure decreases as both valves relieve
  8. PSV closes (t=260s): PSV reseats when pressure drops to blowdown pressure (~51.15 bara)

PSV Hysteresis Behavior

The PSV implements realistic hysteresis (blowdown) behavior to prevent valve chattering:

Key Point: Once the PSV opens, it does NOT close immediately when pressure drops below the set pressure. It stays open until pressure drops to the blowdown/reseat pressure. This prevents rapid cycling (chattering) that could damage the valve.

Typical Blowdown Values

Code Structure

// Setup equipment
Separator separator = new Separator("HP Separator", feedStream);
Splitter gasSplitter = new Splitter("Gas Splitter", separator.getGasOutStream(), 2);
ThrottlingValve pressureControlValve = new ThrottlingValve("PCV-001", gasSplitter.getSplitStream(0));
SafetyValve pressureSafetyValve = new SafetyValve("PSV-001", gasSplitter.getSplitStream(1));

// Configure PSV with automatic opening
pressureSafetyValve.setPressureSpec(55.0);  // Set pressure (bara)
pressureSafetyValve.setFullOpenPressure(60.5);  // Full open at 110% of set
// Blowdown is automatically set to 7% (reseat at 51.15 bara)

// Alternative: Explicitly set blowdown percentage
pressureSafetyValve.setBlowdown(10.0);  // 10% blowdown for liquid service

// Dynamic simulation loop
for (int i = 0; i < numSteps; i++) {
    currentTime = i * dt;

    // Simulate events (blockage, recovery, etc.)
    if (currentTime >= 50.0 && currentTime < 51.0) {
        pressureControlValve.setPercentValveOpening(1.0);  // Block outlet
    }
    if (currentTime >= 200.0 && currentTime < 201.0) {
        pressureControlValve.setPercentValveOpening(50.0);  // Recover
    }

    // Run transient calculations
    // PSV opening is calculated automatically based on inlet pressure
    separator.runTransient(dt, id);
    gasSplitter.runTransient(dt, id);
    pressureControlValve.runTransient(dt, id);
    pressureSafetyValve.runTransient(dt, id);  // Automatic PSV control with hysteresis
}

Automatic PSV Control

The SafetyValve.runTransient() method automatically:

  1. Monitors inlet pressure
  2. Calculates valve opening percentage based on:
    • Set pressure (starts opening)
    • Full open pressure (100% open)
    • Current valve state (open/closed)
  3. Implements hysteresis:
    • When closed: Opens when P ≥ P_set
    • When open: Closes when P ≤ P_blowdown (reseat pressure)
  4. Prevents chattering through state tracking

Results

From the test simulation with 5000 kg/hr feed:

Parameter Value
Feed flow rate 5000 kg/hr
PSV set pressure 55.0 bara
PSV full open pressure 60.5 bara
PSV blowdown pressure 51.15 bara (7% blowdown)
Maximum separator pressure 58.69 bara
Maximum PSV relief flow 6086 kg/hr
PSV opening at max pressure 67.1%

Key Observations

  1. PSV prevents catastrophic overpressure: Maximum pressure (58.69 bara) is well below the full open pressure (60.5 bara), demonstrating effective pressure control.

  2. Adequate relief capacity: PSV relieves 6086 kg/hr, which exceeds the feed rate (5000 kg/hr), ensuring the valve can handle the relief scenario.

  3. Hysteresis prevents chattering:

    • PSV opens at 55.0 bara
    • PSV stays open even when pressure drops to 52-54 bara
    • PSV only closes when pressure reaches 50.95 bara (below the 51.15 bara blowdown)
  4. Smooth pressure control: The automatic PSV control provides smooth pressure regulation during both pressure buildup and recovery phases.

Best Practices

  1. Always use dynamic mode: Set setCalculateSteadyState(false) for all equipment in transient simulations

  2. Size PSV conservatively: Ensure PSV can handle at least 100% of the feed flow rate

  3. Set appropriate blowdown: Use 7-10% for gas, 10-20% for liquid service to prevent chattering

  4. Use unique UUID: Create one UUID per simulation run to track transient state correctly

  5. Choose appropriate time step: 0.5 seconds provides good resolution for PSV dynamics

  6. Monitor key parameters: Track separator pressure, valve openings, and flow rates throughout the simulation

See Also

PSD Valve Trip

PSD Valve with High-High Alarm Trip

Overview

The PSDValve (Process Shutdown Valve) is a safety isolation valve that automatically closes when a High-High (HIHI) pressure alarm is triggered. It provides emergency shutdown protection by monitoring a pressure transmitter and rapidly closing to prevent overpressure conditions from propagating through the process.

Key Features

How It Works

  1. Normal Operation: Valve operates fully open, allowing flow to pass through
  2. Pressure Monitoring: Linked pressure transmitter continuously evaluates pressure against alarm limits
  3. HIHI Alarm: When pressure exceeds highHighLimit, alarm activates after configured delay
  4. Automatic Trip: PSD valve detects HIHI alarm and commands closure
  5. Fast Closure: Valve closes in configured closure time (typically 2 seconds)
  6. Trip Latching: Valve remains closed even if pressure drops - manual reset required
  7. Reset & Recovery: Operator resets trip, then manually opens valve

Basic Usage

1. Create Pressure Transmitter with Alarm Configuration

// Create pressure transmitter monitoring separator inlet
PressureTransmitter PT = new PressureTransmitter("PT-101", separatorInlet);

// Configure HIHI alarm at 55 bara with 1 bara deadband and 0.5 second delay
AlarmConfig alarmConfig = AlarmConfig.builder()
    .highHighLimit(55.0)      // HIHI trip point
    .deadband(1.0)             // Alarm clears at 54 bara
    .delay(0.5)                // 0.5 second confirmation delay
    .unit("bara")
    .build();

PT.setAlarmConfig(alarmConfig);

2. Create PSD Valve

// Create PSD valve on separator inlet
PSDValve psdValve = new PSDValve("PSD-101", feedStream);
psdValve.setPercentValveOpening(100.0);  // Start fully open
psdValve.setCv(150.0);                    // Sizing coefficient
psdValve.setClosureTime(2.0);            // 2 seconds fast closure

// Link to pressure transmitter
psdValve.linkToPressureTransmitter(PT);

3. Run Dynamic Simulation

double time = 0.0;
double dt = 1.0; // 1 second time step

while (time < simulationTime) {
    // Update pressure measurement
    double measuredPressure = psdValve.getOutletStream().getPressure("bara");
    PT.evaluateAlarm(measuredPressure, dt, time);

    // Run equipment transient calculations
    psdValve.runTransient(dt, UUID.randomUUID());
    separator.runTransient(dt, UUID.randomUUID());

    // Check if valve has tripped
    if (psdValve.hasTripped()) {
        System.out.println("PSD VALVE TRIPPED - Emergency shutdown activated!");
        // Implement emergency response...
    }

    time += dt;
}

4. Reset After Trip

// After alarm clears and situation is safe
if (psdValve.hasTripped()) {
    psdValve.reset();                      // Clear trip state
    psdValve.setPercentValveOpening(100.0); // Manually reopen valve
}

Complete Example

import neqsim.process.equipment.valve.PSDValve;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.alarm.AlarmConfig;
import neqsim.process.measurementdevice.PressureTransmitter;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemSrkEos;
import java.util.UUID;

public class PSDValveExample {
    public static void main(String[] args) {
        // Create natural gas mixture at 50 bara, 40°C
        SystemInterface fluid = new SystemSrkEos(273.15 + 40, 50.0);
        fluid.addComponent("nitrogen", 0.5);
        fluid.addComponent("CO2", 1.0);
        fluid.addComponent("methane", 85.0);
        fluid.addComponent("ethane", 8.0);
        fluid.addComponent("propane", 4.0);
        fluid.addComponent("i-butane", 0.75);
        fluid.addComponent("n-butane", 0.75);
        fluid.setMixingRule(2);

        // Create feed stream
        Stream feedStream = new Stream("Feed", fluid);
        feedStream.setFlowRate(5000.0, "kg/hr");
        feedStream.setTemperature(40.0, "C");
        feedStream.setPressure(50.0, "bara");
        feedStream.run();

        // Create PSD valve on separator inlet
        PSDValve psdValve = new PSDValve("PSD-101", feedStream);
        psdValve.setPercentValveOpening(100.0);
        psdValve.setCv(150.0);
        psdValve.setClosureTime(2.0); // 2 second fast closure
        psdValve.run();

        // Create pressure transmitter monitoring PSD outlet (separator inlet)
        PressureTransmitter PT = new PressureTransmitter("PT-101", 
                                                         psdValve.getOutletStream());

        // Configure HIHI alarm
        AlarmConfig alarmConfig = AlarmConfig.builder()
            .highHighLimit(55.0)  // PAHH at 55 bara
            .deadband(1.0)
            .delay(0.5)
            .unit("bara")
            .build();
        PT.setAlarmConfig(alarmConfig);

        // Link PSD valve to pressure transmitter
        psdValve.linkToPressureTransmitter(PT);

        // Create separator
        Separator separator = new Separator("Separator", psdValve.getOutletStream());
        separator.setInternalDiameter(1.5);
        separator.setSeparatorLength(4.0);
        separator.run();

        // Dynamic simulation
        double time = 0.0;
        double dt = 1.0;

        System.out.println("=== PSD VALVE PROTECTION SYSTEM ===");
        System.out.println("HIHI setpoint: 55.0 bara");
        System.out.println("PSD closure time: 2.0 seconds\n");

        for (int i = 0; i < 100; i++) {
            // Run transient calculations
            psdValve.runTransient(dt, UUID.randomUUID());
            separator.runTransient(dt, UUID.randomUUID());

            time += dt;

            // Evaluate alarm
            double pressure = psdValve.getOutletStream().getPressure("bara");
            PT.evaluateAlarm(pressure, dt, time);

            // Status reporting
            if (i % 10 == 0 || psdValve.hasTripped()) {
                String alarmState = "NONE";
                if (PT.getAlarmState().isActive()) {
                    alarmState = PT.getAlarmState().getActiveLevel().toString();
                }

                System.out.printf("Time: %5.0f s | Pressure: %5.2f bara | " +
                                  "Alarm: %4s | PSD: %5.1f %% | Tripped: %3s%n",
                    time, pressure, alarmState,
                    psdValve.getPercentValveOpening(),
                    psdValve.hasTripped() ? "YES" : "NO");
            }

            if (psdValve.hasTripped()) {
                System.out.println("\n*** EMERGENCY SHUTDOWN ACTIVATED ***");
                break;
            }
        }
    }
}

Alarm Configuration

The PSD valve relies on NeqSim's alarm system. Key parameters:

Parameter Description Typical Value
highHighLimit HIHI trip pressure 110% of MAWP
highLimit High alarm (warning only) 105% of MAWP
deadband Hysteresis to prevent chattering 1-2% of setpoint
delay Confirmation time before alarm 0.1-2.0 seconds
unit Engineering unit "bara", "barg", "psia"

API Reference

Constructor

PSDValve(String name)
PSDValve(String name, StreamInterface inletStream)

Configuration Methods

void linkToPressureTransmitter(MeasurementDeviceInterface transmitter)
void setClosureTime(double closureTime)  // seconds
void setTripEnabled(boolean enabled)
void setCv(double Cv)  // Valve sizing coefficient

Status Methods

boolean hasTripped()
boolean isTripEnabled()
double getClosureTime()
MeasurementDeviceInterface getPressureTransmitter()

Control Methods

void reset()  // Clear trip state
void setPercentValveOpening(double opening)  // 0-100%
@Override
void runTransient(double dt, UUID id)

Best Practices

  1. Alarm Setpoint: Set HIHI at safe margin below equipment MAWP (typically 95-98% of MAWP)
  2. Closure Time: Balance between fast response and avoiding water hammer (1-5 seconds typical)
  3. Deadband: Use 1-2% deadband to prevent alarm chattering at trip point
  4. Alarm Delay: Short delay (0.1-0.5s) to confirm trip, avoid nuisance trips
  5. Testing: Regularly test PSD functionality in simulation before deployment
  6. Documentation: Document trip setpoints in process safety documentation
  7. Redundancy: Consider redundant pressure transmitters for critical applications

Comparison: PSD Valve vs Safety Valve

Feature PSD Valve Safety Valve
Activation HIHI alarm signal Pressure overcomes spring
Response Time 1-5 seconds (fast) Milliseconds (immediate)
Reopening Manual reset required Automatic reseating
Primary Use Isolation/shutdown Pressure relief
Flow Direction Stops inlet flow Vents to atmosphere/flare
Typical Location Inlet to equipment Top of vessel/equipment
Failure Mode Should fail closed Must fail open

Integration with Process Safety

The PSD valve integrates with NeqSim's process safety features:

See Also

Rupture Disks

Rupture Disk Dynamic Behavior

This document explains the rupture disk implementation in NeqSim, demonstrating the key difference between rupture disks and pressure safety valves (PSVs).

What is a Rupture Disk?

A rupture disk (also called a bursting disc) is a non-reclosing pressure relief device that:

This is fundamentally different from a safety valve which:

Applications

Rupture disks are typically used for:

  1. Primary relief for rapid pressure rise scenarios (runaway reactions)
  2. Backup protection in series with safety valves
  3. Corrosive/fouling services where PSVs would fail
  4. Emergency relief where instant full opening is required
  5. Low maintenance applications

Implementation

RuptureDisk Class

RuptureDisk disk = new RuptureDisk("RD-001", inletStream);
disk.setBurstPressure(55.0);  // bara - disk ruptures at this pressure
disk.setFullOpenPressure(57.75);  // bara - fully open (typically 5% above burst)
disk.setOutletPressure(1.0, "bara");
disk.setCv(150.0);
disk.setCalculateSteadyState(false);

Key Parameters

Parameter Description Typical Value
Burst Pressure Pressure at which disk ruptures Set by design
Full Open Pressure Pressure for 100% opening 105-110% of burst
Cv Flow coefficient Sized for relief scenario

Automatic Behavior in runTransient()

The rupture disk automatically:

  1. Monitors inlet pressure each time step
  2. Ruptures when pressure ≥ burst pressure
  3. Remains fully open regardless of subsequent pressure changes
  4. Tracks state with hasRuptured() flag

Comparison: Rupture Disk vs Safety Valve

Safety Valve (PSV) with Hysteresis

Pressure rises → Opens at 55 bara → Relieves pressure
Pressure drops → Stays open until 51.15 bara (blowdown)
Pressure below blowdown → Closes → Can reopen if needed

Rupture Disk

Pressure rises → Bursts at 55 bara → Relieves pressure
Pressure drops → STAYS 100% OPEN
Pressure at any level → STAYS 100% OPEN (one-time device)

Example: Blocked Outlet Scenario

// Setup separator with gas splitter
Separator separator = new Separator("HP Separator", feedStream);
Splitter gasSplitter = new Splitter("Gas Splitter", separator.getGasOutStream(), 2);

// Normal operation path
ThrottlingValve pcv = new ThrottlingValve("PCV-001", gasSplitter.getSplitStream(0));
pcv.setPercentValveOpening(50.0);

// Emergency relief path
RuptureDisk disk = new RuptureDisk("RD-001", gasSplitter.getSplitStream(1));
disk.setBurstPressure(55.0);
disk.setFullOpenPressure(57.75);

// Dynamic simulation
UUID id = UUID.randomUUID();
for (int i = 0; i < numSteps; i++) {
    double time = i * dt;

    // Simulate PCV blockage at t=50s
    if (time >= 50.0 && time < 51.0) {
        pcv.setPercentValveOpening(1.0);
    }

    // Simulate PCV recovery at t=200s
    if (time >= 200.0 && time < 201.0) {
        pcv.setPercentValveOpening(50.0);
    }

    // Run transient - disk bursts automatically
    separator.runTransient(dt, id);
    gasSplitter.runTransient(dt, id);
    pcv.runTransient(dt, id);
    disk.runTransient(dt, id);  // Automatic rupture control
}

Test Results

From RuptureDiskDynamicTest:

Behavior Sequence

Time:   0-120s: Normal operation, disk closed, pressure below 55 bara
Time:   ~130s: Disk ruptures at 55 bara
Time: 140-200s: Pressure controlled at ~53 bara, disk 100% open
Time: 200-300s: PCV reopens, pressure drops to 30 bara
              → Disk STILL 100% open!

Key Observations

Metric Value
Burst pressure 55.0 bara
Max pressure 55.35 bara
Max relief flow 5950 kg/hr
Final pressure 30.5 bara
Final disk opening 100%

Critical Behavior: Disk remained fully open even though pressure dropped 24.5 bara below the burst pressure!

Disk Reset (Simulation Only)

For simulation purposes, you can reset a ruptured disk:

disk.reset();  // Simulates disk replacement
// Disk is now unruptured and closed
// In reality, you would physically replace the disk

Best Practices

  1. Sizing: Size rupture disks for full relief capacity - they open instantly
  2. Series Protection: Often used upstream of PSVs to protect them from corrosion
  3. Burst Tolerance: Account for manufacturing tolerance (typically ±5%)
  4. Rapid Opening: Full open pressure is typically 5% above burst (vs 10% for PSV)
  5. One-Time Use: Plan for system shutdown and disk replacement after rupture
  6. Testing: Use reset() method in simulations to test multiple scenarios

When to Use Rupture Disk vs PSV

Use Rupture Disk When:

Use Safety Valve When:

See Also

Chapter 32: HIPPS Systems

HIPPS Summary

HIPPS Implementation Summary for NeqSim

Executive Summary

A complete HIPPS (High Integrity Pressure Protection System) implementation has been added to NeqSim for safety simulation and analysis. HIPPS is a Safety Instrumented System (SIS) that prevents overpressure by shutting down the source of pressure before it reaches unsafe levels, providing an alternative or complement to traditional pressure relief devices (PSVs/rupture disks).

Files Created

1. Core Implementation

File: src/main/java/neqsim/process/equipment/valve/HIPPSValve.java

Key Features:

2. Test Suite

File: src/test/java/neqsim/process/equipment/valve/HIPPSValveTest.java

Test Coverage:

3. Documentation

File: docs/hipps_implementation.md

Documentation Includes:

4. Example Code

File: src/main/java/neqsim/process/util/example/HIPPSExample.java

Example Features:

Implementation Architecture

Class Hierarchy

ThrottlingValve (base)
    └── HIPPSValve (new)

Integration Points

HIPPSValve
    ├── MeasurementDeviceInterface (pressure transmitters)
    ├── AlarmState (HIHI alarm monitoring)
    ├── ProcessEquipmentBaseClass (standard equipment interface)
    └── Serializable (state persistence)

Voting Logic Enum

public enum VotingLogic {
    ONE_OUT_OF_ONE("1oo1"),
    ONE_OUT_OF_TWO("1oo2"),
    TWO_OUT_OF_TWO("2oo2"),
    TWO_OUT_OF_THREE("2oo3"),  // Recommended for SIL 2/3
    TWO_OUT_OF_FOUR("2oo4")
}

Key Capabilities for Safety Simulations

1. Redundancy and Voting

2. Transient Behavior

3. Safety Validation

4. Failure Mode Analysis

5. Industry Compliance

Usage Example (Simple)

// Create HIPPS valve
HIPPSValve hipps = new HIPPSValve("HIPPS-XV-001", feedStream);

// Add redundant transmitters
hipps.addPressureTransmitter(PT1);
hipps.addPressureTransmitter(PT2);
hipps.addPressureTransmitter(PT3);

// Configure for SIL 3
hipps.setVotingLogic(HIPPSValve.VotingLogic.TWO_OUT_OF_THREE);
hipps.setSILRating(3);
hipps.setClosureTime(3.0); // 3 seconds

// In transient simulation
PT1.evaluateAlarm(pressure, dt, time);
PT2.evaluateAlarm(pressure, dt, time);
PT3.evaluateAlarm(pressure, dt, time);
hipps.runTransient(dt, UUID.randomUUID());

if (hipps.hasTripped()) {
    System.out.println("HIPPS activated - overpressure prevented");
}

HIPPS vs PSV Comparison

Aspect HIPPS PSV
Action Stops flow (isolation) Relieves pressure (venting)
Trip Point Below MAWP (e.g., 90%) At/above MAWP
Emissions Prevents flaring Releases to flare
SIL Rating SIL 2 or SIL 3 Mechanical (non-SIL)
Response 2-5 seconds Instantaneous
Redundancy Multiple transmitters Single device
Testing Partial stroke, proof tests Periodic inspection

Safety Simulation Benefits

1. Overpressure Prevention Modeling

2. Emissions Reduction

3. Reliability Analysis

4. Defense-in-Depth

5. Economic Analysis

Running the Examples

Run Test Suite

# Windows (cmd)
.\mvnw test -Dtest=HIPPSValveTest

# Windows (PowerShell)
.\mvnw.cmd test -Dtest=HIPPSValveTest

# Linux/Mac
./mvnw test -Dtest=HIPPSValveTest

Run Example

# Compile and run
.\mvnw exec:java -Dexec.mainClass="neqsim.process.util.example.HIPPSExample"

Integration with Existing NeqSim Components

Compatible Equipment

Alarm System Integration

// HIPPS uses existing alarm infrastructure
AlarmConfig hippsAlarm = AlarmConfig.builder()
    .highHighLimit(90.0)
    .deadband(2.0)
    .delay(0.5)
    .unit("bara")
    .build();

PT.setAlarmConfig(hippsAlarm);

Transient Simulation Integration

// HIPPS participates in transient calculations
hipps.runTransient(dt, UUID.randomUUID());

How to Implement HIPPS for Safety Simulations

Step 1: Identify Protection Requirements

Step 2: Configure HIPPS Components

// Create redundant transmitters
PressureTransmitter PT1 = new PressureTransmitter("PT-A", stream);
PressureTransmitter PT2 = new PressureTransmitter("PT-B", stream);
PressureTransmitter PT3 = new PressureTransmitter("PT-C", stream);

// Configure alarms at trip point
AlarmConfig alarm = AlarmConfig.builder()
    .highHighLimit(tripPoint)
    .deadband(2.0)
    .delay(0.5)
    .unit("bara")
    .build();

// Create HIPPS with voting
HIPPSValve hipps = new HIPPSValve("HIPPS-XV-001", stream);
hipps.addPressureTransmitter(PT1);
hipps.addPressureTransmitter(PT2);
hipps.addPressureTransmitter(PT3);
hipps.setVotingLogic(HIPPSValve.VotingLogic.TWO_OUT_OF_THREE);
hipps.setSILRating(3);

Step 3: Add PSV Backup

// PSV provides backup protection
SafetyValve psv = new SafetyValve("PSV-001", stream);
psv.setPressureSpec(mawp); // Set at MAWP

Step 4: Run Transient Simulation

for (double time = 0; time < totalTime; time += dt) {
    // Update process conditions
    // ...

    // Evaluate alarms
    PT1.evaluateAlarm(pressure, dt, time);
    PT2.evaluateAlarm(pressure, dt, time);
    PT3.evaluateAlarm(pressure, dt, time);

    // Run HIPPS
    hipps.runTransient(dt, UUID.randomUUID());

    // Check protection status
    if (hipps.hasTripped()) {
        // HIPPS activated - analyze response
    }
}

Step 5: Analyze Results

// Get comprehensive diagnostics
System.out.println(hipps.getDiagnostics());

// Verify safety objectives
boolean preventedPsvLift = !psv.getPercentValveOpening() > 0;
boolean belowMAWP = maxPressure < mawp;
boolean trippedCorrectly = hipps.hasTripped();

Standards Compliance

IEC 61508/61511

API RP 14C

API RP 521

Best Practices

1. Set Point Selection

2. Voting Logic Selection

Application Recommended Voting SIL Level
Low risk, simple 1oo1 SIL 1
Medium risk 1oo2 or 2oo3 SIL 2
High risk, critical 2oo3 SIL 3

3. Response Time

4. Testing and Validation

5. Integration with PSV

Conclusion

The HIPPS implementation in NeqSim provides comprehensive capabilities for safety simulation and analysis:

Complete SIS modeling with voting logic and redundancy ✅ Realistic transient behavior including closure dynamics ✅ SIL-rated configuration (SIL 1, 2, 3) per industry standards ✅ Comprehensive testing support (partial stroke, proof tests) ✅ Integration with existing safety systems (PSV, PSD, alarms) ✅ Extensive documentation and working examples ✅ Production-ready code with full test coverage

Key Advantage: HIPPS prevents overpressure before it occurs, eliminating flaring and protecting equipment, while PSVs relieve pressure after it exceeds safe limits. For safety-critical applications, HIPPS + PSV provides robust defense-in-depth protection.

Author

Implementation follows NeqSim architecture patterns and coding standards for process safety simulation, consistent with existing ESD, PSD, and safety valve implementations.

HIPPS Implementation

HIPPS (High Integrity Pressure Protection System) Implementation in NeqSim

Overview

HIPPS is a Safety Instrumented System (SIS) designed to prevent overpressure by shutting down the source of pressure rather than relieving it through pressure safety valves (PSVs) or rupture disks. This document describes the HIPPS implementation in NeqSim and provides guidance for safety simulations.

What is HIPPS?

High Integrity Pressure Protection System (HIPPS) is an automated safety system that:

HIPPS vs. PSV Comparison

Aspect HIPPS PSV (Pressure Safety Valve)
Action Stops flow (isolation) Relieves pressure (venting)
Trip Point Below MAWP (e.g., 90%) At or above MAWP
Environmental Impact Prevents flaring Releases to flare
Safety Rating SIL 2 or SIL 3 Mechanical (non-SIL)
Testing Partial stroke, proof tests Periodic inspection
Response Time 2-5 seconds typical Instantaneous (spring-loaded)
Redundancy Multiple transmitters, voting logic Single device
Failure Mode Fail-safe (close) or diagnosed Fail-safe (open)
Cost Higher initial cost Lower initial cost
Application Subsea, closed systems General overpressure protection

Implementation Components

1. HIPPSValve Class

Location: src/main/java/neqsim/process/equipment/valve/HIPPSValve.java

Key Features:

2. Voting Logic

HIPPS uses redundant pressure transmitters with voting logic to prevent spurious trips while maintaining safety:

1oo1 (1 out of 1)

1oo2 (1 out of 2)

2oo2 (2 out of 2)

2oo4 (2 out of 4)

Usage Examples

Example 1: Basic HIPPS Configuration (2oo3 Voting)

// Create high-pressure feed stream
SystemInterface fluid = new SystemSrkEos(298.15, 100.0);
fluid.addComponent("methane", 85.0);
fluid.addComponent("ethane", 10.0);
fluid.addComponent("propane", 5.0);
fluid.setMixingRule("classic");
fluid.createDatabase(true);

Stream feedStream = new Stream("HP Feed", fluid);
feedStream.setFlowRate(20000.0, "kg/hr");
feedStream.setPressure(100.0, "bara");
feedStream.setTemperature(40.0, "C");
feedStream.run();

// Create three redundant pressure transmitters
PressureTransmitter PT1 = new PressureTransmitter("PT-101A", feedStream);
PressureTransmitter PT2 = new PressureTransmitter("PT-101B", feedStream);
PressureTransmitter PT3 = new PressureTransmitter("PT-101C", feedStream);

// Configure HIHI alarm at 90 bara (below 100 bara MAWP)
AlarmConfig hippsAlarm = AlarmConfig.builder()
    .highHighLimit(90.0)  // HIPPS trips at 90% of MAWP
    .deadband(2.0)
    .delay(0.5)          // 500ms confirmation delay
    .unit("bara")
    .build();

PT1.setAlarmConfig(hippsAlarm);
PT2.setAlarmConfig(hippsAlarm);
PT3.setAlarmConfig(hippsAlarm);

// Create HIPPS valve with 2oo3 voting (SIL 3)
HIPPSValve hippsValve = new HIPPSValve("HIPPS-XV-001", feedStream);
hippsValve.addPressureTransmitter(PT1);
hippsValve.addPressureTransmitter(PT2);
hippsValve.addPressureTransmitter(PT3);
hippsValve.setVotingLogic(HIPPSValve.VotingLogic.TWO_OUT_OF_THREE);
hippsValve.setClosureTime(3.0); // 3 second SIL-rated actuator
hippsValve.setSILRating(3);
hippsValve.setProofTestInterval(8760.0); // Annual proof test

// Add to process system
ProcessSystem process = new ProcessSystem();
process.add(hippsValve);

Example 2: Dynamic Simulation with HIPPS

// Transient simulation with pressure ramp
double timeStep = 0.5; // seconds
double totalTime = 30.0;

for (double time = 0; time <= totalTime; time += timeStep) {
    // Update process conditions (e.g., blocked outlet scenario)
    if (time > 5.0) {
        // Simulate pressure buildup
        double pressure = 80.0 + (time - 5.0) * 2.0; // 2 bara/sec ramp
        feedStream.setPressure(pressure, "bara");
        feedStream.run();
    }

    // Evaluate alarms on all transmitters
    double currentPressure = feedStream.getPressure("bara");
    PT1.evaluateAlarm(currentPressure, timeStep, time);
    PT2.evaluateAlarm(currentPressure, timeStep, time);
    PT3.evaluateAlarm(currentPressure, timeStep, time);

    // Run HIPPS transient calculation
    hippsValve.runTransient(timeStep, UUID.randomUUID());

    // Check HIPPS status
    if (hippsValve.hasTripped()) {
        System.out.println("HIPPS activated at t=" + time + "s, P=" + currentPressure + " bara");
        System.out.println("Active transmitters: " + hippsValve.getActiveTransmitterCount());
        break;
    }

    // Continue processing downstream equipment...
}

Example 3: HIPPS with PSV Backup

// HIPPS provides primary protection, PSV is backup

// Create HIPPS (trips at 90 bara)
HIPPSValve hippsValve = new HIPPSValve("HIPPS-XV-001", feedStream);
// ... configure as in Example 1 ...

// Create PSV as backup (set at 100 bara MAWP)
SafetyValve psv = new SafetyValve("PSV-001", feedStream);
psv.setPressureSpec(100.0); // PSV set pressure at MAWP
psv.setFullOpenPressure(110.0); // Full open at 10% overpressure
psv.setBlowdown(7.0); // 7% blowdown

// In normal operation:
// 1. Pressure rises due to upset condition
// 2. HIPPS trips at 90 bara (prevents further pressure rise)
// 3. PSV never lifts because HIPPS stopped the overpressure
// 4. No flaring or emissions

// In HIPPS failure scenario:
// 1. Pressure continues to rise
// 2. PSV lifts at 100 bara (backup protection)
// 3. System is protected, but gas is flared

Example 4: Transmitter Failure Scenario

// Simulate a transmitter failure during operation

HIPPSValve hippsValve = new HIPPSValve("HIPPS-XV-001", feedStream);
hippsValve.setVotingLogic(HIPPSValve.VotingLogic.TWO_OUT_OF_THREE);

PressureTransmitter PT1 = new PressureTransmitter("PT-101A", feedStream);
PressureTransmitter PT2 = new PressureTransmitter("PT-101B", feedStream);
PressureTransmitter PT3 = new PressureTransmitter("PT-101C", feedStream);

hippsValve.addPressureTransmitter(PT1);
hippsValve.addPressureTransmitter(PT2);
hippsValve.addPressureTransmitter(PT3);

// During operation, PT2 fails (diagnosed and bypassed)
hippsValve.removePressureTransmitter(PT2);

// Change voting to 1oo2 for continued operation
hippsValve.setVotingLogic(HIPPSValve.VotingLogic.ONE_OUT_OF_TWO);

// System continues operating with degraded redundancy
// Schedule maintenance to repair PT2 and restore 2oo3 voting

Example 5: Partial Stroke Testing

// Perform partial stroke test (required for SIL validation)

HIPPSValve hippsValve = new HIPPSValve("HIPPS-XV-001", feedStream);

// During normal operation, perform 15% stroke test
hippsValve.performPartialStrokeTest(0.15); // 15% stroke

// Simulation of partial stroke test
double testDuration = 5.0; // seconds
double timeStep = 0.1;

for (double time = 0; time < testDuration; time += timeStep) {
    hippsValve.runTransient(timeStep, UUID.randomUUID());

    if (hippsValve.isPartialStrokeTestActive()) {
        System.out.println("Test in progress: Opening = " + 
            hippsValve.getPercentValveOpening() + "%");
    }
}

// Valve returns to 100% open after test
// Test validates valve can move (demonstrates functional operation)

Safety Simulation Best Practices

1. Response Time Modeling

HIPPS response time includes:

// Model realistic closure time
hippsValve.setClosureTime(3.0); // 3 seconds typical for SIL-rated ball valve

// Account for alarm confirmation delay
AlarmConfig alarm = AlarmConfig.builder()
    .highHighLimit(90.0)
    .delay(0.5) // 500 ms confirmation delay
    .build();

2. Set Point Selection

HIPPS trip point should be:

// Example: MAWP = 100 bara
// Normal operation = 70-80 bara
// HIPPS trip = 90 bara (10% margin below MAWP)
// PSV set = 100 bara (at MAWP)

double mawp = 100.0;
double hippsTrip = mawp * 0.90; // 90% of MAWP

3. Transmitter Placement

4. Failure Mode Analysis

Model both success and failure scenarios:

// Scenario 1: HIPPS successful operation
// - Transmitters detect overpressure
// - Voting logic triggers
// - Valve closes in 3 seconds
// - Pressure stabilizes below MAWP

// Scenario 2: HIPPS spurious trip
hippsValve.recordSpuriousTrip();
// - Production lost
// - Economic impact
// - Need to restart system

// Scenario 3: HIPPS failure to close
hippsValve.setTripEnabled(false); // Simulate failure
// - PSV must provide protection
// - Flaring occurs
// - Verify PSV capacity adequate

5. Integration with Process Control

// HIPPS should be independent of process control system
// But can provide signals for:
// - Alarm annunciation
// - Automatic process shutdown
// - Data logging

if (hippsValve.hasTripped()) {
    // Trigger alarms
    // Shut down feed pumps/compressors
    // Log event for investigation
    System.out.println(hippsValve.getDiagnostics());
}

6. Proof Test Interval

// Track proof test intervals for SIL validation
hippsValve.setProofTestInterval(8760.0); // Annual proof test

// During operation
if (hippsValve.isProofTestDue()) {
    // Schedule maintenance
    // Perform full functional test
    // Document results
    hippsValve.performProofTest(); // Reset timer
}

Typical Applications

1. Subsea Pipeline Protection

[Platform] --100 bara--> [Subsea Pipeline] ---> [HIPPS] --50 bara--> [Receiving Platform]

2. Blocked Outlet Scenario

[Compressor] --> [HIPPS] --> [Valve] --> [Process]

3. Thermal Expansion

[Storage] --liquid--> [HIPPS] ---> [Isolated Section] ---> [Valve]

Diagnostic and Monitoring

Getting HIPPS Status

// Basic status
System.out.println(hippsValve.toString());

// Comprehensive diagnostics
System.out.println(hippsValve.getDiagnostics());

// Key metrics
int activeTx = hippsValve.getActiveTransmitterCount();
boolean tripped = hippsValve.hasTripped();
int spurious = hippsValve.getSpuriousTripCount();
double lastTrip = hippsValve.getLastTripTime();
boolean testDue = hippsValve.isProofTestDue();

Output Example

=== HIPPS DIAGNOSTICS ===
System: HIPPS-XV-001
SIL Rating: SIL 3
Configuration: 2oo3 voting
Closure Time: 3.0 s

Transmitter Status:
  PT-1: ALARM (92.50 bara)
  PT-2: ALARM (92.45 bara)
  PT-3: OK (89.80 bara)

Operational History:
  Total Trips: 1
  Spurious Trips: 0
  Last Trip: 15.5 s
  Runtime: 120.0 s

Maintenance:
  Proof Test Interval: 8760 hrs
  Time Since Proof Test: 450.5 hrs
  Status: OK

Testing

Comprehensive test suite located at: src/test/java/neqsim/process/equipment/valve/HIPPSValveTest.java

Tests cover:

Run tests:

mvnw test -Dtest=HIPPSValveTest

Standards and References

Industry Standards

SIL Requirements

SIL Level PFD (Probability of Failure on Demand) Typical Application
SIL 1 10⁻¹ to 10⁻² Low risk, 1oo1 voting
SIL 2 10⁻² to 10⁻³ Medium risk, 1oo2 or 2oo3 voting
SIL 3 10⁻³ to 10⁻⁴ High risk, 2oo3 voting

Summary

HIPPS in NeqSim provides:

Key Advantage: HIPPS prevents overpressure before it occurs, eliminating flaring and protecting equipment, while PSVs relieve pressure after it exceeds safe limits.

For safety-critical applications, HIPPS + PSV provides defense-in-depth protection strategy.

Author

Implementation follows NeqSim architecture patterns and coding standards for process safety simulation.

HIPPS Safety Logic

HIPPS Safety Logic Implementation

Overview

High Integrity Pressure Protection System (HIPPS) is a Safety Instrumented System (SIS) designed to prevent overpressure in process equipment by rapidly closing isolation valves when pressure exceeds safe limits. HIPPS acts as the first line of defense before pressure relief devices (PSVs) or Emergency Shutdown (ESD) systems are activated.

Key Concepts

What is HIPPS?

HIPPS is an automated safety system that:

HIPPS vs ESD

Feature HIPPS ESD
Activation Pressure 90-95% MAOP 98-99% MAOP
Primary Function Prevent overpressure Emergency shutdown
Response Time <2 seconds 2-10 seconds
Scope Local pressure protection Full facility shutdown
SIL Level SIL 2 or SIL 3 SIL 1 or SIL 2
Typical Voting 2oo3 1oo2 or 2oo3

Defense in Depth

A typical pressure safety system has multiple layers:

  1. Process Control System (PCS) - Normal control at 80-85% MAOP
  2. HIPPS - First safety layer at 90-95% MAOP
  3. ESD - Backup safety layer at 98% MAOP
  4. Pressure Relief Valves (PSVs) - Last resort at 100%+ MAOP

Architecture

Class Structure

HIPPSLogic (implements ProcessLogic)
├── VotingLogic (enum: 1oo1, 1oo2, 2oo2, 2oo3, 2oo4, 3oo4)
├── List<Detector> (pressure transmitters)
├── ThrottlingValve (isolation valve)
└── ProcessLogic (escalation logic - typically ESD)

Key Components

1. Pressure Sensors (Detectors)

2. Logic Solver

3. Final Element

Implementation

Basic HIPPS Setup

// Create HIPPS with 2oo3 voting (SIL 3)
HIPPSLogic hipps = new HIPPSLogic("HIPPS-101", VotingLogic.TWO_OUT_OF_THREE);

// Add pressure transmitters
double hippsSetpoint = 95.0; // 95 bara (95% of 100 bara MAOP)
Detector pt1 = new Detector("PT-101A", DetectorType.PRESSURE, 
                            AlarmLevel.HIGH_HIGH, hippsSetpoint, "bara");
Detector pt2 = new Detector("PT-101B", DetectorType.PRESSURE, 
                            AlarmLevel.HIGH_HIGH, hippsSetpoint, "bara");
Detector pt3 = new Detector("PT-101C", DetectorType.PRESSURE, 
                            AlarmLevel.HIGH_HIGH, hippsSetpoint, "bara");

hipps.addPressureSensor(pt1);
hipps.addPressureSensor(pt2);
hipps.addPressureSensor(pt3);

// Set isolation valve
ThrottlingValve isolationValve = new ThrottlingValve("HIPPS-Isolation-Valve", stream);
hipps.setIsolationValve(isolationValve);

HIPPS with ESD Escalation

// Create ESD logic as backup
ESDLogic esdLogic = new ESDLogic("ESD Level 1");
esdLogic.addAction(new TripValveAction(esdValve), 0.0);

// Link HIPPS to escalate to ESD after 5 seconds if pressure remains high
hipps.linkToEscalationLogic(esdLogic, 5.0);

Simulation Loop

// In transient simulation
for (double time = 0; time < totalTime; time += timeStep) {
    // Run process equipment
    stream.run();
    isolationValve.run();

    // Get pressure from process
    double pressure = stream.getPressure();

    // Update HIPPS (all three sensors)
    hipps.update(pressure, pressure, pressure);

    // Execute HIPPS logic (checks for escalation)
    hipps.execute(timeStep);

    // Check status
    if (hipps.isTripped()) {
        System.out.println("HIPPS ACTIVATED: Isolation valve closed");
    }

    if (hipps.hasEscalated()) {
        System.out.println("ESCALATED TO ESD: HIPPS failed to control pressure");
    }
}

Voting Logic Patterns

Standard Patterns

Pattern Description Spurious Trip Rate Safety Integrity Typical Use
1oo1 1 out of 1 must trip High Low Low criticality
1oo2 1 out of 2 must trip Medium Medium Standard applications
2oo2 2 out of 2 must trip Very Low Low High availability needed
2oo3 2 out of 3 must trip Low High HIPPS standard (SIL 3)
2oo4 2 out of 4 must trip Very Low High Critical applications
3oo4 3 out of 4 must trip Low Very High Ultra-high reliability

Why 2oo3 for HIPPS?

The 2oo3 voting pattern is the industry standard for HIPPS because:

  1. High Safety Integrity: Two sensors must agree before trip (reduces false trips)
  2. Fault Tolerance: System remains operational if one sensor fails
  3. Maintenance Capability: One sensor can be bypassed without compromising safety
  4. Balanced Availability: Low spurious trip rate means fewer production interruptions
  5. SIL 3 Capable: Meets requirements for high-criticality applications

Configuration Options

Bypass Management

// Bypass one sensor for maintenance (max 1 allowed)
Detector pt1 = hipps.getPressureSensor(0);
pt1.setBypass(true);

// Set maximum bypassed sensors (default is 1)
hipps.setMaxBypassedSensors(1);

Valve Closure Time

// Set target closure time (default 2 seconds)
hipps.setValveClosureTime(1.5); // Very fast closure

Manual Override

// Override HIPPS (inhibit trip function)
// WARNING: Requires management approval and risk assessment
hipps.setOverride(true);

// Check override status
if (hipps.isOverridden()) {
    System.out.println("WARNING: HIPPS is overridden");
}

Reset After Trip

// Reset HIPPS after pressure returns to normal
if (hipps.reset()) {
    System.out.println("HIPPS reset successful");
} else {
    System.out.println("Cannot reset - pressure still high");
}

Safety Considerations

SIL (Safety Integrity Level)

HIPPS typically requires SIL 2 or SIL 3 per IEC 61511:

Design Requirements

  1. Independent Sensors: Three independent pressure transmitters
  2. Diverse Measurement: Consider different sensor technologies
  3. Rapid Response: Valve closure <2 seconds
  4. Fail-Safe Design: De-energize to close (fail-closed)
  5. Regular Testing: Partial stroke testing, full stroke testing
  6. Bypass Constraints: Maximum 1 sensor bypassed at a time
  7. Escalation: Backup ESD system if HIPPS fails

Common Failure Modes

Failure Mode Detection Mitigation
Sensor failure Self-diagnostics, voting 2oo3 voting, regular testing
Valve failure to close Position feedback, escalation ESD backup, proof testing
Logic solver failure Watchdog, self-test Redundant processors
Common cause failure Design diversity Different sensor technologies

Standards Compliance

IEC 61511 (Process Industry SIS)

IEC 61508 (Functional Safety)

ISA-84 / ANSI/ISA-84.00.01

Performance Metrics

Key Performance Indicators

// Trip statistics
double tripTime = hipps.getTimeSinceTrip();
boolean hasEscalated = hipps.hasEscalated();

// Sensor status
int trippedCount = 0;
int bypassedCount = 0;
for (Detector sensor : hipps.getPressureSensors()) {
    if (sensor.isTripped()) trippedCount++;
    if (sensor.isBypassed()) bypassedCount++;
}

Typical Metrics to Track

Example Scenarios

Scenario 1: Normal HIPPS Trip

Pressure: 50 bara → 96 bara (exceeds 95 bara setpoint)
Result: 
  - 3/3 sensors trip
  - 2oo3 voting satisfied
  - Isolation valve closes in <2 seconds
  - Pressure controlled
  - ESD not activated

Scenario 2: Single Sensor Failure

Pressure: 50 bara → 96 bara
Sensor Status:
  - PT-101A: TRIPPED
  - PT-101B: TRIPPED
  - PT-101C: FAULTY (not counted)
Result:
  - 2/2 active sensors trip
  - 2oo3 voting satisfied (excludes faulty sensor)
  - HIPPS activates successfully

Scenario 3: HIPPS Failure - ESD Escalation

Pressure: 50 bara → 96 bara
HIPPS: Trips and closes valve
Pressure: Remains at 96 bara (valve failure)
Time: 5 seconds elapsed
Result:
  - HIPPS escalation timer expires
  - ESD activated automatically
  - Full shutdown initiated

Scenario 4: Maintenance Bypass

Sensor Status:
  - PT-101A: BYPASSED (maintenance)
  - PT-101B: NORMAL
  - PT-101C: NORMAL
Pressure: 50 bara → 96 bara
Result:
  - PT-101B: TRIPPED
  - PT-101C: TRIPPED
  - 2/2 active sensors trip
  - 2oo3 voting satisfied
  - HIPPS activates successfully with 1 sensor bypassed

Best Practices

Design Phase

  1. SIL Determination: Perform LOPA to determine required SIL
  2. Voting Selection: Use 2oo3 for SIL 3, 1oo2 for SIL 2
  3. Setpoint Calculation: 90-95% MAOP (below ESD, above control)
  4. Valve Sizing: Full-bore valve for rapid closure
  5. Response Time Analysis: Model full loop response including valve stroking

Implementation Phase

  1. Sensor Installation: Independent tapping points, avoid process deadlegs
  2. Calibration: Factory calibration with certificates
  3. Logic Configuration: Test voting logic thoroughly
  4. Valve Testing: Partial stroke test before commissioning
  5. Integration Testing: Test escalation to ESD

Operational Phase

  1. Bypass Procedures: Management of change (MOC) for bypasses
  2. Proof Testing: Annual full stroke test, quarterly partial stroke test
  3. Performance Monitoring: Track spurious trips, response times
  4. Incident Investigation: Analyze all HIPPS activations
  5. Training: Regular operator training on HIPPS operation

Maintenance Phase

  1. Sensor Calibration: Annual verification
  2. Valve Maintenance: Lubrication, seal replacement per schedule
  3. Logic Solver Testing: Self-diagnostics, watchdog verification
  4. Spare Parts: Critical spares available (sensors, valves, solenoids)

Future Enhancements

Planned Features

  1. Demand Rate Tracking: Calculate PFD based on activation history
  2. Proof Test Integration: Schedule and track proof test activities
  3. Partial Stroke Testing: Automated PST for valves
  4. Diagnostic Coverage: Calculate SFF (Safe Failure Fraction)
  5. LOPA Integration: Link to risk analysis tools
  6. Performance Dashboards: Real-time KPI visualization

Advanced Capabilities

References

See Also

Chapter 33: ESD & Fire Systems

ESD Blowdown

ESD Blowdown System Implementation

Overview

This implementation adds a complete Emergency Shutdown (ESD) blowdown system to NeqSim, including:

  1. BlowdownValve (BDValve) - A normally-closed valve that opens during ESD events
  2. PushButton - A manual instrument that can activate the blowdown valve
  3. Orifice - ISO 5167 pressure-driven flow restriction for controlled depressurization
  4. Flare Integration - Safe disposal of blowdown gas

Components Created

1. BlowdownValve Class

Location: src/main/java/neqsim/process/equipment/valve/BlowdownValve.java

Key Features:

Usage Example:

// Create blowdown valve
BlowdownValve bdValve = new BlowdownValve("BD-101", blowdownStream);
bdValve.setOpeningTime(5.0); // 5 seconds to fully open

// Activate in emergency
bdValve.activate();

// Check status
if (bdValve.isActivated()) {
    System.out.println("Blowdown in progress");
}

2. PushButton Instrument

Location: src/main/java/neqsim/process/measurementdevice/PushButton.java

Key Features:

Usage Example:

// Create push button linked to BD valve
PushButton esdButton = new PushButton("ESD-PB-101", bdValve);

// Operator pushes button in emergency
esdButton.push(); // Automatically activates linked BD valve

// Check button state
if (esdButton.isPushed()) {
    System.out.println("ESD button pushed - blowdown active");
}

// Reset button (valve stays activated for safety)
esdButton.reset();

3. Orifice Class (Transient Mode)

Location: src/main/java/neqsim/process/equipment/diffpressure/Orifice.java

Key Features:

Transient Behavior: In dynamic/transient mode, the orifice acts as a pressure-driven flow restriction device. Unlike steady-state mode where flow may be specified upstream, in transient mode the orifice calculates and determines the actual flow based on the pressure differential:

Flow Equation (ISO 5167):

m = A × C × ε × √(2ρΔP / (1 - β⁴))

Where:

Usage Example:

// Create orifice with ISO 5167 parameters
// Orifice(name, pipeDiameter, orificeDiameter, P_upstream_ref, P_downstream, dischargeCoeff)
Orifice bdOrifice = new Orifice("BD-Orifice", 
    0.45,  // Pipe diameter (m)
    0.18,  // Orifice diameter (m)
    60.0,  // Upstream reference pressure (bara)
    1.5,   // Downstream pressure - flare header (bara)
    0.61); // Discharge coefficient

// In transient simulation, the orifice automatically calculates flow
bdOrifice.runTransient(timeStep, uuid);

// As separator pressure drops, orifice flow decreases
// Example: ΔP 22.5→4.2 bar causes flow to drop 29,110→7,756 kg/hr (73% reduction)

Important Notes:

4. Complete ESD System Test

Location: src/test/java/neqsim/process/equipment/valve/BlowdownValveESDSystemTest.java

Test Coverage:

Key Scenario:

  1. Normal operation with gas to process
  2. ESD activation via push button at t=10s
  3. Splitter redirects gas from process to blowdown
  4. BD valve opens over 5 seconds
  5. Gas flows through orifice to flare - flow rate automatically decreases as separator depressurizes
  6. Orifice demonstrates ISO 5167 behavior: flow ∝ √(ΔP)
  7. Flare tracks heat release and emissions

5. Demonstration Example

Location: src/main/java/neqsim/process/util/example/ESDBlowdownSystemExample.java

A standalone runnable example showing:

System Architecture

Separator (50 bara)
    |
    v
Gas Splitter
    |
    +----> Process Stream (normal operation)
    |
    +----> Blowdown Stream
              |
              v
         BD Valve (BD-101) <--- ESD Push Button (ESD-PB-101)
              |
              v
         BD Orifice - ISO 5167 pressure-driven flow
              |      Flow = f(√ΔP) - decreases as P₁ drops
              |      Controls depressurization rate
              v
         Flare Header (1.5 bara)
              |
              v
         Flare - Combusts blowdown gas

Physics and Behavior

Orifice Flow Dynamics

In transient simulations, the orifice flow is pressure-driven following ISO 5167:

Flow Rate ∝ √(ΔP)

Example from ESD Simulation:

This demonstrates realistic depressurization behavior where:

  1. Initial flow is high due to large pressure differential
  2. As separator depressurizes, driving force (ΔP) decreases
  3. Flow rate automatically reduces following √(ΔP) relationship
  4. Prevents excessive depressurization rates at low pressures
  5. Provides controlled, safe blowdown progression

Why This Matters for Safety Studies

Realistic Depressurization Profiles:

PSV/Rupture Disc Scenarios:

Key Design Features

Safety

Flexibility

Monitoring

Running the Example

Via Main Method:

mvn exec:java -Dexec.mainClass="neqsim.process.util.example.ESDBlowdownSystemExample"

Via Test:

mvn test -Dtest=BlowdownValveESDSystemTest

Expected Output

The system will show:

  1. Configuration - System setup and parameters
  2. Normal Operation - Initial steady state
  3. ESD Activation - Push button activation
  4. Dynamic Simulation - Blowdown progression over time
  5. Summary - Total gas blown down, heat released, emissions

Sample Output:

╔════════════════════════════════════════════════════════════════╗
║        EMERGENCY SHUTDOWN (ESD) BLOWDOWN SYSTEM TEST          ║
╚════════════════════════════════════════════════════════════════╝

═══ SYSTEM CONFIGURATION ═══
Separator operating pressure: 50.0 bara
Gas flow rate: 10000.0 kg/hr
Blowdown valve: BD-101 (normally closed)
ESD Push Button: ESD-PB-101 (linked to BD-101)
BD Orifice Cv: 150.0
Flare header pressure: 1.5 bara
BD valve opening time: 5.0 seconds

>>> OPERATOR PUSHES ESD BUTTON - BLOWDOWN INITIATED <<<

Time (s) | Sep Press | Process Flow | BD Flow    | BD Opening | Flare Flow | Heat Release
         | (bara)    | (kg/hr)      | (kg/hr)    | (%)        | (kg/hr)    | (MW)
---------|-----------|--------------|------------|------------|------------|-------------
    10.0 |     50.00 |         0.0  |    10000.0 |        0.0 |        0.0 |         0.00
    12.0 |     50.00 |         0.0  |    10000.0 |       40.0 |     4000.0 |        29.45
    14.0 |     50.00 |         0.0  |    10000.0 |       80.0 |     8000.0 |        58.91
    16.0 |     50.00 |         0.0  |    10000.0 |      100.0 |    10000.0 |        73.63

═══ BLOWDOWN SUMMARY ═══
Maximum blowdown flow: 10000.0 kg/hr
Total gas blown down: 138.9 kg
Total heat released: 3.21 GJ
Total CO2 emissions: 382.5 kg

✓ ESD push button successfully triggered blowdown
✓ BD valve automatically activated by push button
✓ Controlled depressurization through BD orifice

Integration with Existing NeqSim Components

Compatible Equipment:

Measurement Integration:

Testing

All tests are in BlowdownValveESDSystemTest.java:

  1. testESDBlowdownSystem() - Complete dynamic simulation with orifice flow validation
  2. testPushButtonOperation() - Button activation and reset
  3. testPushButtonManualMode() - Manual control mode
  4. testBlowdownValveOperation() - Valve behavior
  5. testMultipleBlowdownSources() - Multiple BD sources to common flare
  6. testPressureReliefViaBlowdown() - Pressure buildup and relief scenario

Orifice Flow Validation: The tests verify that orifice flow correctly responds to pressure changes:

Run all tests:

mvn test -Dtest=BlowdownValveESDSystemTest

Future Enhancements

Potential additions:

Author

Implementation follows NeqSim architecture patterns and coding standards.

Pressure Monitoring

Pressure Monitoring in ESD Blowdown System

Overview

The ESD blowdown system now includes comprehensive pressure monitoring during transient calculations to verify that the separator pressure is properly released via the blowdown valve.

Key Features

1. Continuous Pressure Monitoring

The system tracks separator pressure at every time step:

2. Pressure Relief Verification

Automated checks verify:

Usage Examples

Example 1: Basic Pressure Monitoring (ESDBlowdownSystemExample.java)

// Track pressure during blowdown
double initialPressure = separator.getGasOutStream().getPressure("bara");
double minPressure = initialPressure;
double maxPressure = initialPressure;

for (double time = 0.0; time <= totalTime; time += timeStep) {
    // Run equipment in transient mode
    separator.run();
    bdValve.runTransient(timeStep, uuid);
    // ... other equipment

    // Monitor separator pressure
    double currentPressure = separator.getGasOutStream().getPressure("bara");
    minPressure = Math.min(minPressure, currentPressure);
    maxPressure = Math.max(maxPressure, currentPressure);

    System.out.printf("Time: %.1fs, Pressure: %.2f bara%n", time, currentPressure);
}

// Verify pressure relief
System.out.printf("Pressure drop: %.2f bar (%.1f%% reduction)%n", 
    initialPressure - finalPressure, 
    100.0 * (initialPressure - finalPressure) / initialPressure);

Example 2: Pressure Buildup and Relief Test

The new test testPressureReliefViaBlowdown() demonstrates:

Scenario:

  1. Normal Operation (0-5s) - Separator at 60 bara, outlet valve 50% open
  2. Outlet Blockage (5-10s) - Outlet valve closes to 5%, pressure rises
  3. ESD Activation (10s) - Push button activates blowdown valve
  4. Depressurization (10-30s) - Pressure drops as gas flows to flare

Output:

═══ PRESSURE PROFILE ═══
Initial pressure: 60.00 bara
Maximum pressure reached: 65.23 bara
Pressure at ESD activation: 65.23 bara
Final pressure: 52.18 bara
Pressure rise before ESD: 5.23 bar (8.7% increase)
Pressure drop after ESD: 13.05 bar (20.0% reduction)

✓ Pressure buildup detected: 60.00 bara → 65.23 bara
✓ ESD successfully activated at 65.23 bara
✓ Pressure relieved to 52.18 bara (20.0% reduction)

Monitoring Points

Separator Pressure

double sepPressure = separator.getPressure("bara");
// or
double sepPressure = separator.getGasOutStream().getPressure("bara");

Blowdown Flow Rate

double bdFlow = blowdownStream.getFlowRate("kg/hr");

Valve Opening

double opening = bdValve.getPercentValveOpening(); // 0-100%
boolean isOpen = bdValve.getPercentValveOpening() > 90.0;

Flare Load

double heatRelease = flare.getHeatDuty("MW");
double totalGas = flare.getCumulativeGasBurned("kg");

Transient Calculation Pattern

// Initialize tracking variables
double initialPressure = separator.getPressure("bara");
double maxPressure = initialPressure;
double finalPressure = initialPressure;

// Time loop
double timeStep = 0.5; // seconds
for (double time = 0.0; time <= simulationTime; time += timeStep) {

    // 1. Update process conditions (if needed)
    if (time >= eventTime) {
        esdButton.push(); // Trigger ESD
        splitter.setSplitFactors(new double[] {0.0, 1.0});
    }

    // 2. Run all equipment
    feedStream.run();
    separator.run();
    // ... other steady-state equipment

    // 3. Run transient equipment
    bdValve.runTransient(timeStep, UUID.randomUUID());

    // ... more equipment

    flare.run();
    flare.updateCumulative(timeStep); // Track cumulative values

    // 4. Monitor and record
    double currentPressure = separator.getPressure("bara");
    maxPressure = Math.max(maxPressure, currentPressure);
    finalPressure = currentPressure;

    // 5. Output (optional)
    System.out.printf("t=%.1fs: P=%.2f bara, BD=%.1f%%, Flow=%.1f kg/hr%n",
        time, currentPressure, bdValve.getPercentValveOpening(), 
        blowdownStream.getFlowRate("kg/hr"));
}

// 6. Verify results
if (finalPressure < initialPressure) {
    System.out.println("✓ Pressure successfully relieved");
}

Verification Checks

Automated Assertions

// Pressure should decrease after blowdown
assertTrue(finalPressure < pressureAtEsdActivation, 
    "Pressure should decrease after ESD activation");

// Valve should be activated
assertTrue(bdValve.isActivated(), "BD valve should be activated");

// Gas should flow to flare
assertTrue(flare.getCumulativeGasBurned("kg") > 0, 
    "Gas should flow to flare");

// Valve should be fully open
assertTrue(bdValve.getPercentValveOpening() > 90.0, 
    "BD valve should be nearly fully open");

Console Output Verification

The system provides detailed output showing:

Running the Examples

Run the basic example with pressure monitoring:

mvn exec:java -Dexec.mainClass="neqsim.process.util.example.ESDBlowdownSystemExample"

Run the comprehensive test suite:

# Run all blowdown tests
mvn test -Dtest=BlowdownValveESDSystemTest

# Run specific pressure relief test
mvn test -Dtest=BlowdownValveESDSystemTest#testPressureReliefViaBlowdown

Expected Results

When the blowdown system is working correctly, you should observe:

  1. Pressure Decrease - Separator pressure drops after ESD activation
  2. Valve Opening - BD valve gradually opens to 100% over configured time
  3. Gas Flow - Significant flow rate through blowdown valve to flare
  4. Heat Release - Flare shows increasing heat duty as gas combusts
  5. Cumulative Tracking - Total gas burned and emissions tracked

Typical Values

Troubleshooting

If pressure is not relieved:

  1. Check BD valve is activated: bdValve.isActivated()
  2. Verify valve opening: bdValve.getPercentValveOpening()
  3. Check flow to blowdown: blowdownStream.getFlowRate("kg/hr")
  4. Verify splitter redirects flow: splitter.setSplitFactors(...)
  5. Check orifice pressure drop: bdOrifice.getOutletPressure()

Integration with Process Systems

The pressure monitoring integrates with:

Summary

The enhanced ESD blowdown system provides:

This ensures that the blowdown valve correctly relieves pressure when activated, protecting equipment from overpressure conditions.

Fire Heat Transfer

Fire and blowdown calculation enhancements

This note summarizes how to extend NeqSim blowdown calculations with rigorous fire exposure models.

Heat transfer modelling (wetted vs. unwetted)

Fire heat loads

Vessel rupture assessment (Scandpower guideline)

These utilities are designed to plug into existing blowdown scenarios and flare models so that transient depressurization can be tracked alongside external fire loads and structural integrity. The helper SeparatorFireExposure.evaluate(...) (or separator.evaluateFireExposure(...)) wraps area lookup, heat-load estimation, wall temperatures, and rupture checks into a single call so the fire calculations can be dropped into a process simulation loop without hand-wiring each piece. If you want the separator inventory to warm up from the calculated fire load, set the duty on the separator (separator.setDuty(fireResult.totalFireHeat())) and call separator.runTransient(...) so the energy balance absorbs that heat during the timestep.

Separator fire blowdown worked example

The runnable SeparatorFireDepressurizationExample (src/main/java/neqsim/process/util/example/SeparatorFireDepressurizationExample.java) illustrates how to couple a separator depressurization to the flare with the fire utilities:

To run the illustration:

mvn -pl . -Dexec.mainClass="neqsim.process.util.example.SeparatorFireDepressurizationExample" exec:java

The output prints separator pressure, flow to flare, wall temperatures, rupture margin, and fire heat metrics at each timestep so the fire impact on depressurization can be reviewed end-to-end.

Fire Blowdown

Fire/blowdown helper capabilities

This note summarizes the current fire, heat-transfer, and structural integrity helpers available in NeqSim and how to apply them in process simulations.

Fire heat-load modelling

Heat-transfer / wall-temperature treatment

Structural integrity / rupture logic

Integration with process equipment

Chapter 34: Integrated Safety Systems

Integrated Safety

Integrated Safety Systems Example

Overview

The IntegratedSafetySystemExample demonstrates a comprehensive safety system implementation for process facilities, incorporating multiple layers of protection following the principles of Safety Instrumented Systems (SIS) and Defense in Depth.

Safety Architecture

Protection Layers (Onion Model)

The example implements a complete safety architecture with four distinct layers:

┌─────────────────────────────────────┐
│  1. High Pressure Alarm (SIL-1)     │ ← 55.0 bara
│     ├─ Operator intervention        │
│     └─ Alarms and warnings          │
├─────────────────────────────────────┤
│  2. ESD System (SIL-2)               │ ← 58.0 bara
│     ├─ Emergency shutdown            │
│     ├─ Blowdown activation           │
│     └─ Fire detection response       │
├─────────────────────────────────────┤
│  3. HIPPS (SIL-3)                    │ ← 60.0 bara
│     ├─ High integrity protection     │
│     ├─ Fast-acting valve closure     │
│     └─ Redundant pressure monitoring │
├─────────────────────────────────────┤
│  4. PSV (Mechanical)                 │ ← 65.0 bara
│     └─ Final mechanical relief       │
└─────────────────────────────────────┘

System Components

1. High Integrity Pressure Protection System (HIPPS)

Purpose: Prevent overpressure by rapidly closing inlet valve before pressure reaches dangerous levels.

Safety Integrity Level: SIL-3 (PFD: 0.0001-0.001)

Features:

Implementation:

HIPPSController hippsController = 
    new HIPPSController("HIPPS-Logic-001", hippsPT1, hippsPT2, hippsValve);

// 2oo2 voting for higher integrity
if (p1 >= HIPPS_ACTIVATION_PRESSURE && p2 >= HIPPS_ACTIVATION_PRESSURE) {
    hippsValve.setPercentValveOpening(0.0); // Close immediately
}

2. Emergency Shutdown (ESD) System

Purpose: Shut down process and activate blowdown when emergency conditions are detected.

Safety Integrity Level: SIL-2 (PFD: 0.001-0.01)

Activation Conditions:

Actions on Activation:

  1. Close inlet isolation valve
  2. Activate blowdown valve
  3. Redirect gas flow to flare system

Implementation:

ESDController esdController = 
    new ESDController("ESD-Logic-201", separatorPT, separatorTT, 
                      esdButton, esdInletValve, bdValve);

// Multiple trigger conditions
if (pressure >= HIGH_HIGH_PRESSURE_ALARM || 
    temperature >= FIRE_DETECTION_TEMPERATURE || 
    manualESD.isPushed()) {
    esdValve.setPercentValveOpening(0.0);
    blowdownValve.activate();
}

3. Fire Detection System

Purpose: Detect fire conditions and trigger ESD.

Configuration:

Implementation:

FireDetectionSystem fireSystem = 
    new FireDetectionSystem(
        new TemperatureTransmitter[] {fireTT1, fireTT2, fireTT3}, 
        2  // voting threshold
    );

4. Blowdown System

Purpose: Rapidly depressurize equipment during emergency situations.

Features:

Implementation:

BlowdownValve bdValve = new BlowdownValve("BD-301", blowdownStream);
bdValve.setOpeningTime(5.0);
bdValve.setCv(250.0);

5. Pressure Safety Valve (PSV)

Purpose: Final mechanical protection layer - relieves pressure if all other systems fail.

Characteristics:

Implementation:

SafetyValve psv = new SafetyValve("PSV-401", separatorGasOut);
psv.setPressureSpec(65.0);
psv.setFullOpenPressure(67.0);
psv.setBlowdown(7.0);

6. Flare System

Purpose: Safely combust and dispose of emergency relief gases.

Features:

Safety Scenarios

The example demonstrates four operational scenarios:

Scenario 1: Normal Operation

Expected Behavior:

Scenario 2: HIPPS Activation (SIL-3)

Expected Behavior:

Scenario 3: ESD and Blowdown (SIL-2)

Expected Behavior:

Scenario 4: PSV Relief (Final Protection)

Expected Behavior:

SIL Requirements and Implementation

Safety Integrity Levels (IEC 61508/61511)

SIL Level PFD Range RRF Range Implementation
SIL-3 10⁻⁴ to 10⁻³ 10,000 to 1,000 HIPPS with 2oo2 voting
SIL-2 10⁻³ to 10⁻² 1,000 to 100 ESD with redundant sensors
SIL-1 10⁻² to 10⁻¹ 100 to 10 Alarms with operator action

PFD: Probability of Failure on Demand
RRF: Risk Reduction Factor

Voting Architectures

2oo2 (HIPPS - SIL-3):

2oo3 (Fire Detection):

Key Design Principles

1. Defense in Depth

Multiple independent protection layers ensure safety even if individual layers fail.

2. Fail-Safe Design

3. Separation of Functions

4. Diversity

Usage Example

// Run the integrated safety system example
java neqsim.process.util.example.IntegratedSafetySystemExample

// Expected output:
// - System configuration summary
// - Four safety scenarios with detailed monitoring
// - Verification of each protection layer

Output Interpretation

Normal Operation

HIPPS status: NORMAL
ESD status: NORMAL
Fire detection: NORMAL
PSV status: CLOSED

During HIPPS Activation

>>> HIPPS ACTIVATED (SIL-3) - Both pressure sensors confirm <<<
HIPPS Valve: Closing from 100% to 0%
Separator pressure: Controlled below 60 bara

During ESD and Blowdown

>>> ESD ACTIVATED (SIL-2) - Manual Push Button <<<
ESD inlet valve: Closing to 0%
BD valve: Opening to 100%
Blowdown flow: Increasing to flare
Separator pressure: Decreasing

PSV Relief

Sep P > 65.0 bara
PSV status: RELIEVING
PSV Flow: High flow to flare

Performance Metrics

The example tracks and reports:

  1. Pressure profiles during each scenario
  2. Valve opening percentages over time
  3. Flow rates to flare system
  4. Cumulative emissions (gas burned, CO₂, heat)
  5. Response times of safety systems

API Reference

Key Classes Used

Best Practices

  1. Always implement multiple protection layers - Never rely on a single safety device
  2. Use appropriate SIL ratings - Match safety system integrity to risk level
  3. Test safety systems regularly - Proof test intervals per IEC 61511
  4. Document all safety logic - Clear, auditable control algorithms
  5. Monitor performance - Track activation rates and failure modes
  6. Train operators - Ensure understanding of safety system behavior

Further Development

This example can be extended to include:

References

SIS Logic

Safety Instrumented System (SIS) Logic Implementation

Overview

Implemented a comprehensive Safety Instrumented System (SIS) framework for NeqSim following IEC 61511 standards, enabling realistic fire and gas detection with voting logic and automatic ESD triggering.

Components Implemented

1. VotingLogic Enum (neqsim.process.logic.sis)

Represents standard voting patterns for redundant sensors:

VotingLogic voting = VotingLogic.TWO_OUT_OF_THREE;
boolean shouldTrip = voting.evaluate(trippedCount); // true if ≥2 tripped

2. Detector Class (neqsim.process.logic.sis)

Represents fire, gas, or process detectors with:

Detector fireDetector = new Detector("FD-101", DetectorType.FIRE, 
                                      AlarmLevel.HIGH, 60.0, "°C");
fireDetector.update(temperatureValue); // Evaluates trip condition
if (fireDetector.isTripped()) {
  // Detector has tripped
}

3. SafetyInstrumentedFunction (SIF) Class (neqsim.process.logic.sis)

Complete SIF implementation following IEC 61511 architecture:

Key Features:

// Create fire SIF with 2oo3 voting
SafetyInstrumentedFunction fireSIF = 
    new SafetyInstrumentedFunction("Fire Detection SIF", VotingLogic.TWO_OUT_OF_THREE);

// Add 3 detectors
fireSIF.addDetector(new Detector("FD-101", DetectorType.FIRE, AlarmLevel.HIGH, 60.0, "°C"));
fireSIF.addDetector(new Detector("FD-102", DetectorType.FIRE, AlarmLevel.HIGH, 60.0, "°C"));
fireSIF.addDetector(new Detector("FD-103", DetectorType.FIRE, AlarmLevel.HIGH, 60.0, "°C"));

// Link to ESD logic
fireSIF.linkToLogic(esdLogic);

// Update detector values
fireSIF.update(temp1, temp2, temp3);

// Check if SIF tripped (2 of 3 detectors exceeded setpoint)
if (fireSIF.isTripped()) {
  // ESD logic automatically activated
}

IEC 61511 Compliance

Safety Integrity Level (SIL) Features

  1. Redundancy

    • Multiple detectors per hazard
    • Voting logic prevents spurious trips
    • One detector can fail without losing safety function
  2. Bypass Management

    • Maximum bypassed detectors enforced (typically 1)
    • 2oo3 with 1 bypassed becomes effectively 2oo2
    • Safety function maintained during maintenance
  3. Fault Handling

    • Faulty detectors excluded from voting
    • System enters FAILED state if too many bypassed
    • Alarm on fault conditions
  4. Reset Logic

    • Requires all trip conditions cleared
    • Operator acknowledgment
    • Linked logic sequences also reset

Example: Fire & Gas Detection System

The FireGasSISExample demonstrates a complete safety system:

System Architecture

┌─────────────────────────────────┐
│  Fire Detection (2oo3)          │
│  ┌─────┐ ┌─────┐ ┌─────┐       │
│  │FD-101│ │FD-102│ │FD-103│     │
│  └──┬──┘ └──┬──┘ └──┬──┘       │
│     └───────┴───────┘            │
│         2/3 must trip            │
└──────────────┬──────────────────┘
               │
               ├─────────┐
               │         │
┌──────────────▼───────────────────┐
│  Gas Detection (2oo3)            │
│  ┌─────┐ ┌─────┐ ┌─────┐        │
│  │GD-101│ │GD-102│ │GD-103│      │
│  └──┬──┘ └──┬──┘ └──┬──┘        │
│     └───────┴───────┘             │
│         2/3 must trip             │
└──────────────┬───────────────────┘
               │
               ▼
      ┌────────────────┐
      │  ESD Logic     │
      │  1. Trip ESD   │
      │  2. Open BD    │
      │  3. Redirect   │
      └────────────────┘

Scenarios Demonstrated

Scenario 1: Normal Operation

Scenario 2: Single Detector Trip (1/3)

Scenario 3: Fire Detected (2/3)

Scenario 4: Bypass Capability

Integration with Process Logic Framework

The SIS components integrate seamlessly with the existing process logic framework:

// Create SIF
SafetyInstrumentedFunction fireSIF = 
    new SafetyInstrumentedFunction("Fire SIF", VotingLogic.TWO_OUT_OF_THREE);
fireSIF.addDetector(fireDetector1);
fireSIF.addDetector(fireDetector2);
fireSIF.addDetector(fireDetector3);

// Create ESD logic with actions
ESDLogic esdLogic = new ESDLogic("ESD Level 1");
esdLogic.addAction(new TripValveAction(esdValve), 0.0);
esdLogic.addAction(new ActivateBlowdownAction(bdValve), 0.5);
esdLogic.addAction(new SetSplitterAction(splitter, factors), 0.0);

// Link SIF to ESD logic
fireSIF.linkToLogic(esdLogic);

// Push button can also trigger ESD
PushButton esdButton = new PushButton("ESD-PB-101");
esdButton.linkToLogic(esdLogic);

// In simulation loop:
fireSIF.update(temp1, temp2, temp3); // Automatic evaluation
// or
esdButton.push(); // Manual trigger

Key Benefits

1. Realistic Safety Systems

2. Operational Flexibility

3. Reduced Spurious Trips

4. Integration Ready

Voting Logic Comparison

Pattern Spurious Trip Rate Safety Integrity Availability Common Use
1oo1 High Low Low Low criticality
1oo2 Low Medium High Balance needed
2oo2 Very Low Low Medium Rare
2oo3 Low High High Standard choice
2oo4 Very Low High Very High Critical systems

Files Created

Core SIS Framework (3 files)

Examples (1 file)

Example Output Highlights

SCENARIO 3: FIRE DETECTED - 2oo3 VOTING SATISFIED
>>> FD-101 and FD-102 detect fire <<<
Fire Detection SIF [2oo3] - TRIPPED (2/3 tripped)
  FD-101: TRIPPED
  FD-102: TRIPPED
  FD-103: NORMAL
SIF Status: TRIPPED - ESD ACTIVATED
ESD Logic: ESD Level 1 - RUNNING (Step 1/3: Trip ESD valve ESD-XV-101)

Time (s) | Fire SIF | Gas SIF  | ESD Step | ESD Valve (%) | BD Valve (%)
---------|----------|----------|----------|---------------|-------------
     0.0 |  TRIPPED |  NORMAL  | Step 1/3 |          80.0 |          0.0
     1.0 |  TRIPPED |  NORMAL  | Step 1/3 |          60.0 |          0.0
     ...
     5.0 |  TRIPPED |  NORMAL  | Step 2/3 |           0.0 |          0.0
     ...

Future Enhancements

  1. Time-based voting - Require N detectors tripped for T seconds
  2. Demand rate tracking - Calculate SIF demand frequency
  3. Proof test tracking - Record detector testing intervals
  4. PFD calculations - Probability of Failure on Demand

Phase 3 (Advanced)

  1. Dynamic voting - Adjust voting based on operational mode
  2. Partial stroke testing - Test valves without full trip
  3. SIL verification - Built-in SIL calculations per IEC 61508
  4. LOPA integration - Layers of Protection Analysis

Standards Compliance

IEC 61511 (Functional Safety - Process Industry)

✓ SIF architecture (sensor → logic → final element) ✓ Voting logic patterns ✓ Bypass management ✓ Proof test considerations

IEC 61508 (Functional Safety - Generic)

✓ Safety integrity levels ✓ Systematic failure prevention ✓ Diagnostic coverage

ISA-84 / ANSI/ISA-84.00.01

✓ Safety instrumented systems ✓ Safety lifecycle management ✓ SIS design requirements

Conclusion

The SIS logic framework provides NeqSim with industry-standard fire and gas detection capabilities, enabling realistic simulation of safety-critical process control systems. The 2oo3 voting implementation balances safety integrity with operational availability, making it suitable for modeling high-reliability applications.

Combined with the process logic framework, NeqSim now supports comprehensive modeling of:

All following recognized international standards for process safety instrumentation.

Choke Protection

Choke Collapse PSD Protection Scenario

Overview

This document describes a critical safety scenario where an inlet choke valve (throttle valve) suddenly fails open to 100%, causing rapid pressure rise in downstream equipment. A Process Shutdown (PSD) valve monitors the pressure and automatically closes when a High-High (HIHI) alarm is triggered, protecting the system from overpressure.

Scenario Description

Normal Operation

Failure Event

  1. t = 0.0 s: Choke valve fails open to 100%
  2. t = 0-3 s: Pressure rises rapidly from 50 to 56 bara
  3. t = 2.5 s: Pressure reaches 55 bara, triggering HI alarm
  4. t = 3.0 s: Pressure exceeds HIHI setpoint, PSD valve trips and closes
  5. t > 3.0 s: PSD valve fully closed, system isolated and protected

Protection Response

Implementation

System Configuration

// High pressure feed at 100 bara
SystemInterface feedGas = new SystemSrkEos(273.15 + 40.0, 100.0);
feedGas.addComponent("methane", 85.0);
feedGas.addComponent("ethane", 8.0);
// ... add other components
feedGas.setMixingRule("classic");

Stream feedStream = new Stream("High Pressure Feed", feedGas);
feedStream.setFlowRate(5000.0, "kg/hr");
feedStream.setPressure(100.0, "bara");

// Choke valve - normally at 30% opening
ThrottlingValve chokeValve = new ThrottlingValve("Inlet Choke Valve", feedStream);
chokeValve.setPercentValveOpening(30.0);
chokeValve.setOutletPressure(50.0);

Stream chokeOutlet = new Stream("Choke Outlet", chokeValve.getOutletStream());

// PSD valve for protection
PSDValve psdValve = new PSDValve("PSD Inlet Protection", chokeOutlet);
psdValve.setPercentValveOpening(100.0);
psdValve.setClosureTime(2.0); // Fast closure

// Pressure transmitter with HIHI alarm
PressureTransmitter pressureTransmitter = new PressureTransmitter(
    "Separator Inlet PT", chokeOutlet);

AlarmConfig alarmConfig = AlarmConfig.builder()
    .highHighLimit(55.0)
    .highLimit(52.0)
    .deadband(0.5)
    .delay(1.0)
    .unit("bara")
    .build();
pressureTransmitter.setAlarmConfig(alarmConfig);

// Link PSD valve to pressure transmitter
psdValve.linkToPressureTransmitter(pressureTransmitter);

Simulating the Failure

// Run initial steady state
feedStream.run();
chokeValve.run();
psdValve.run();

// FAILURE EVENT: Choke valve fails open
chokeValve.setPercentValveOpening(100.0);

// Simulate dynamic response
double timeStep = 0.5; // 0.5 second time steps
for (double time = 0.0; time <= simulationTime; time += timeStep) {
    // Run process
    feedStream.run();
    chokeValve.run();
    chokeOutlet.run();

    // Evaluate alarm
    pressureTransmitter.evaluateAlarm(
        chokeOutlet.getPressure("bara"), timeStep, time);

    // Run PSD transient behavior
    psdValve.runTransient(timeStep, UUID.randomUUID());

    // Check if PSD tripped
    if (psdValve.hasTripped()) {
        System.out.println("PSD valve tripped at " + time + " s");
        break;
    }
}

Test Results

Choke Collapse Test

===== CHOKE COLLAPSE SCENARIO =====
Initial Configuration:
  Feed pressure: 100.0 bara
  Choke opening: 30.0% (normal operation)
  PSD opening: 100.0% (normal operation)
  PSD HIHI setpoint: 55.0 bara

Time (s) | Choke Opening | Pressure (bara) | Alarm State | PSD Opening | PSD Tripped
---------|---------------|-----------------|-------------|-------------|------------
    0.0  |     100.0%    |      50.00      |   NONE      |    100.0%   |    NO
    0.5  |     100.0%    |      51.00      |   NONE      |    100.0%   |    NO
    1.0  |     100.0%    |      52.00      |   NONE      |    100.0%   |    NO
    1.5  |     100.0%    |      53.00      |   HI        |    100.0%   |    NO
    2.0  |     100.0%    |      54.00      |   HI        |    100.0%   |    NO
    2.5  |     100.0%    |      55.00      |   HI        |    100.0%   |    NO
    3.0  |     100.0%    |      56.00      |   HIHI      |      0.0%   |    YES

Results:

Recovery Test

===== CHOKE REPAIR AND PSD RESET TEST =====
Step 1: Simulating choke collapse...
  PSD tripped at 56.0 bara

Step 2: Repairing choke valve (returning to 30% opening)...
  Choke repaired and pressure returned to 50 bara

Step 3: Attempting to open PSD valve while still tripped...
  ✓ PSD correctly prevents opening while tripped

Step 4: Resetting PSD valve...
  PSD valve reset complete

Step 5: Opening PSD valve to resume operation...
  ✓ PSD successfully opened to 100.0%

===== RESET TEST SUMMARY =====
✓ Choke collapse triggered PSD trip
✓ Choke repaired (returned to 30% opening)
✓ PSD prevented opening while tripped
✓ PSD reset successful
✓ System ready to resume normal operation

Key Features Demonstrated

1. Rapid Failure Detection

2. Automatic Protection

3. Trip Latching

4. Recovery Procedure

  1. Repair/replace failed choke valve
  2. Verify pressure has returned to safe levels
  3. Reset PSD valve to clear trip state
  4. Manually open PSD valve to resume operation

Safety Analysis

Layers of Protection

Layer Device Setpoint Action Response Time
1 HI Alarm 52 bara Operator notification Immediate
2 HIHI Alarm 55 bara Triggers PSD closure 1 second delay
3 PSD Valve On HIHI Closes to 0% 2 seconds

Effectiveness

Best Practices

1. Alarm Configuration

AlarmConfig alarmConfig = AlarmConfig.builder()
    .highHighLimit(55.0)      // Trip setpoint
    .highLimit(52.0)          // Early warning
    .deadband(0.5)            // Prevent chattering
    .delay(1.0)               // Confirmation time
    .unit("bara")
    .build();

2. PSD Valve Settings

3. Testing

This scenario complements other safety devices in NeqSim:

Comparison Matrix

Feature PSD Valve Safety Valve (PSV) Rupture Disk
Activation HIHI alarm Set pressure Burst pressure
Response Fast closure Opens to relieve Bursts open
Reset Manual Self-resetting Requires replacement
Use Case Process shutdown Overpressure relief Last-resort protection
Typical Setpoint 55 bara (HIHI) 55 bara (set) 65 bara (burst)

Integrated Protection Strategy

For complete system protection, use all three in series:

  1. PSD Valve (Primary): Isolates on abnormal process conditions
  2. PSV (Secondary): Relieves pressure if PSD fails
  3. Rupture Disk (Ultimate): Prevents catastrophic failure

Example Application

Production Separator Protection

// High pressure feed from wellhead
Stream wellheadStream = new Stream("Wellhead", highPressureGas);
wellheadStream.setPressure(100.0, "bara");

// Choke valve for flow control
ThrottlingValve chokeValve = new ThrottlingValve("Production Choke", wellheadStream);
chokeValve.setPercentValveOpening(30.0);

// PSD valve for emergency isolation
PSDValve psdValve = new PSDValve("ESD Inlet", chokeValve.getOutletStream());
psdValve.linkToPressureTransmitter(pressureTransmitter);

// Production separator
Separator separator = new Separator("Production Sep", psdValve.getOutletStream());
separator.setInternalDiameter(1.5);
separator.setSeparatorLength(4.0);

// PSV for overpressure relief
SafetyValve psv = new SafetyValve("PSV-101", separator.getGasOutStream());
psv.setSetPressure(55.0, "bara");

// Rupture disk as last resort
RuptureDisk ruptureDisk = new RuptureDisk("RD-101", separator.getGasOutStream());
ruptureDisk.setBurstPressure(65.0, "bara");

References

Test Class

Complete test implementation: neqsim.process.equipment.valve.ChokeCollapsePSDProtectionTest

Run tests:

mvn test -Dtest=ChokeCollapsePSDProtectionTest

Safety Chain Tests

Integrated HIPPS/ESD Safety Chain Tests

This repository now includes an integration test that links alarms, HIPPS isolation, and ESD depressurization logic against dynamic equipment models during a transient upset. The goal is to verify that layered safety functions respond coherently when feed pressure surges beyond high-high limits.

What the test covers

Running the integration test

Execute the JUnit test directly:

mvn -q -Dtest=IntegratedSafetyChainTransientTest test

The test lives at src/test/java/neqsim/process/util/scenario/IntegratedSafetyChainTransientTest.java and uses ProcessScenarioRunner to coordinate logic execution with the process model.

Scenario Generation

Automatic Scenario Generation

This document describes the automatic safety scenario generation infrastructure added to NeqSim.

Overview

Process safety analysis requires systematic evaluation of failure modes. The AutomaticScenarioGenerator class automatically identifies potential failures from process topology and generates scenarios for what-if analysis.

Key Features

Usage

Basic Usage

ProcessSystem process = new ProcessSystem();
// ... configure process with valves, compressors, coolers, etc. ...

AutomaticScenarioGenerator generator = new AutomaticScenarioGenerator(process);

// Enable specific failure modes
generator.addFailureModes(
    FailureMode.COOLING_LOSS,
    FailureMode.VALVE_STUCK_CLOSED,
    FailureMode.COMPRESSOR_TRIP
);

// Generate single-failure scenarios
List<ProcessSafetyScenario> singleFailures = generator.generateSingleFailures();

// Generate combination scenarios (up to 2 simultaneous failures)
List<ProcessSafetyScenario> combinations = generator.generateCombinations(2);

Convenience Methods on ProcessSystem

// Quick scenario generation
List<ProcessSafetyScenario> scenarios = process.generateSafetyScenarios();

// With combination depth
List<ProcessSafetyScenario> combinations = process.generateCombinationScenarios(2);

Failure Modes

Mode Description HAZOP Deviation Applicable To
COOLING_LOSS Complete loss of cooling No flow Coolers
HEATING_LOSS Complete loss of heating No flow Heaters
VALVE_STUCK_CLOSED Valve stuck closed No flow Valves
VALVE_STUCK_OPEN Valve stuck open More flow Valves
VALVE_CONTROL_FAILURE Control valve failure Other Control valves
COMPRESSOR_TRIP Compressor emergency stop No flow Compressors
PUMP_TRIP Pump emergency stop No flow Pumps
BLOCKED_OUTLET Downstream blockage No flow Separators
POWER_FAILURE Loss of electrical power Other All electrical
INSTRUMENT_FAILURE Instrument/control failure Other All
EXTERNAL_FIRE Fire exposure High temperature All
LOSS_OF_CONTAINMENT Leak or rupture Less pressure All

HAZOP Deviations

Deviation Description
NO_FLOW Complete loss of flow
LESS_FLOW Reduced flow rate
MORE_FLOW Increased flow rate
REVERSE_FLOW Flow in wrong direction
HIGH_PRESSURE Pressure above normal
LOW_PRESSURE / LESS_PRESSURE Pressure below normal
HIGH_TEMPERATURE Temperature above normal
LOW_TEMPERATURE Temperature below normal
HIGH_LEVEL Liquid level too high
LOW_LEVEL Liquid level too low
CONTAMINATION Unwanted substance present
CORROSION Material degradation

Running Scenarios

Manual Execution

// Generate scenarios
List<ProcessSafetyScenario> scenarios = generator.generateSingleFailures();

for (ProcessSafetyScenario scenario : scenarios) {
    // Create a copy of the process for each scenario
    ProcessSystem copy = process.copy();

    // Apply the scenario
    scenario.applyTo(copy);

    // Run simulation
    try {
        copy.run();

        // Analyze results
        analyzeScenarioResults(scenario, copy);

    } catch (Exception e) {
        // Scenario caused simulation failure - important finding!
        recordFailedScenario(scenario, e);
    }
}

Automated Execution with ScenarioRunResult

The generator provides built-in scenario execution capabilities:

// Run all single-failure scenarios automatically
List<ScenarioRunResult> results = generator.runAllSingleFailures();

// Or run specific scenarios
List<ProcessSafetyScenario> scenarios = generator.generateSingleFailures();
List<ScenarioRunResult> results = generator.runScenarios(scenarios);

// Get execution summary
String summary = generator.summarizeResults(results);
System.out.println(summary);

ScenarioRunResult

Each result contains:

Field Type Description
scenario ProcessSafetyScenario The scenario that was run
successful boolean Whether simulation completed without error
errorMessage String Error message if failed (null otherwise)
resultValues Map Key results: pressures, temperatures, levels
executionTimeMs long Execution time in milliseconds

Example Summary Output

=== Scenario Execution Summary ===
Total scenarios: 12
Successful: 10 (83.3%)
Failed: 2 (16.7%)

Failed scenarios:
  - COOLING_LOSS on Cooler-1: Simulation did not converge
  - COMPRESSOR_TRIP on MainCompressor: Exceeded iteration limit

Failure Mode Summary

String summary = generator.getFailureModeSummary();
System.out.println(summary);

// Output:
// Failure Mode Analysis Summary
// =============================
// Total potential failures: 42
//
// By Equipment Type:
//   ThrottlingValve: 12 failure modes
//   Compressor: 8 failure modes
//   Cooler: 6 failure modes
//   Separator: 8 failure modes
//   Pump: 8 failure modes

Integration with Existing Safety Framework

The AutomaticScenarioGenerator complements the existing safety framework:

Best Practices

  1. Systematic Coverage: Enable all relevant failure modes for comprehensive analysis
  2. Prioritization: Focus detailed analysis on high-consequence scenarios
  3. Combination Limits: Limit combination depth to 2-3 for practical analysis
  4. Documentation: Record all scenario results for safety case
  5. Regular Updates: Regenerate scenarios after process modifications

Chapter 35: PVT Simulation

PVT Overview

PVT Simulation Package

The pvtsimulation package provides tools for simulating standard PVT laboratory experiments used in reservoir fluid characterization.

Table of Contents


Overview

Location: neqsim.pvtsimulation

Purpose:


Package Structure

pvtsimulation/
├── simulation/                      # PVT experiment simulations
│   ├── BasePVTsimulation.java       # Base class
│   ├── SimulationInterface.java     # Interface
│   │
│   ├── SaturationPressure.java      # Bubble/dew point
│   ├── SaturationTemperature.java   # Saturation temperature
│   │
│   ├── ConstantMassExpansion.java   # CME experiment
│   ├── ConstantVolumeDepletion.java # CVD experiment
│   ├── DifferentialLiberation.java  # DL experiment
│   │
│   ├── SeparatorTest.java           # Single separator
│   ├── MultiStageSeparatorTest.java # Multi-stage separation
│   │
│   ├── SwellingTest.java            # Gas injection swelling
│   ├── SlimTubeSim.java             # Slim tube MMP
│   ├── MMPCalculator.java           # Minimum miscibility pressure
│   │
│   ├── ViscositySim.java            # Viscosity vs pressure
│   ├── ViscosityWaxOilSim.java      # Wax oil viscosity
│   ├── DensitySim.java              # Density vs pressure
│   ├── WaxFractionSim.java          # Wax precipitation
│   └── GOR.java                     # Gas-oil ratio
│
├── regression/                      # Parameter fitting
│   └── PVTRegression.java
│
├── modeltuning/                     # Model tuning
│   └── ModelTuning.java
│
├── reservoirproperties/             # Reservoir calculations
│   └── ReservoirProperties.java
│
├── util/                            # Utilities
│   ├── parameterfitting/            # Parameter fitting utilities
│   │   ├── AsphalteneOnsetFunction.java
│   │   └── AsphalteneOnsetFitting.java
│   └── PVTUtil.java
│
└── flowassurance/                   # Flow assurance analysis
    ├── AsphalteneStabilityAnalyzer.java
    ├── DeBoerAsphalteneScreening.java
    └── AsphalteneMethodComparison.java

Sub-packages

Package Documentation Description
flowassurance flowassurance/ Asphaltene stability, De Boer screening, CPA onset calculations

PVT Experiments

Saturation Pressure

Calculate bubble point or dew point pressure.

import neqsim.pvtsimulation.simulation.SaturationPressure;

SystemInterface oil = new SystemSrkEos(373.15, 200.0);
oil.addComponent("nitrogen", 0.5);
oil.addComponent("CO2", 2.0);
oil.addComponent("methane", 40.0);
oil.addComponent("ethane", 8.0);
oil.addComponent("propane", 5.0);
oil.addComponent("n-pentane", 3.0);
oil.addComponent("n-heptane", 20.0);
oil.addComponent("n-C10", 21.5);
oil.setMixingRule("classic");

SaturationPressure satPres = new SaturationPressure(oil);
satPres.setTemperature(373.15, "K");
satPres.run();

System.out.println("Saturation pressure: " + satPres.getSaturationPressure() + " bar");

Constant Mass Expansion (CME)

Simulates isothermal expansion of reservoir fluid, measuring relative volume.

import neqsim.pvtsimulation.simulation.ConstantMassExpansion;

ConstantMassExpansion cme = new ConstantMassExpansion(oil);
cme.setTemperature(373.15, "K");

// Set pressure steps
double[] pressures = {300, 250, 200, 180, 160, 140, 120, 100, 80, 60, 40};
cme.setPressures(pressures, "bara");

cme.run();

// Get results
double[] relativeVolumes = cme.getRelativeVolume();
double[] liquidVolumes = cme.getLiquidRelativeVolume();
double[] Yvalues = cme.getYfactor();
double[] densities = cme.getDensity();
double[] Zfactors = cme.getZfactor();

// Print results
System.out.println("P (bar)\tVrel\tVliq\tY\tDensity\tZ");
for (int i = 0; i < pressures.length; i++) {
    System.out.printf("%.1f\t%.4f\t%.4f\t%.4f\t%.2f\t%.4f%n",
        pressures[i], relativeVolumes[i], liquidVolumes[i],
        Yvalues[i], densities[i], Zfactors[i]);
}

CME Output Properties:

Constant Volume Depletion (CVD)

Simulates gas condensate depletion at constant volume.

import neqsim.pvtsimulation.simulation.ConstantVolumeDepletion;

SystemInterface condensate = new SystemPrEos(373.15, 400.0);
// Add components...

ConstantVolumeDepletion cvd = new ConstantVolumeDepletion(condensate);
cvd.setTemperature(373.15, "K");

double[] pressures = {400, 350, 300, 250, 200, 150, 100, 50};
cvd.setPressures(pressures, "bara");

cvd.run();

// Results
double[] liquidDropout = cvd.getLiquidVolumeFraction();
double[] cumulativeGasProduced = cvd.getCumulativeGasProduced();
double[] Zfactors = cvd.getZfactor();
double[] Bg = cvd.getBg();

CVD Output Properties:

Differential Liberation (DL)

Black oil experiment - gas released at each pressure step.

import neqsim.pvtsimulation.simulation.DifferentialLiberation;

DifferentialLiberation dl = new DifferentialLiberation(oil);
dl.setTemperature(373.15, "K");

double[] pressures = {300, 250, 200, 150, 100, 50, 1.01325};
dl.setPressures(pressures, "bara");

dl.run();

// Results
double[] Rs = dl.getRs();           // Solution GOR
double[] Bo = dl.getBo();           // Oil FVF
double[] oilDensity = dl.getOilDensity();
double[] oilViscosity = dl.getOilViscosity();
double[] gasDensity = dl.getGasDensity();
double[] gasGravity = dl.getGasGravity();
double[] Bg = dl.getBg();

System.out.println("P (bar)\tRs (Sm3/Sm3)\tBo\tρ_oil (kg/m3)");
for (int i = 0; i < pressures.length; i++) {
    System.out.printf("%.1f\t%.2f\t\t%.4f\t%.2f%n",
        pressures[i], Rs[i], Bo[i], oilDensity[i]);
}

DL Output Properties:

Separator Test

Model surface separation conditions.

import neqsim.pvtsimulation.simulation.SeparatorTest;

SeparatorTest sepTest = new SeparatorTest(oil);

// Define separator stages
double[] temperatures = {323.15, 288.15};  // K
double[] pressures = {50.0, 1.01325};       // bar

sepTest.setSeparatorConditions(temperatures, pressures);
sepTest.run();

// Results
double GOR = sepTest.getGOR();
double Bo = sepTest.getBo();
double oilDensity = sepTest.getOilDensity();
double oilMW = sepTest.getOilMolarMass();

System.out.println("GOR: " + GOR + " Sm3/Sm3");
System.out.println("Bo: " + Bo);
System.out.println("Stock tank oil density: " + oilDensity + " kg/m3");

Multi-Stage Separator Test

import neqsim.pvtsimulation.simulation.MultiStageSeparatorTest;

MultiStageSeparatorTest mst = new MultiStageSeparatorTest(oil);

// Configure stages
mst.addSeparator(50.0, 323.15);   // HP separator
mst.addSeparator(10.0, 308.15);   // LP separator  
mst.addSeparator(1.01325, 288.15); // Stock tank

mst.run();

double[] stageGORs = mst.getStageGOR();
double totalGOR = mst.getTotalGOR();

Swelling Test

Gas injection swelling experiment.

import neqsim.pvtsimulation.simulation.SwellingTest;

SystemInterface injectionGas = new SystemSrkEos(373.15, 200.0);
injectionGas.addComponent("CO2", 1.0);
injectionGas.setMixingRule("classic");

SwellingTest swelling = new SwellingTest(oil);
swelling.setInjectionGas(injectionGas);
swelling.setTemperature(373.15, "K");

// Injection amounts (moles per mole original oil)
double[] injectionAmounts = {0.0, 0.1, 0.2, 0.3, 0.4, 0.5};
swelling.setInjectionAmounts(injectionAmounts);

swelling.run();

double[] saturationPressures = swelling.getSaturationPressures();
double[] swellingFactors = swelling.getSwellingFactors();
double[] oilDensities = swelling.getOilDensities();

MMP Calculation

Minimum miscibility pressure via slim tube simulation.

import neqsim.pvtsimulation.simulation.MMPCalculator;

MMPCalculator mmp = new MMPCalculator(oil, injectionGas);
mmp.setTemperature(373.15, "K");
mmp.run();

double minimumMiscibilityPressure = mmp.getMMP();
System.out.println("MMP: " + minimumMiscibilityPressure + " bar");

Viscosity Simulation

import neqsim.pvtsimulation.simulation.ViscositySim;

ViscositySim viscSim = new ViscositySim(oil);
viscSim.setTemperature(373.15, "K");

double[] pressures = {400, 300, 200, 150, 100, 50};
viscSim.setPressures(pressures, "bara");

viscSim.run();

double[] oilViscosities = viscSim.getOilViscosity();
double[] gasViscosities = viscSim.getGasViscosity();

Integration with Characterization

Tuning to Lab Data

import neqsim.pvtsimulation.regression.PVTRegression;

// Set up experiment with measured data
ConstantMassExpansion cme = new ConstantMassExpansion(oil);
cme.setExperimentalData(measuredPressures, measuredRelativeVolumes);

// Create regression
PVTRegression regression = new PVTRegression(oil);
regression.addExperiment(cme);

// Select parameters to tune
regression.tuneParameter("C7+", "Tc", 0.95, 1.05);  // ±5%
regression.tuneParameter("C7+", "Pc", 0.95, 1.05);
regression.tuneParameter("C7+", "acf", 0.90, 1.10);

// Run regression
regression.run();

// Get tuned fluid
SystemInterface tunedOil = regression.getTunedSystem();

Complete PVT Study Example

// Create reservoir oil
SystemInterface oil = new SystemSrkEos(373.15, 300.0);
oil.addComponent("nitrogen", 0.5);
oil.addComponent("CO2", 2.1);
oil.addComponent("methane", 35.2);
oil.addComponent("ethane", 7.8);
oil.addComponent("propane", 5.4);
oil.addComponent("i-butane", 1.2);
oil.addComponent("n-butane", 3.1);
oil.addComponent("i-pentane", 1.5);
oil.addComponent("n-pentane", 2.1);
oil.addComponent("n-hexane", 3.5);
oil.addTBPfraction("C7", 8.2, 96.0 / 1000.0, 0.738);
oil.addTBPfraction("C10", 12.4, 134.0 / 1000.0, 0.785);
oil.addTBPfraction("C15", 8.5, 206.0 / 1000.0, 0.835);
oil.addTBPfraction("C20+", 8.5, 450.0 / 1000.0, 0.920);
oil.setMixingRule("classic");
oil.useVolumeCorrection(true);

double reservoirTemp = 373.15;  // K

// 1. Saturation Pressure
SaturationPressure sat = new SaturationPressure(oil);
sat.setTemperature(reservoirTemp, "K");
sat.run();
double Psat = sat.getSaturationPressure();
System.out.println("Bubble point: " + Psat + " bar");

// 2. CME
ConstantMassExpansion cme = new ConstantMassExpansion(oil.clone());
cme.setTemperature(reservoirTemp, "K");
double[] cmePressures = generatePressureRange(Psat + 50, 50, 15);
cme.setPressures(cmePressures, "bara");
cme.run();

// 3. Differential Liberation
DifferentialLiberation dl = new DifferentialLiberation(oil.clone());
dl.setTemperature(reservoirTemp, "K");
double[] dlPressures = generatePressureRange(Psat, 1.01325, 10);
dl.setPressures(dlPressures, "bara");
dl.run();

// 4. Separator Test
SeparatorTest sep = new SeparatorTest(oil.clone());
sep.setSeparatorConditions(
    new double[]{323.15, 288.15},  // Temperatures
    new double[]{30.0, 1.01325}    // Pressures
);
sep.run();

// Print summary
System.out.println("\n=== PVT Summary ===");
System.out.println("Bubble point: " + Psat + " bar");
System.out.println("GOR: " + sep.getGOR() + " Sm3/Sm3");
System.out.println("Bo at Psat: " + dl.getBo()[0]);
System.out.println("Oil density at STC: " + sep.getOilDensity() + " kg/m3");

Best Practices

  1. Clone fluids before running multiple experiments
  2. Use appropriate EoS for fluid type (CPA for polar, PR for heavy oils)
  3. Match temperature units carefully (K vs °C)
  4. Validate against lab data before using for field predictions
  5. Document characterization for reproducibility

PVT Workflows

PVT Simulation Workflows

This guide covers PVT simulation workflows in NeqSim, backed by regression tests under src/test/java/neqsim/pvtsimulation/simulation. Use these tested setups to reproduce experiments in your own studies.


Constant Volume Depletion (CVD)

CVD simulation maintains reservoir volume constant while reducing pressure, measuring the liquid dropout from gas condensate reservoirs.

Basic CVD Setup

import neqsim.pvtsimulation.simulation.ConstantVolumeDepletion;
import neqsim.thermo.system.SystemSrkEos;

// Create fluid with TBP fractions
SystemSrkEos fluid = new SystemSrkEos(97.5 + 273.15, 300.0);  // T(K), P(bara)
fluid.addComponent("nitrogen", 0.34);
fluid.addComponent("CO2", 3.59);
fluid.addComponent("methane", 67.42);
fluid.addComponent("ethane", 9.02);
fluid.addComponent("propane", 4.31);
fluid.addComponent("i-butane", 0.93);
fluid.addComponent("n-butane", 1.71);
fluid.addComponent("i-pentane", 0.74);
fluid.addComponent("n-pentane", 0.85);
fluid.addTBPfraction("C6", 1.38, 86.0 / 1000, 0.664);
fluid.addTBPfraction("C7", 1.57, 96.0 / 1000, 0.738);
fluid.addTBPfraction("C8", 1.73, 107.0 / 1000, 0.765);
fluid.addTBPfraction("C9", 1.40, 121.0 / 1000, 0.781);
fluid.addTBPfraction("C10+", 5.01, 230.0 / 1000, 0.820);

fluid.setMixingRule("classic");
fluid.useVolumeCorrection(true);
fluid.init(0);
fluid.init(1);

// Configure CVD simulation
ConstantVolumeDepletion cvd = new ConstantVolumeDepletion(fluid);
cvd.setTemperature(97.5, "C");
cvd.setPressures(new double[] {300, 250, 200, 150, 100, 50});  // bara

// Run simulation
cvd.runCalc();

// Get results
double[] relativeVolume = cvd.getRelativeVolume();
double[] liquidVolumeFraction = cvd.getLiquidVolume();
double[] Zgas = cvd.getZgas();

CVD Setup Checklist

  1. Create SystemInterface with EOS and add components/TBP fractions
  2. Enable database use and select a mixing rule
  3. Initialize the system (state 0 and 1) before constructing ConstantVolumeDepletion
  4. Call setTemperature(...), setPressures(...), and runCalc()
  5. Optionally load experimental data for regression with setExperimentalData(...)
  6. Retrieve results: getRelativeVolume(), getLiquidVolume(), getZgas()

Differential Liberation (DL)

DL simulation removes liberated gas at each pressure step, measuring oil shrinkage and gas evolution - essential for black oil PVT tables.

Basic DL Setup

import neqsim.pvtsimulation.simulation.DifferentialLiberation;
import neqsim.thermo.system.SystemSrkEos;

// Create rich oil system with TBP characterization
SystemSrkEos fluid = new SystemSrkEos(97.5 + 273.15, 250.0);
fluid.addComponent("nitrogen", 0.5);
fluid.addComponent("CO2", 2.1);
fluid.addComponent("methane", 45.0);
fluid.addComponent("ethane", 7.5);
fluid.addComponent("propane", 5.2);
fluid.addComponent("i-butane", 1.1);
fluid.addComponent("n-butane", 2.8);
fluid.addComponent("i-pentane", 1.4);
fluid.addComponent("n-pentane", 1.9);
fluid.addTBPfraction("C6", 2.5, 86.0 / 1000, 0.685);
fluid.addTBPfraction("C7", 4.2, 96.0 / 1000, 0.755);
fluid.addTBPfraction("C8", 3.8, 107.0 / 1000, 0.775);
fluid.addTBPfraction("C9", 3.2, 121.0 / 1000, 0.790);
fluid.addTBPfraction("C10+", 18.8, 350.0 / 1000, 0.880);

fluid.setMixingRule("classic");
fluid.useVolumeCorrection(true);
fluid.init(0);
fluid.init(1);

// Configure DL simulation
DifferentialLiberation dl = new DifferentialLiberation(fluid);
dl.setTemperature(97.5, "C");
dl.setPressures(new double[] {250, 225, 200, 175, 150, 125, 100, 75, 50, 25, 1});

// Run simulation
dl.runCalc();

// Get results
double[] Bo = dl.getBo();           // Oil formation volume factor
double[] Rs = dl.getRs();           // Solution gas-oil ratio (Sm3/Sm3)
double[] Bg = dl.getBg();           // Gas formation volume factor
double[] oilDensity = dl.getOilDensity();

Interpreting DL Outputs

Property Description Expected Trend
Bo Oil formation volume factor (Vres/Vstock) Decreases from ~1.7 to ~1.05 as pressure drops
Rs Solution gas-oil ratio Decreases to zero at final stage
Bg Gas formation volume factor Increases as gas expands at lower pressure
Oil Density Density of remaining oil Increases as light components liberate

Constant Composition Expansion (CCE)

CCE measures PV behavior without removing any material - used to determine bubble/dew point pressure.

import neqsim.pvtsimulation.simulation.ConstantMassExpansion;

// After creating and initializing fluid...
ConstantMassExpansion cce = new ConstantMassExpansion(fluid);
cce.setTemperature(100.0, "C");

// Run to find saturation pressure
cce.runCalc();

double saturationPressure = cce.getSaturationPressure();
double[] relativeVolume = cce.getRelativeVolume();
double[] Ytfactor = cce.getYfactor();

Saturation Pressure Calculation

Quick calculation of bubble or dew point pressure:

import neqsim.thermodynamicoperations.ThermodynamicOperations;

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);

// For bubble point (oil system)
ops.calcBubblePoint();
double bubbleP = fluid.getPressure("bara");

// For dew point (gas condensate)
ops.calcDewPoint();
double dewP = fluid.getPressure("bara");

Slim Tube Simulation

Minimum miscibility pressure (MMP) determination:

import neqsim.pvtsimulation.simulation.SlimTubeSim;

// Create injection gas
SystemSrkEos injectionGas = new SystemSrkEos(373.15, 200.0);
injectionGas.addComponent("CO2", 1.0);
injectionGas.setMixingRule("classic");

// Configure slim tube
SlimTubeSim slimTube = new SlimTubeSim(reservoirFluid, injectionGas);
slimTube.setTemperature(100.0, "C");
slimTube.setPressures(new double[] {150, 200, 250, 300, 350, 400});
slimTube.runCalc();

double[] recovery = slimTube.getOilRecovery();

Best Practices

  1. Always initialize - Set mixing rule and call init(0) and init(1) before creating PVT simulations
  2. Set temperature explicitly - Use setTemperature() on each simulation to avoid state carryover
  3. Use volume correction - Enable useVolumeCorrection(true) for better liquid density predictions
  4. Validate against lab data - Use setExperimentalData() methods for regression
  5. Check convergence - Verify flash calculations converge at each pressure step

PVT Workflow Module

PVT Workflow: From Lab Data to Tuned Fluid Model

This document describes the complete workflow for generating and tuning fluid models from laboratory PVT data in NeqSim.

Typical Laboratory PVT Report Data

A standard PVT laboratory report contains the following sections. Below are example data tables showing the format typically received from labs like Core Lab, Schlumberger, or Intertek.

Sample Information

Well Name:           A-1
Sample Type:         Bottom Hole Sample (BHS)
Sampling Depth:      2850 m TVD
Sampling Date:       2024-06-15
Reservoir Pressure:  285 bara
Reservoir Temp:      98 °C (371.15 K)
Saturation Pressure: 248 bara (Bubble Point)

Compositional Analysis (Mole %)

Component Mol % MW (g/mol) Density (g/cm³)
N₂ 0.34 28.01 -
CO₂ 3.53 44.01 -
H₂S 0.00 34.08 -
C₁ 70.78 16.04 -
C₂ 8.94 30.07 -
C₃ 5.05 44.10 -
i-C₄ 0.85 58.12 -
n-C₄ 1.68 58.12 -
i-C₅ 0.62 72.15 -
n-C₅ 0.79 72.15 -
C₆ 0.83 86.18 0.664
C₇ 1.06 92.2 0.7324
C₈ 1.06 104.6 0.7602
C₉ 0.79 119.1 0.7677
C₁₀ 0.57 133.0 0.790
C₁₁ 0.38 147.0 0.795
C₁₂+ 2.73 263.0 0.854
Total 100.00

Constant Composition Expansion (CCE) at 98°C

Pressure (bara) Relative Volume Y-Factor Oil Density (kg/m³) Oil Viscosity (cP)
350 0.9521 - 612.3 0.285
300 0.9712 - 625.1 0.312
280 0.9845 - 632.8 0.328
248 (Psat) 1.0000 - 645.2 0.352
220 1.0523 2.145 658.4 0.385
200 1.1124 2.287 670.1 0.412
180 1.1892 2.456 682.5 0.445
150 1.3245 2.712 698.2 0.498
120 1.5421 3.024 715.8 0.562
100 1.7856 3.312 728.4 0.615

Differential Liberation (DLE) at 98°C

Pressure (bara) Rs (Sm³/Sm³) Bo (m³/Sm³) Oil Density (kg/m³) Oil Viscosity (cP) Gas Z-factor Gas Gravity
248 (Psat) 152.3 1.4521 645.2 0.352 - -
220 138.5 1.4012 658.4 0.385 0.862 0.745
200 125.2 1.3654 670.1 0.412 0.851 0.768
180 112.8 1.3285 682.5 0.445 0.838 0.792
150 94.5 1.2756 698.2 0.498 0.815 0.825
120 75.2 1.2198 715.8 0.562 0.792 0.861
100 61.8 1.1812 728.4 0.615 0.775 0.892
80 48.2 1.1425 742.1 0.685 0.758 0.928
50 28.5 1.0912 762.5 0.812 0.732 0.975
20 8.5 1.0385 785.2 1.025 0.712 1.045
1.01 (STO) 0.0 1.0000 825.4 2.850 - -

Constant Volume Depletion (CVD) at 98°C (for Gas Condensates)

Pressure (bara) Liquid Dropout (%) Z-factor Cumulative Gas Produced (%) Liquid Density (kg/m³)
285 (Pdew) 0.0 0.892 0.0 -
260 4.2 0.875 8.5 612.5
230 8.5 0.856 18.2 628.4
200 12.8 0.838 28.5 645.2
170 15.2 0.821 39.8 658.7
140 14.5 0.805 51.2 672.1
110 12.1 0.792 62.8 685.4
80 8.8 0.781 74.5 698.2
50 5.2 0.772 86.2 712.5

Multi-Stage Separator Test

Test Conditions:

Stage Pressure (bara) Temperature (°C)
1 (HP Sep) 45.0 45.0
2 (LP Sep) 8.0 35.0
3 (Stock Tank) 1.01 15.0

Results:

Property Stage 1 Stage 2 Stage 3 Total
GOR (Sm³/Sm³) 95.2 18.5 8.2 121.9
Gas Gravity 0.752 0.985 1.245 0.812
Oil Density (kg/m³) 712.5 758.2 825.4 -
Oil Viscosity (cP) 0.52 0.85 2.45 -

Stock Tank Oil Properties:

Viscosity Data (Separate Measurements)

Pressure (bara) Temperature (°C) Oil Viscosity (cP) Gas Viscosity (cP)
285 98 0.285 0.0185
248 98 0.352 0.0178
200 98 0.412 0.0165
150 98 0.498 0.0148
100 98 0.615 0.0132
248 80 0.425 0.0168
248 60 0.585 0.0155

Swelling Test (CO₂ Injection)

Cumulative CO₂ Injected (mol%) Saturation Pressure (bara) Relative Swelling Factor Oil Density (kg/m³)
0.0 248.0 1.000 645.2
5.0 268.5 1.025 638.4
10.0 292.1 1.052 630.1
15.0 318.4 1.082 620.5
20.0 348.2 1.115 608.2
25.0 382.5 1.152 594.8
30.0 421.8 1.195 578.5

Overview

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Lab PVT Data  │───▶│  Initial Fluid  │───▶│  EOS Regression │───▶│  Export Model   │
│   (CCE,DLE,CVD) │    │  Characterization│    │  (Parameter Fit)│    │  (E300, CSV)    │
└─────────────────┘    └─────────────────┘    └─────────────────┘    └─────────────────┘

Step 1: Create Initial Fluid from Composition

Start with laboratory-reported composition and characterize heavy fractions:

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermo.system.SystemInterface;

// Create fluid at reservoir conditions
SystemInterface fluid = new SystemSrkEos(373.15, 250.0);  // T(K), P(bar)

// Add defined components (from lab composition)
fluid.addComponent("nitrogen", 0.34);
fluid.addComponent("CO2", 3.53);
fluid.addComponent("methane", 70.78);
fluid.addComponent("ethane", 8.94);
fluid.addComponent("propane", 5.05);
fluid.addComponent("i-butane", 0.85);
fluid.addComponent("n-butane", 1.68);
fluid.addComponent("i-pentane", 0.62);
fluid.addComponent("n-pentane", 0.79);
fluid.addComponent("n-hexane", 0.83);

// Add C7+ fractions (TBP cuts from lab)
fluid.addTBPfraction("C7", 1.06, 92.2/1000.0, 0.7324);   // name, mole%, MW(kg/mol), SG
fluid.addTBPfraction("C8", 1.06, 104.6/1000.0, 0.7602);
fluid.addTBPfraction("C9", 0.79, 119.1/1000.0, 0.7677);
fluid.addTBPfraction("C10", 0.57, 133.0/1000.0, 0.79);

// Add plus fraction
fluid.addPlusFraction("C20+", 2.11, 381.0/1000.0, 0.88);

// Characterize plus fraction into pseudo-components
fluid.getCharacterization().characterisePlusFraction();

// Initialize database and set mixing rule
fluid.createDatabase(true);
fluid.setMixingRule(2);  // Classic mixing rule

Step 2: Add Laboratory PVT Data

Import experimental data from CCE, DLE, CVD, and separator tests:

import neqsim.pvtsimulation.regression.PVTRegression;
import neqsim.pvtsimulation.regression.RegressionParameter;

PVTRegression regression = new PVTRegression(fluid);

// CCE (Constant Composition Expansion) data
double[] ccePressures = {400, 350, 300, 280, 260, 240, 220, 200, 180};  // bara
double[] cceRelVol = {0.95, 0.97, 0.99, 1.00, 1.02, 1.05, 1.10, 1.18, 1.30};
double cceTemperature = 373.15;  // K
regression.addCCEData(ccePressures, cceRelVol, cceTemperature);

// DLE (Differential Liberation) data
double[] dlePressures = {280, 240, 200, 160, 120, 80, 40, 1.01325};  // bara
double[] dleRs = {150, 130, 110, 85, 60, 35, 15, 0};  // Sm³/Sm³
double[] dleBo = {1.45, 1.40, 1.35, 1.28, 1.20, 1.12, 1.06, 1.02};  // m³/Sm³
double[] dleOilDensity = {650, 680, 710, 740, 770, 800, 830, 850};  // kg/m³
double dleTemperature = 373.15;  // K
regression.addDLEData(dlePressures, dleRs, dleBo, dleOilDensity, dleTemperature);

// CVD (Constant Volume Depletion) data - for gas condensates
double[] cvdPressures = {350, 300, 250, 200, 150, 100};  // bara
double[] cvdLiquidDropout = {0, 5, 12, 18, 15, 10};  // volume %
double[] cvdZFactor = {0.85, 0.82, 0.80, 0.78, 0.77, 0.76};
double cvdTemperature = 373.15;  // K
regression.addCVDData(cvdPressures, cvdLiquidDropout, cvdZFactor, cvdTemperature);

// Separator test data
regression.addSeparatorData(
    125.0,    // GOR (Sm³/Sm³)
    1.35,     // Bo
    35.0,     // API gravity
    50.0,     // separator pressure (bar)
    313.15,   // separator temperature (K)
    373.15    // reservoir temperature (K)
);

Step 3: Configure Regression Parameters

Select which EOS parameters to tune:

// Binary Interaction Parameters (BIPs)
regression.addRegressionParameter(RegressionParameter.BIP_METHANE_C7PLUS);
regression.addRegressionParameter(RegressionParameter.BIP_C2C6_C7PLUS);
regression.addRegressionParameter(RegressionParameter.BIP_CO2_HC);

// Critical property multipliers for C7+ pseudo-components
regression.addRegressionParameter(RegressionParameter.TC_MULTIPLIER_C7PLUS);
regression.addRegressionParameter(RegressionParameter.PC_MULTIPLIER_C7PLUS);
regression.addRegressionParameter(RegressionParameter.OMEGA_MULTIPLIER_C7PLUS);

// Volume shift for density matching
regression.addRegressionParameter(RegressionParameter.VOLUME_SHIFT_C7PLUS);

// Viscosity parameters (if tuning viscosity)
regression.addRegressionParameter(RegressionParameter.VISCOSITY_LBC_MULTIPLIER);
regression.addRegressionParameter(RegressionParameter.VISCOSITY_PEDERSEN_ALPHA);

// Custom bounds (optional)
regression.addRegressionParameter(
    RegressionParameter.BIP_METHANE_C7PLUS, 
    0.0,    // lower bound
    0.15,   // upper bound
    0.05    // initial guess
);

// Set experiment weights (optional)
regression.setExperimentWeight(ExperimentType.CCE, 1.0);
regression.setExperimentWeight(ExperimentType.DLE, 1.5);  // Higher weight for DLE
regression.setExperimentWeight(ExperimentType.SEPARATOR, 1.0);

// Optimization settings
regression.setMaxIterations(200);
regression.setTolerance(1e-8);
regression.setVerbose(true);

Step 4: Run Regression

Execute the optimization:

import neqsim.pvtsimulation.regression.RegressionResult;

RegressionResult result = regression.runRegression();

// Get the tuned fluid
SystemInterface tunedFluid = result.getTunedFluid();

// Check results
System.out.println("Final chi-square: " + result.getChiSquare());
System.out.println("Optimized parameters:");
double[] params = result.getOptimizedParameters();
for (int i = 0; i < params.length; i++) {
    System.out.println("  Parameter " + i + ": " + params[i]);
}

Step 5: Validate and Compare with Lab Data

Generate comparison reports:

import neqsim.pvtsimulation.util.PVTReportGenerator;
import neqsim.pvtsimulation.simulation.*;

// Run PVT simulations with tuned fluid
ConstantMassExpansion cce = new ConstantMassExpansion(tunedFluid);
cce.setTemperature(373.15);
cce.setPressures(ccePressures);
cce.runCalc();

DifferentialLiberation dle = new DifferentialLiberation(tunedFluid);
dle.setTemperature(373.15);
dle.setPressures(dlePressures);
dle.runCalc();

// Create report generator
PVTReportGenerator report = new PVTReportGenerator(tunedFluid);
report.setProjectInfo("Field X Development", "Well A-1 Sample");
report.setReservoirConditions(250.0, 100.0);  // P(bar), T(°C)

// Add simulation results
report.addCCE(cce);
report.addDLE(dle);

// Add lab data for comparison
for (int i = 0; i < ccePressures.length; i++) {
    report.addLabCCEData(ccePressures[i], "RelVol", cceRelVol[i], "");
}
for (int i = 0; i < dlePressures.length; i++) {
    report.addLabDLEData(dlePressures[i], "Bo", dleBo[i], "m³/Sm³");
    report.addLabDLEData(dlePressures[i], "Rs", dleRs[i], "Sm³/Sm³");
}

// Generate comparison with statistics
String comparison = report.generateLabComparison();
System.out.println(comparison);
// Output includes AAD (Average Absolute Deviation) and ARE (Average Relative Error)

// Generate full Markdown report
String fullReport = report.generateMarkdownReport();

Step 6: Export to Reservoir Simulator

Export the tuned fluid model to Eclipse E300 format:

import neqsim.blackoil.io.EclipseEOSExporter;
import java.nio.file.Path;

// Simple export with default settings
EclipseEOSExporter.toFile(tunedFluid, Path.of("PVT_TUNED.INC"));

// Export with custom configuration
EclipseEOSExporter.ExportConfig config = new EclipseEOSExporter.ExportConfig()
    .setUnits(EclipseEOSExporter.Units.FIELD)      // METRIC or FIELD
    .setReferenceTemperature(373.15)               // Reservoir temp (K)
    .setIncludePVTO(true)                          // Live oil table
    .setIncludePVTG(true)                          // Wet gas table
    .setIncludePVTW(true)                          // Water properties
    .setIncludeDensity(true)                       // Stock tank densities
    .setComment("Tuned to Well A-1 PVT data");

EclipseEOSExporter.toFile(tunedFluid, Path.of("PVT_FIELD.INC"), config);

// Or get as string for inspection
String eclipseContent = EclipseEOSExporter.toString(tunedFluid, config);
System.out.println(eclipseContent);

Generated Eclipse Keywords

The exporter produces standard Eclipse keywords:

Unit Systems

Unit System Pressure Density GOR/CGR Viscosity
METRIC bar kg/m³ Sm³/Sm³ mPa·s
FIELD psia lb/ft³ scf/stb cp

Export Compositional EOS to E300 Format

In addition to black-oil PVT tables, you can export the full compositional EOS model to Eclipse E300 format. This preserves all component properties and binary interaction coefficients:

import neqsim.thermo.util.readwrite.EclipseFluidReadWrite;

// Export tuned fluid to E300 compositional format
EclipseFluidReadWrite.write(tunedFluid, "TUNED_FLUID.e300", 100.0);  // reservoir temp in °C

// Or get as string for inspection
String e300Content = EclipseFluidReadWrite.toE300String(tunedFluid, 100.0);
System.out.println(e300Content);

The E300 compositional file includes:

Keyword Description
NCOMPS Number of components
EOS Equation of state (SRK, PR)
RTEMP Reservoir temperature
CNAMES Component names
TCRIT Critical temperatures (K)
PCRIT Critical pressures (bar)
ACF Acentric factors
MW Molecular weights (g/mol)
TBOIL Normal boiling points (K)
VCRIT Critical volumes (m³/kmol)
SSHIFT Volume translation parameters
PARACHOR Parachor values for IFT
ZI Mole fractions
BIC Binary interaction coefficients

Read E300 File Back into NeqSim

// Read the E300 file back into a NeqSim fluid
SystemInterface importedFluid = EclipseFluidReadWrite.read("TUNED_FLUID.e300");

// Set conditions and run flash
importedFluid.setPressure(200.0, "bara");
importedFluid.setTemperature(100.0, "C");
ThermodynamicOperations ops = new ThermodynamicOperations(importedFluid);
ops.TPflash();

Step 7: Export CSV for Other Applications

Generate CSV files for spreadsheet analysis or other simulators:

// CCE data
String cceCSV = report.generateCCECSV();

// DLE data
String dleCSV = report.generateDLECSV();

// CVD data  
String cvdCSV = report.generateCVDCSV();

// Viscosity data
String viscCSV = report.generateViscosityCSV();

// Density data
String densCSV = report.generateDensityCSV();

// Swelling test
String swellCSV = report.generateSwellingCSV();

// GOR data
String gorCSV = report.generateGORCSV();

// MMP data
String mmpCSV = report.generateMMPCSV();

Available Regression Parameters

Parameter Description Typical Bounds
BIP_METHANE_C7PLUS BIP between CH₄ and C7+ 0.0 - 0.10
BIP_C2C6_C7PLUS BIP between C2-C6 and C7+ 0.0 - 0.05
BIP_CO2_HC BIP between CO₂ and hydrocarbons 0.08 - 0.18
BIP_N2_HC BIP between N₂ and hydrocarbons 0.02 - 0.12
VOLUME_SHIFT_C7PLUS Volume shift multiplier for C7+ 0.8 - 1.2
TC_MULTIPLIER_C7PLUS Critical temperature multiplier 0.95 - 1.05
PC_MULTIPLIER_C7PLUS Critical pressure multiplier 0.95 - 1.05
OMEGA_MULTIPLIER_C7PLUS Acentric factor multiplier 0.90 - 1.10
PLUS_MOLAR_MASS_MULTIPLIER Plus fraction MW adjustment 0.90 - 1.10
GAMMA_ALPHA Whitson gamma distribution shape 0.5 - 4.0
GAMMA_ETA Whitson gamma min MW (η) 75.0 - 95.0
VISCOSITY_LBC_MULTIPLIER LBC viscosity correlation factor 0.8 - 1.5
VISCOSITY_PEDERSEN_ALPHA Pedersen viscosity parameter 0.5 - 2.0

Separator Optimization

Find optimal separator conditions:

import neqsim.pvtsimulation.simulation.MultiStageSeparatorTest;

MultiStageSeparatorTest sepTest = new MultiStageSeparatorTest(tunedFluid);
sepTest.setReservoirConditions(250.0, 373.15);  // P(bar), T(K)

// Add separator stages
sepTest.addSeparatorStage(50.0, 40.0, "HP Separator");   // P(bar), T(°C)
sepTest.addSeparatorStage(10.0, 30.0, "LP Separator");
sepTest.addSeparatorStage(1.01325, 15.0, "Stock Tank");

// Run simulation
sepTest.run();

// Optimize first stage pressure/temperature
MultiStageSeparatorTest.OptimizationResult optResult = 
    sepTest.optimizeFirstStageSeparator(
        5.0, 80.0, 16,    // pressure: min, max, steps
        20.0, 60.0, 9     // temperature: min, max, steps
    );

System.out.println("Optimal P: " + optResult.getOptimalPressure() + " bara");
System.out.println("Optimal T: " + optResult.getOptimalTemperature() + " °C");
System.out.println("Max Recovery: " + optResult.getMaximumOilRecovery());
System.out.println("GOR at optimum: " + optResult.getGorAtOptimum() + " Sm³/Sm³");

Complete Example

See PVTRegressionTest.java for working examples.

Property Flash

Property flash workflows proven by tests

ThermodynamicOperationsTest exercises NeqSim's property flash API across PT/TP orderings, unit handling, and online composition updates. This guide distills the tested patterns so you can set up property flashes with confidence.

PT vs TP flash symmetry

testFlash creates a binary methane/ethane SRK system and runs both flash(FlashType.PT, P, T, unitP, unitT) and flash(FlashType.TP, T, P, unitT, unitP) on the same state, then compares all returned properties for equality.【F:src/test/java/neqsim/thermodynamicoperations/ThermodynamicOperationsTest.java†L25-L48】 The test verifies that property flashes are order-invariant when pressure and temperature units are supplied explicitly.

Takeaway: You can interchange PT and TP flash modes without changing results, as long as you reinitialize (init(2) and initPhysicalProperties()) before reading properties.

Validating request inputs

testFluidDefined builds several SRK air mixtures and calls propertyFlash with streaming pressure/temperature vectors and optional online compositions to mimic live analyzers.【F:src/test/java/neqsim/thermodynamicoperations/ThermodynamicOperationsTest.java†L50-L119】 The test first omits init(0) to prove the API returns a descriptive error ("Sum of fractions must be approximately to 1 or 100..."), then reinitializes and confirms all calculation errors are null.

Setup checklist from the test:

  1. Add components and set a mixing rule plus any volume corrections.
  2. Call init(0) before requesting properties to normalize molar fractions.
  3. Pass FlashMode 1, 2, or 3; any other mode yields the explicit error asserted in testNeqSimPython.【F:src/test/java/neqsim/thermodynamicoperations/ThermodynamicOperationsTest.java†L120-L152】
  4. When streaming online composition updates, make sure each inner list matches the length of the pressure/temperature vectors.

Integration pattern for external clients

testNeqSimPython and testNeqSimPython2 illustrate how property flashes can be called from foreign interfaces (e.g., Python bindings) while maintaining result integrity.【F:src/test/java/neqsim/thermodynamicoperations/ThermodynamicOperationsTest.java†L120-L209】 The tests check that:

When wrapping the API externally, mirror these assertions to guard against transport or serialization errors.

Whitson Reader

Whitson PVT Parameter File Reader

The WhitsonPVTReader class enables NeqSim to read PVT parameter files exported from Whitson+ and similar PVT software, creating fully configured fluid systems with accurate thermodynamic properties.

Overview

When working with PVT characterization software like Whitson+, you can export equation of state parameters and component properties to a tab-separated file format. The WhitsonPVTReader parses these files and creates NeqSim SystemInterface objects with:

File Format

The Whitson PVT parameter file is a tab-separated text file with three main sections:

1. Parameters Section

Key-value pairs defining global EOS and model parameters:

Parameter   Value
Name    Predictive_EOS_Parameters
EOS Type    PR
LBC P0  0.1023
LBC P1  0.023364
LBC P2  0.058533
LBC P3  -0.037734245
LBC P4  0.00839916
LBC F0  0.1
C7+ Gamma Shape 0.677652
C7+ Gamma Bound 94.9981
Omega A, ΩA 0.457236
Omega B, ΩB 0.0777961

2. Component Table

Component properties with a header row and units row:

Component   MW  Pc  Tc  AF, ω   Volume Shift, s ZcVisc  VcVisc  Vc  Zc  Pchor   SG  Tb  LMW
-   -   bara    C   -   -   -   m3/kmol m3/kmol -   -   -   C   -
CO2 44.01000    73.74000    30.97000    0.225000    0.001910    0.274330    0.09407 0.09407 0.274330    80.00   0.76193 -88.266 
N2  28.01400    33.98000    -146.95000  0.037000    -0.167580   0.291780    0.09010 0.09010 0.291780    59.10   0.28339 -195.903    
C1  16.04300    45.99000    -82.59000   0.011000    -0.149960   0.286200    0.09860 0.09860 0.286200    71.00   0.14609 -161.593    
...

Column definitions:

Column Description Units
Component Component name -
MW Molecular weight g/mol
Pc Critical pressure bara
Tc Critical temperature °C
AF, ω Acentric factor -
Volume Shift, s Peneloux volume shift -
ZcVisc Critical compressibility for viscosity -
VcVisc Critical volume for viscosity m³/kmol
Vc Critical volume m³/kmol
Zc Critical compressibility -
Pchor Parachor -
SG Specific gravity -
Tb Normal boiling point °C
LMW Lumped molecular weight (optional) g/mol

3. Binary Interaction Parameters (BIPs)

Full symmetric matrix of binary interaction parameters:

BIPS    CO2 N2  C1  C2  C3  ...
CO2 0.00    0.00    0.11    0.13    0.13    ...
N2  0.00    0.00    0.03    0.01    0.09    ...
C1  0.11    0.03    0.00    0.00    0.00    ...
...

Usage Examples

Basic Usage

Read a parameter file and create a fluid with equal molar composition:

import neqsim.thermo.util.readwrite.WhitsonPVTReader;
import neqsim.thermo.system.SystemInterface;

// Read parameter file
SystemInterface fluid = WhitsonPVTReader.read("path/to/volveparam.txt");

// Set conditions
fluid.setTemperature(373.15);  // 100°C in Kelvin
fluid.setPressure(200.0);       // 200 bar

// Run flash calculation
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Get properties
double density = fluid.getDensity("kg/m3");
double viscosity = fluid.getViscosity("cP");

With Custom Composition

Specify molar composition matching the component order in the file:

// Define composition (must match number of components in file)
double[] composition = {
    0.02,   // CO2
    0.01,   // N2
    0.70,   // C1
    0.10,   // C2
    0.05,   // C3
    0.08,   // C7
    0.04    // C10
};

SystemInterface fluid = WhitsonPVTReader.read("path/to/volveparam.txt", composition);

Accessing Parsed Parameters

The reader provides getters for extracted parameters:

WhitsonPVTReader reader = new WhitsonPVTReader();
reader.parseFile("path/to/volveparam.txt");

// Get LBC viscosity parameters
double[] lbcParams = reader.getLBCParameters();
// Returns: [P0, P1, P2, P3, P4, F0]

// Get C7+ gamma distribution parameters
double[] gammaParams = reader.getGammaParameters();
// Returns: [shape, bound]

// Get Omega parameters
double omegaA = reader.getOmegaA();
double omegaB = reader.getOmegaB();

// Get component information
int numComponents = reader.getNumberOfComponents();
List<String> names = reader.getComponentNames();

Component Name Mapping

The reader automatically maps Whitson component names to NeqSim standard names:

Whitson Name NeqSim Name
C1 methane
C2 ethane
C3 propane
i-C4 i-butane
n-C4 n-butane
i-C5 i-pentane
n-C5 n-pentane
NEO-C5 22-dim-C3
C6 n-hexane
N2 nitrogen
CO2 CO2
H2S H2S
H2O water

C7+ fractions (C7, C8, C9, ..., C36+) are added as pseudo-components with the suffix _PC.

Supported EOS Types

File Value NeqSim Class
PR SystemPrEos
SRK SystemSrkEos
PR78 SystemPrEos1978

Integration with Whitson+ Workflows

This reader enables seamless integration between Whitson+ PVT characterization and NeqSim process simulation:

  1. PVT Characterization: Use Whitson+ to fit EOS parameters to laboratory data (CCE, CVD, DLE, separator tests)
  2. Parameter Export: Export the fitted parameters to a tab-separated file
  3. NeqSim Import: Use WhitsonPVTReader to create a NeqSim fluid
  4. Process Simulation: Use the characterized fluid in NeqSim process simulations

This workflow ensures consistency between PVT modeling and process simulation, using the same EOS parameters throughout.

PVT Simulations with Imported Fluids

Once a fluid is created from a Whitson file, you can run standard PVT simulations:

import neqsim.pvtsimulation.simulation.*;
import neqsim.pvtsimulation.util.PVTReportGenerator;

// Create fluid from Whitson file
double[] composition = {0.02, 0.01, 0.70, 0.10, 0.05, 0.08, 0.04};
SystemInterface fluid = WhitsonPVTReader.read("volveparam.txt", composition);

// Initialize
fluid.setTemperature(373.15);  // 100°C
fluid.setPressure(300.0);
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
fluid.initPhysicalProperties();

// Saturation pressure (dew point for gas condensate)
SaturationPressure satPres = new SaturationPressure(fluid);
satPres.setTemperature(100.0, "C");
satPres.run();
double psat = satPres.getSaturationPressure();

// Constant Composition Expansion (CCE)
ConstantMassExpansion cce = new ConstantMassExpansion(fluid);
cce.setTemperature(100.0, "C");
cce.setPressures(new double[]{300, 250, 200, 150, 100});
cce.runCalc();
double[] relVol = cce.getRelativeVolume();

// Multi-Stage Separator Test
MultiStageSeparatorTest sepTest = new MultiStageSeparatorTest(fluid);
sepTest.setReservoirConditions(300.0, 100.0);
sepTest.addSeparatorStage(50.0, 40.0, "HP Separator");
sepTest.addSeparatorStage(10.0, 30.0, "LP Separator");
sepTest.addSeparatorStage(1.01325, 15.0, "Stock Tank");
sepTest.run();

double gor = sepTest.getTotalGOR();        // Sm³/Sm³
double bo = sepTest.getBo();               // m³/Sm³
double apiGravity = sepTest.getStockTankAPIGravity();
double stockTankDensity = sepTest.getStockTankOilDensity();  // kg/m³

// Get oil properties at each separator stage
for (var stage : sepTest.getStageResults()) {
    double oilDensity = stage.getOilDensity();     // kg/m³
    double oilViscosity = stage.getOilViscosity(); // cP
}

// Generate PVT Report
PVTReportGenerator report = new PVTReportGenerator(fluid);
report.setProjectInfo("My Project", "Gas Condensate Sample")
      .setReservoirConditions(300.0, 100.0)
      .setSaturationPressure(psat, false)  // false = dew point
      .addCCE(cce)
      .addSeparatorTest(sepTest);

String markdownReport = report.generateMarkdownReport();
System.out.println(markdownReport);

LBC Viscosity Model

The reader automatically applies the LBC viscosity model with the parameters from the Whitson file (P0-P4). The viscosity is calculated using:

// Viscosity is automatically calculated with LBC model
fluid.initPhysicalProperties();

// Gas phase viscosity
double gasViscosity = fluid.getPhase("gas").getPhysicalProperties().getViscosity();
double gasViscosityCP = gasViscosity * 1000;  // Convert Pa·s to cP

// Oil phase viscosity (if oil phase exists)
if (fluid.hasPhaseType("oil")) {
    double oilViscosity = fluid.getPhase("oil").getPhysicalProperties().getViscosity();
    double oilViscosityCP = oilViscosity * 1000;  // Convert Pa·s to cP
}

You can also use ViscositySim to calculate viscosity at multiple pressures:

import neqsim.pvtsimulation.simulation.ViscositySim;

ViscositySim viscSim = new ViscositySim(fluid);
double[] pressures = {300.0, 250.0, 200.0, 150.0, 100.0};
double[] temperatures = new double[pressures.length];
Arrays.fill(temperatures, 373.15);  // 100°C in Kelvin
viscSim.setTemperaturesAndPressures(temperatures, pressures);
viscSim.runCalc();

double[] gasViscosity = viscSim.getGasViscosity();   // Pa·s (multiply by 1000 for cP)
double[] oilViscosity = viscSim.getOilViscosity();   // Pa·s (multiply by 1000 for cP)

Example Output

A typical PVT report for a gas condensate fluid created from a Whitson parameter file:

# PVT Study Report

## Project Information

| Property | Value |
|----------|-------|
| Project | Whitson PVT Reader Test |
| Fluid Name | Test Gas Condensate |
| Report Date | 2025-12-15 12:59 |

## Reservoir Conditions

| Property | Value | Unit |
|----------|-------|------|
| Reservoir Pressure | 300.0 | bara |
| Reservoir Temperature | 100.0 | °C |
| Dew Point Pressure | 248.41 | bara |

## Fluid Composition

| Component | Mole Fraction | MW (g/mol) |
|-----------|--------------|------------|
| CO2 | 0.020000 | 44.01 |
| nitrogen | 0.010000 | 28.01 |
| methane | 0.700000 | 16.04 |
| ethane | 0.100000 | 30.07 |
| propane | 0.050000 | 44.10 |
| C7_PC | 0.080000 | 97.63 |
| C10_PC | 0.040000 | 134.47 |

## Constant Composition Expansion (CCE)

| Pressure (bara) | Rel. Volume | Y-Factor | Density (kg/m³) |
|-----------------|-------------|----------|-----------------|
| 298.1 | 0.8332 | - | 332.0 |
| 273.2 | 0.8703 | - | 318.3 |
| 248.4 | 0.9163 | - | 302.8 |
| 223.6 | 1.0042 | 1.1212 | 234.5 |
| 198.7 | 1.1188 | 1.0966 | 193.6 |
| 173.9 | 1.2725 | 1.0707 | 160.5 |

## Separator Test

=== Multi-Stage Separator Test Results ===

Reservoir Conditions: P = 300.0 bara, T = 100.0 °C
Number of Stages: 3

Stage-by-Stage Results:
| Stage | P (bara) | T (°C) | GOR (Sm³/Sm³) | Cum GOR (Sm³/Sm³) |
|-------|----------|--------|---------------|-------------------|
| HP Separator | 50.0 | 40.0 | 1096.9 | 1096.9 |
| LP Separator | 10.0 | 30.0 | 53.9 | 1150.8 |
| Stock Tank | 1.0 | 15.0 | 23.6 | 1174.4 |

Overall Results:
  Total GOR:           1174.4 Sm³/Sm³
  Bo (FVF):            5.0880 rm³/Sm³
  Stock Tank Density:  751.5 kg/m³
  API Gravity:         56.6 °API

## Quality Metrics

---
*Report generated by NeqSim PVT Report Generator*

Additional PVT Data

Gas Viscosity (LBC Model)

Pressure (bara) Viscosity (cP)
300.0 0.0387
250.0 0.0340
200.0 0.0227
150.0 0.0178
100.0 0.0151

Gas Density

Pressure (bara) Density (kg/m³)
300.0 333.04
250.0 303.87
200.0 195.47
150.0 132.69
100.0 82.11

Separator Stage Oil Properties

Stage P (bara) T (°C) Oil Density (kg/m³) Oil Viscosity (cP)
HP Separator 50.0 40.0 - 0.6315
LP Separator 10.0 30.0 675.35 0.6143
Stock Tank 1.0 15.0 723.65 0.6643

Oil Properties vs Pressure

For gas condensates, an oil (condensate) phase forms below the dew point pressure. The properties are:

Pressure (bara) Density (kg/m³) Viscosity (cP)
200.0 482.81 0.0666
150.0 544.85 0.0903

Note: Oil phase only exists below the dew point pressure (248.4 bara for this fluid).

Gas Condensate Metrics Summary

Property Value Unit
Dew Point Pressure 248.41 bara
GOR 1174.4 Sm³/Sm³
CGR 5.4 bbl/MMscf
Bo 5.0880 m³/Sm³
Stock Tank API 56.6 °API
Stock Tank Density 751.5 kg/m³
Molar Mass 30.79 g/mol

See Also

Solution Gas-Water Ratio

Solution Gas-Water Ratio (Rsw) Calculation

Overview

The Solution Gas-Water Ratio (Rsw) represents the volume of gas dissolved in water at reservoir conditions, expressed as standard cubic meters of gas per standard cubic meter of water (Sm³/Sm³). This property is essential for:

The SolutionGasWaterRatio class in NeqSim provides three calculation methods with varying levels of complexity and accuracy.

Physical Background

Gas Solubility in Water

Gas solubility in water is governed by Henry's Law at low pressures:

$$x_g = \frac{P_g}{H}$$

where:

At higher pressures, deviations from Henry's Law become significant, and equation of state methods are required.

Key Factors Affecting Rsw

  1. Pressure: Rsw increases approximately linearly with pressure at moderate conditions
  2. Temperature: Shows a minimum around 70-100°C for hydrocarbons
    • At low T: solubility decreases with increasing T (entropy effect)
    • At high T: solubility increases with T (approaching critical point)
  3. Salinity: Dissolved salts reduce gas solubility ("salting-out" effect)
  4. Gas Composition: CO₂ is 20-50× more soluble than methane; H₂S is even more soluble

Typical Values

Condition Rsw (Sm³/Sm³)
Shallow reservoir (50 bar, 50°C) 0.5 - 1.0
Medium depth (150 bar, 80°C) 1.5 - 3.0
Deep reservoir (300 bar, 120°C) 3.0 - 6.0
CO₂-rich gas (100 bar, 50°C) 5.0 - 15.0

Available Calculation Methods

1. McCain (Culberson-McKetta) Correlation

Best for: Quick estimates, pure methane systems, engineering screening

The McCain correlation is based on the Culberson-McKetta experimental data (1951) with coefficients from McCain (1990).

Formulation

For pure water: $$R_{sw,pure} = A + B \cdot P + C \cdot P^2$$

where coefficients A, B, C are temperature-dependent polynomials:

$$A = 8.15839 - 6.12265 \times 10^{-2}T + 1.91663 \times 10^{-4}T^2 - 2.1654 \times 10^{-7}T^3$$

$$B = 1.01021 \times 10^{-2} - 7.44241 \times 10^{-5}T + 3.05553 \times 10^{-7}T^2 - 2.94883 \times 10^{-10}T^3$$

$$C = (-9.02505 + 0.130237T - 8.53425 \times 10^{-4}T^2 + 2.34122 \times 10^{-6}T^3 - 2.37049 \times 10^{-9}T^4) \times 10^{-7}$$

where T is in °F and P is in psia.

Salinity Correction

$$R_{sw,brine} = R_{sw,pure} \times 10^{-C_s \cdot S}$$

where:

Validity Range

Parameter Range
Temperature 60-350°F (15-177°C)
Pressure 14.7-10,000 psia (1-690 bar)
Salinity 0-30 wt% NaCl
Gas type Methane (use with caution for other gases)

2. Søreide-Whitson Method

Best for: Multi-component gas mixtures, moderate salinity, process simulation

Uses the modified Peng-Robinson equation of state with Søreide-Whitson alpha function and mixing rules specifically developed for hydrocarbon-water systems.

Key Features

Mixing Rule

The Søreide-Whitson mixing rule (mixing rule 11 in NeqSim) uses:

$$a_m = \sum_i \sum_j x_i x_j \sqrt{a_i a_j}(1 - k_{ij})$$

with special binary interaction parameters for water-hydrocarbon pairs that account for salinity.

Validity Range

Parameter Range
Temperature 273-473 K (0-200°C)
Pressure 1-1000 bar
Salinity 0-6 mol/kg (0-26 wt% NaCl)
Gas type Any hydrocarbon mixture with CO₂, N₂, H₂S

3. Electrolyte CPA Method

Best for: High-accuracy predictions, electrolyte systems, research applications

Uses the Cubic-Plus-Association (CPA) equation of state with electrolyte extensions for rigorous modeling of ion-water-gas interactions.

Key Features

CPA Equation

$$P = \frac{RT}{V_m - b} - \frac{a}{V_m(V_m + b)} - \frac{1}{2}\frac{RT}{V_m}\left(1 + \rho\frac{\partial \ln g}{\partial \rho}\right)\sum_A x_A(1 - X_A)$$

where the last term accounts for hydrogen bonding associations.

Validity Range

Parameter Range
Temperature 273-473 K (0-200°C)
Pressure 1-1000 bar
Salinity 0-6 mol/kg NaCl equivalent
Gas type Any composition

Usage Examples

Basic Usage with McCain Correlation

import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.pvtsimulation.simulation.SolutionGasWaterRatio;

// Create gas system
SystemInterface gas = new SystemSrkCPAstatoil(350.0, 100.0); // 350 K, 100 bar
gas.addComponent("methane", 0.95);
gas.addComponent("CO2", 0.05);
gas.setMixingRule(10);

// Create Rsw calculator
SolutionGasWaterRatio rswCalc = new SolutionGasWaterRatio(gas);

// Set conditions
double[] temperatures = {350.0, 360.0, 370.0}; // K
double[] pressures = {100.0, 150.0, 200.0};    // bar
rswCalc.setTemperaturesAndPressures(temperatures, pressures);

// Set salinity (pure water)
rswCalc.setSalinity(0.0);

// Use McCain method
rswCalc.setCalculationMethod(SolutionGasWaterRatio.CalculationMethod.MCCAIN);
rswCalc.runCalc();

// Get results
double[] rsw = rswCalc.getRsw();
for (int i = 0; i < rsw.length; i++) {
    System.out.printf("T=%.1f K, P=%.1f bar: Rsw = %.4f Sm³/Sm³%n", 
                      temperatures[i], pressures[i], rsw[i]);
}

Using Søreide-Whitson for Multi-Component Gas

// Create gas with multiple components
SystemInterface gas = new SystemSrkCPAstatoil(373.15, 200.0);
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.05);
gas.addComponent("propane", 0.03);
gas.addComponent("CO2", 0.05);
gas.addComponent("nitrogen", 0.02);
gas.setMixingRule(10);

SolutionGasWaterRatio rswCalc = new SolutionGasWaterRatio(gas);
rswCalc.setTemperaturesAndPressures(new double[]{373.15}, new double[]{200.0});

// Use Søreide-Whitson method
rswCalc.setCalculationMethod(SolutionGasWaterRatio.CalculationMethod.SOREIDE_WHITSON);
rswCalc.runCalc();

System.out.printf("Rsw (Søreide-Whitson) = %.4f Sm³/Sm³%n", rswCalc.getRsw(0));

Accounting for Salinity

SolutionGasWaterRatio rswCalc = new SolutionGasWaterRatio(gas);
rswCalc.setTemperaturesAndPressures(new double[]{350.0}, new double[]{100.0});

// Compare pure water vs seawater vs formation water
double[] salinities = {0.0, 3.5, 10.0}; // wt% NaCl
String[] waterTypes = {"Pure water", "Seawater", "Formation water"};

rswCalc.setCalculationMethod(SolutionGasWaterRatio.CalculationMethod.ELECTROLYTE_CPA);

for (int i = 0; i < salinities.length; i++) {
    rswCalc.setSalinity(salinities[i], "wt%");
    rswCalc.runCalc();
    System.out.printf("%s (%.1f wt%% NaCl): Rsw = %.4f Sm³/Sm³%n", 
                      waterTypes[i], salinities[i], rswCalc.getRsw(0));
}

Salinity Unit Conversions

The class supports multiple salinity units:

// Set salinity in different units
rswCalc.setSalinity(0.5);           // Default: molality (mol NaCl / kg water)
rswCalc.setSalinity(3.5, "wt%");    // Weight percent NaCl
rswCalc.setSalinity(35000, "ppm");  // Parts per million

Method Selection Guide

Scenario Recommended Method Reason
Quick screening McCain Fast, simple
Pure methane system McCain Optimized for CH₄
Multi-component gas Søreide-Whitson Accounts for composition
CO₂-rich gas (>20% CO₂) Søreide-Whitson or CPA McCain underestimates
High salinity (>5 wt%) Electrolyte CPA Best ion modeling
Research/validation Electrolyte CPA Most rigorous
Process simulation Søreide-Whitson Good balance

Comparison with Literature

Methane Solubility in Pure Water

T (°C) P (bar) Literature (Sm³/Sm³) McCain Søreide-Whitson CPA
25 100 1.8-2.2 2.20 1.02 2.52
50 100 1.5-1.8 1.78 1.03 1.94
75 100 1.4-1.7 1.57 1.13 1.65
100 100 1.5-1.8 1.59 1.31 1.52

Salinity Effect

The salting-out coefficient ($k_s$) represents the reduction in solubility per unit salinity:

$$\log_{10}\left(\frac{R_{sw,brine}}{R_{sw,pure}}\right) = -k_s \cdot m_{salt}$$

Method Typical $k_s$ (L/mol)
McCain 0.10-0.15
Søreide-Whitson 0.12-0.18
Electrolyte CPA 0.10-0.16
Experimental (Duan & Mao, 2006) 0.11-0.14

API Reference

Constructor

public SolutionGasWaterRatio(SystemInterface inputSystem)

Creates a new Rsw calculator using the given thermodynamic system as the gas composition source.

Key Methods

Method Description
setCalculationMethod(CalculationMethod method) Set calculation method (MCCAIN, SOREIDE_WHITSON, ELECTROLYTE_CPA)
setSalinity(double salinity) Set salinity in mol/kg water
setSalinity(double salinity, String unit) Set salinity with unit ("wt%", "ppm")
setTemperaturesAndPressures(double[] T, double[] P) Set calculation conditions
runCalc() Execute calculation
getRsw() Get array of calculated Rsw values
getRsw(int index) Get Rsw at specific index
calculateRsw(double T, double P) Single-point calculation

Calculation Methods Enum

public enum CalculationMethod {
    MCCAIN,           // Empirical correlation (fast)
    SOREIDE_WHITSON,  // Modified PR-EoS (recommended)
    ELECTROLYTE_CPA   // CPA with electrolytes (most accurate)
}

References

  1. Culberson, O.L. and McKetta, J.J. (1951). "Phase Equilibria in Hydrocarbon-Water Systems III - The Solubility of Methane in Water at Pressures to 10,000 psia." Journal of Petroleum Technology, 3(08), 223-226.

  2. McCain, W.D. Jr. (1990). The Properties of Petroleum Fluids, 2nd Edition. PennWell Publishing Company.

  3. Søreide, I. and Whitson, C.H. (1992). "Peng-Robinson Predictions for Hydrocarbons, CO₂, N₂, and H₂S with Pure Water and NaCl Brine." Fluid Phase Equilibria, 77, 217-240.

  4. Duan, Z. and Mao, S. (2006). "A Thermodynamic Model for Calculating Methane Solubility, Density and Gas Phase Composition of Methane-Bearing Aqueous Fluids from 273 to 523 K and from 1 to 2000 bar." Geochimica et Cosmochimica Acta, 70(13), 3369-3386.

  5. Haghighi, H., Chapoy, A., and Tohidi, B. (2009). "Methane and Water Phase Equilibria in the Presence of Single and Mixed Electrolyte Solutions Using the Cubic-Plus-Association Equation of State." Oil & Gas Science and Technology, 64(2), 141-154.

See Also

Chapter 36: Black Oil Models

Black Oil Overview

Black Oil Package

The blackoil package provides black oil model capabilities for reservoir engineering applications, including PVT table handling and flash calculations.

Table of Contents


Overview

Location: neqsim.blackoil

Purpose:


Package Structure

blackoil/
├── BlackOilFlash.java           # Black oil flash calculator
├── BlackOilFlashResult.java     # Flash results container
├── BlackOilPVTTable.java        # PVT table storage
├── BlackOilConverter.java       # Compositional to black oil conversion
├── SystemBlackOil.java          # Black oil system representation
│
└── io/                          # I/O utilities
    └── BlackOilTableExporter.java

Black Oil Model

Theory

The black oil model describes reservoir fluids using three pseudo-components:

Key Properties

Property Symbol Description
Solution GOR Rs Gas dissolved in oil (Sm³/Sm³)
Oil FVF Bo Oil formation volume factor
Gas FVF Bg Gas formation volume factor
Water FVF Bw Water formation volume factor
Oil-in-Gas ratio Rv Oil vaporized in gas (Sm³/Sm³)
Oil viscosity μo Dynamic viscosity of oil
Gas viscosity μg Dynamic viscosity of gas
Water viscosity μw Dynamic viscosity of water

Correlations

Standing Correlation for Rs

$$R_s = \gamma_g \left( \frac{P}{18.2} \cdot 10^{0.0125 \cdot API - 0.00091 \cdot T} \right)^{1.2048}$$

Vasquez-Beggs for Bo

$$B_o = 1 + C_1 R_s + C_2 (T - 60) \left( \frac{API}{\gamma_{g,100}} \right) + C_3 R_s (T - 60) \left( \frac{API}{\gamma_{g,100}} \right)$$


PVT Tables

BlackOilPVTTable

import neqsim.blackoil.BlackOilPVTTable;

// Create PVT table
BlackOilPVTTable pvtTable = new BlackOilPVTTable();

// Set pressure points
double[] pressures = {50, 100, 150, 200, 250, 300};
pvtTable.setPressures(pressures);

// Set properties at each pressure
pvtTable.setRs(new double[]{80, 100, 120, 140, 160, 180});
pvtTable.setBo(new double[]{1.25, 1.30, 1.35, 1.40, 1.45, 1.50});
pvtTable.setBg(new double[]{0.010, 0.008, 0.006, 0.005, 0.004, 0.003});
pvtTable.setMuO(new double[]{1.5, 1.2, 1.0, 0.9, 0.8, 0.7});
pvtTable.setMuG(new double[]{0.015, 0.016, 0.017, 0.018, 0.019, 0.020});

Interpolation

// Get properties at any pressure
double P = 175.0;  // bar
double Rs = pvtTable.Rs(P);
double Bo = pvtTable.Bo(P);
double Bg = pvtTable.Bg(P);
double muO = pvtTable.mu_o(P);
double muG = pvtTable.mu_g(P);

BlackOilFlash

Flash Calculator

import neqsim.blackoil.BlackOilFlash;
import neqsim.blackoil.BlackOilFlashResult;

// Create flash calculator
double rho_o_sc = 850.0;   // Oil density at SC, kg/m³
double rho_g_sc = 0.85;    // Gas density at SC, kg/m³
double rho_w_sc = 1000.0;  // Water density at SC, kg/m³

BlackOilFlash flash = new BlackOilFlash(pvtTable, rho_o_sc, rho_g_sc, rho_w_sc);

// Perform flash at reservoir conditions
double P = 200.0;    // bar
double T = 373.15;   // K (not used in simple model)
double Otot_std = 1000.0;  // Stock tank oil, Sm³
double Gtot_std = 150000.0; // Total gas, Sm³
double W_std = 500.0;      // Water, Sm³

BlackOilFlashResult result = flash.flash(P, T, Otot_std, Gtot_std, W_std);

Flash Results

// Phase volumes at reservoir conditions
double V_oil = result.V_o;   // Oil volume, m³
double V_gas = result.V_g;   // Gas volume, m³
double V_water = result.V_w; // Water volume, m³

// Phase properties
double rho_oil = result.rho_o;   // Oil density, kg/m³
double rho_gas = result.rho_g;   // Gas density, kg/m³
double rho_water = result.rho_w; // Water density, kg/m³

double mu_oil = result.mu_o;     // Oil viscosity, cP
double mu_gas = result.mu_g;     // Gas viscosity, cP
double mu_water = result.mu_w;   // Water viscosity, cP

// PVT properties used
double Rs = result.Rs;   // Solution GOR
double Bo = result.Bo;   // Oil FVF
double Bg = result.Bg;   // Gas FVF
double Bw = result.Bw;   // Water FVF

Compositional to Black Oil Conversion

BlackOilConverter

Generate black oil tables from compositional EoS model.

import neqsim.blackoil.BlackOilConverter;

// Create compositional oil
SystemInterface oil = new SystemPrEos(373.15, 300.0);
oil.addComponent("nitrogen", 0.5);
oil.addComponent("CO2", 2.0);
oil.addComponent("methane", 35.0);
oil.addComponent("ethane", 8.0);
oil.addComponent("propane", 5.0);
oil.addComponent("n-butane", 3.0);
oil.addComponent("n-pentane", 2.5);
oil.addComponent("n-hexane", 3.0);
oil.addTBPfraction("C7+", 41.0, 220.0/1000.0, 0.85);
oil.setMixingRule("classic");
oil.useVolumeCorrection(true);

// Convert to black oil
BlackOilConverter converter = new BlackOilConverter(oil);
converter.setTemperature(373.15, "K");

// Define separator conditions
converter.setSeparatorTemperature(288.15, "K");
converter.setSeparatorPressure(1.01325, "bara");

// Generate table
double[] pressures = {1, 50, 100, 150, 200, 250, 300};
converter.setPressures(pressures, "bara");
converter.run();

// Get black oil table
BlackOilPVTTable boTable = converter.getBlackOilTable();

Export to Simulators

Eclipse Format

import neqsim.blackoil.io.BlackOilTableExporter;

BlackOilTableExporter exporter = new BlackOilTableExporter(boTable);
exporter.setFormat("ECLIPSE");
exporter.exportToFile("PVTO.inc");

Example PVTO Output

PVTO
-- Rs      P       Bo      viscosity
   50.0    50.0   1.250    1.50
          100.0   1.248    1.55
          150.0   1.246    1.60 /
  100.0   100.0   1.350    1.20
          150.0   1.347    1.25
          200.0   1.345    1.30 /
/

Complete Example

import neqsim.blackoil.*;
import neqsim.pvtsimulation.simulation.*;

// Step 1: Create compositional model
SystemInterface oil = new SystemPrEos(373.15, 250.0);
oil.addComponent("nitrogen", 0.3);
oil.addComponent("CO2", 1.5);
oil.addComponent("methane", 40.0);
oil.addComponent("ethane", 7.0);
oil.addComponent("propane", 4.5);
oil.addComponent("i-butane", 1.0);
oil.addComponent("n-butane", 2.5);
oil.addComponent("i-pentane", 1.2);
oil.addComponent("n-pentane", 1.8);
oil.addComponent("n-hexane", 2.5);
oil.addTBPfraction("C7-C10", 15.0, 120.0/1000.0, 0.78);
oil.addTBPfraction("C11-C15", 10.0, 180.0/1000.0, 0.82);
oil.addTBPfraction("C16+", 12.7, 350.0/1000.0, 0.90);
oil.setMixingRule("classic");
oil.useVolumeCorrection(true);

// Step 2: Run differential liberation
DifferentialLiberation dl = new DifferentialLiberation(oil);
dl.setTemperature(373.15, "K");
double[] pressures = {250, 200, 150, 100, 75, 50, 25, 1.01325};
dl.setPressures(pressures, "bara");
dl.run();

// Step 3: Create black oil table
BlackOilPVTTable boTable = new BlackOilPVTTable();
boTable.setPressures(pressures);
boTable.setRs(dl.getRs());
boTable.setBo(dl.getBo());
boTable.setMuO(dl.getOilViscosity());

// Add gas properties
boTable.setBg(dl.getBg());
boTable.setMuG(dl.getGasViscosity());

// Step 4: Separator test for stock tank conditions
SeparatorTest sep = new SeparatorTest(oil.clone());
sep.setSeparatorConditions(
    new double[]{323.15, 288.15},
    new double[]{30.0, 1.01325}
);
sep.run();

double rho_o_sc = sep.getOilDensity();

// Step 5: Create flash calculator
BlackOilFlash boFlash = new BlackOilFlash(boTable, rho_o_sc, 0.85, 1000.0);

// Step 6: Calculate at different conditions
System.out.println("P (bar)\tRs\tBo\tρ_oil\tμ_oil");
for (double P : new double[]{50, 100, 150, 200}) {
    BlackOilFlashResult r = boFlash.flash(P, 373.15, 1000.0, 150000.0, 0.0);
    System.out.printf("%.0f\t%.1f\t%.4f\t%.1f\t%.3f%n",
        P, r.Rs, r.Bo, r.rho_o, r.mu_o);
}

Best Practices

  1. Validate against compositional - Compare black oil results with full EoS
  2. Use appropriate correlations - Match fluid type (light, medium, heavy oil)
  3. Check consistency - Ensure Rs and Bo are consistent at bubble point
  4. Include undersaturated region - Extend table above bubble point
  5. Document separator conditions - Record conditions used for conversion

Limitations


Flash Playbook

Black-oil flash playbook from regression tests

The black-oil flash workflow is exercised in SystemBlackOilTest, which demonstrates both Eclipse deck import and direct tabular setup before running a flash. The following notes pair the tested setup with the theory it relies on so you can reproduce the flow efficiently.

Importing Eclipse PVT decks

testBasicFlash constructs a minimal Eclipse deck with metric units, PVTO/PVTG/PVTW tables, and standard-condition densities, then feeds it to EclipseBlackOilImporter.fromFile(...).【F:src/test/java/neqsim/blackoil/SystemBlackOilTest.java†L31-L74】 The importer returns a BlackOilPVTTable and an initialized SystemBlackOil instance whose bubblepoint is read directly from the tables.

Key steps mirrored from the test:

  1. Write the deck text to disk (or keep it in memory) and call the importer.
  2. Supply standard-condition densities (oil/gas/water) when constructing SystemBlackOil.
  3. Set the current pressure, temperature, and standard volumes (setStdTotals) before calling flash().
  4. Inspect reservoir volumes, viscosities, and densities from the returned system object— the test only asserts positivity but those values correspond to the PVTO/PVTG/PVTW correlations.

The flash solves phase-split mass balance for three pseudo-phases using deck-derived formation volume factors (B_o, B_g, B_w), dissolved gas–oil ratio (R_s), and vaporized oil–gas ratio (R_v). Reservoir volumes are calculated as

[ V_{res} = B_x \times V_{std} ]

for each phase (x\in{o,g,w}), with viscosities pulled directly from the tables.

Building PVT tables in code

testDirectPVTTable shows how to bypass deck parsing by interpolating PVTO/PVTG/PVTW data onto a merged pressure grid, then wrapping it in BlackOilPVTTable.Record entries.【F:src/test/java/neqsim/blackoil/SystemBlackOilTest.java†L77-L133】 The resulting table is flashed the same way as the imported one.

When scripting your own tests:

Black Oil Export

Black Oil PVT and Reservoir Simulator Export

This document describes NeqSim's black oil implementation and the ability to export PVT data to reservoir simulators like Eclipse and CMG.

Overview

NeqSim provides a complete workflow for converting compositional EOS (Equation of State) fluid models to black oil PVT tables suitable for reservoir simulation. The neqsim.blackoil package includes:

Black Oil Conversion

The BlackOilConverter class performs flash calculations at multiple pressures to generate black oil properties:

import neqsim.blackoil.BlackOilConverter;
import neqsim.blackoil.BlackOilPVTTable;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemSrkEos;

// Create a compositional fluid
SystemInterface fluid = new SystemSrkEos(373.15, 200.0); // 100°C, 200 bar
fluid.addComponent("nitrogen", 0.01);
fluid.addComponent("CO2", 0.02);
fluid.addComponent("methane", 0.50);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.05);
fluid.addComponent("n-butane", 0.03);
fluid.addComponent("n-pentane", 0.02);
fluid.addComponent("n-hexane", 0.02);
fluid.addComponent("n-heptane", 0.07);
fluid.addComponent("water", 0.20);
fluid.setMixingRule("classic");

// Define pressure grid for PVT table
double[] pressures = {20, 50, 100, 150, 200, 250, 300, 350, 400}; // bar

// Convert to black oil
double Tref = 373.15;  // Reference temperature (K)
double Pstd = 1.01325; // Standard pressure (bar)
double Tstd = 288.15;  // Standard temperature (K)

BlackOilPVTTable pvtTable = BlackOilConverter.convert(fluid, Tref, pressures, Pstd, Tstd);

Black Oil Properties

The BlackOilPVTTable contains records with the following properties:

Property Symbol Unit Description
Pressure p bar Reservoir pressure
Solution GOR Rs Sm³/Sm³ Gas dissolved in oil at standard conditions
Oil FVF Bo Rm³/Sm³ Oil formation volume factor
Oil viscosity μo Pa·s Oil dynamic viscosity
Gas FVF Bg Rm³/Sm³ Gas formation volume factor
Gas viscosity μg Pa·s Gas dynamic viscosity
Vaporized OGR Rv Sm³/Sm³ Oil vaporized in gas (for volatile oil/gas condensate)
Water FVF Bw Rm³/Sm³ Water formation volume factor
Water viscosity μw Pa·s Water dynamic viscosity

Eclipse Export

The EclipseEOSExporter generates PVT include files compatible with Schlumberger Eclipse reservoir simulator.

Basic Usage

import neqsim.blackoil.io.EclipseEOSExporter;
import java.nio.file.Path;

// Export with default settings (METRIC units)
EclipseEOSExporter.toFile(fluid, Path.of("PVT.INC"));

// Or get as string
String eclipseOutput = EclipseEOSExporter.toString(fluid);

Configuration Options

import neqsim.blackoil.io.EclipseEOSExporter;
import neqsim.blackoil.io.EclipseEOSExporter.ExportConfig;
import neqsim.blackoil.io.EclipseEOSExporter.Units;

ExportConfig config = new ExportConfig()
    .setUnits(Units.FIELD)                              // METRIC or FIELD
    .setIncludeHeader(true)                             // Include header comments
    .setComment("Generated for Field X, Well A-1")      // Custom comment
    .setPressureGrid(new double[]{50, 100, 150, 200, 250, 300, 350, 400})
    .setIncludePVTO(true)                               // Include live oil table
    .setIncludePVTG(true)                               // Include wet gas table
    .setIncludePVTW(true)                               // Include water properties
    .setIncludeDensity(true);                           // Include DENSITY keyword

String output = EclipseEOSExporter.toString(fluid, config);

Export from Pre-computed PVT Table

// If you already have a BlackOilPVTTable
double rhoOilSc = 820.0;   // Oil density at standard conditions (kg/m³)
double rhoGasSc = 1.2;     // Gas density at standard conditions (kg/m³)
double rhoWaterSc = 1000.0; // Water density at standard conditions (kg/m³)

EclipseEOSExporter.toFile(pvtTable, rhoOilSc, rhoGasSc, rhoWaterSc, Path.of("PVT.INC"));

Eclipse Output Format

The exporter generates the following Eclipse keywords:

DENSITY

DENSITY
-- Oil      Gas       Water
   820.0    1.200000  1000.0 /

PVTO (Live Oil)

PVTO
-- Rs       P         Bo        mu_o
   50.0     100.0     1.250     0.00080
            150.0     1.220     0.00085
            200.0     1.200     0.00090 /
   80.0     150.0     1.350     0.00070
            200.0     1.320     0.00075 /
/

PVTG (Wet Gas)

PVTG
-- Pg       Rv        Bg        mu_g
   100.0    0.0       0.0120    1.50E-05
            0.0001    0.0122    1.52E-05 /
   150.0    0.0       0.0080    1.80E-05 /
/

PVTW (Water)

PVTW
-- Pref     Bw        Cw        mu_w    Cv
   200.0    1.020     4.5E-05   0.00050 0.0 /

Unit Systems

Property METRIC FIELD
Pressure bar (BARSA) psia (PSIA)
Density kg/m³ lb/ft³
FVF rm³/sm³ rb/stb
GOR sm³/sm³ scf/stb
Viscosity cP cP

CMG Export

The CMGEOSExporter generates PVT data files compatible with CMG reservoir simulators (IMEX, GEM, STARS).

Basic Usage

import neqsim.blackoil.io.CMGEOSExporter;
import java.nio.file.Path;

// Export with default settings (IMEX, SI units)
CMGEOSExporter.toFile(fluid, Path.of("PVT.DAT"));

// Or get as string
String cmgOutput = CMGEOSExporter.toString(fluid);

Configuration Options

import neqsim.blackoil.io.CMGEOSExporter;
import neqsim.blackoil.io.CMGEOSExporter.ExportConfig;
import neqsim.blackoil.io.CMGEOSExporter.Simulator;
import neqsim.blackoil.io.CMGEOSExporter.Units;

ExportConfig config = new ExportConfig()
    .setSimulator(Simulator.IMEX)                       // IMEX, GEM, or STARS
    .setUnits(Units.FIELD)                              // SI or FIELD
    .setModelName("RESERVOIR_FLUID_MODEL")              // Model identifier
    .setComment("PVT model for Field X")                // Custom comment
    .setPressureGrid(new double[]{50, 100, 150, 200, 250, 300});

CMGEOSExporter.toFile(fluid, Path.of("PVT.DAT"), config);

CMG Simulators

Simulator Type Description
IMEX Black Oil Implicit-Explicit black oil simulator
GEM Compositional Generalized equation-of-state model
STARS Thermal Steam, thermal, and advanced processes

IMEX Output Format

** ============================================================
** Generated by NeqSim - Black Oil PVT Export
** Simulator: IMEX
** Units: SI
** ============================================================

*MODEL *BLACKOIL

*DENSITY *OIL 820.0
*DENSITY *GAS 1.2
*DENSITY *WATER 1000.0

*BOTOIL
** P(kPa)    Rs(m3/m3)    Bo(m3/m3)    mu_o(cP)
   10000.0   50.0         1.250        0.80
   15000.0   80.0         1.350        0.70
   20000.0   100.0        1.420        0.65

*BOTGAS
** P(kPa)    Bg(m3/m3)    mu_g(cP)
   10000.0   0.0120       0.015
   15000.0   0.0080       0.018
   20000.0   0.0060       0.021

Unit Systems

Property SI FIELD
Pressure kPa psia
Density kg/m³ lb/ft³
FVF m³/m³ bbl/bbl
GOR m³/m³ scf/bbl
Viscosity cP cP

Complete Workflow Example

import neqsim.blackoil.BlackOilConverter;
import neqsim.blackoil.BlackOilPVTTable;
import neqsim.blackoil.io.EclipseEOSExporter;
import neqsim.blackoil.io.CMGEOSExporter;
import neqsim.thermo.system.SystemSrkEos;
import java.nio.file.Path;

public class PVTExportExample {
    public static void main(String[] args) {
        // 1. Create compositional fluid model
        var fluid = new SystemSrkEos(373.15, 250.0);
        fluid.addComponent("methane", 0.60);
        fluid.addComponent("ethane", 0.08);
        fluid.addComponent("propane", 0.05);
        fluid.addComponent("n-butane", 0.03);
        fluid.addComponent("n-pentane", 0.02);
        fluid.addComponent("n-heptane", 0.12);
        fluid.addComponent("water", 0.10);
        fluid.setMixingRule("classic");

        // 2. Export to Eclipse (METRIC units)
        var eclipseConfig = new EclipseEOSExporter.ExportConfig()
            .setUnits(EclipseEOSExporter.Units.METRIC)
            .setComment("Light oil reservoir - Block A");
        EclipseEOSExporter.toFile(fluid, Path.of("eclipse_pvt.inc"), eclipseConfig);

        // 3. Export to CMG IMEX (FIELD units)
        var cmgConfig = new CMGEOSExporter.ExportConfig()
            .setSimulator(CMGEOSExporter.Simulator.IMEX)
            .setUnits(CMGEOSExporter.Units.FIELD)
            .setModelName("BLOCK_A_FLUID");
        CMGEOSExporter.toFile(fluid, Path.of("cmg_pvt.dat"), cmgConfig);

        System.out.println("PVT files exported successfully!");
    }
}

E300 Compositional EOS Export

In addition to black-oil PVT tables, NeqSim can export the full compositional EOS model to Eclipse E300 format. This is useful when you need to preserve all EOS parameters for compositional reservoir simulation.

E300 Format Background

The Eclipse E300 format is a proprietary format originated by Schlumberger for their Eclipse compositional reservoir simulator. While there is no public formal specification, the format is widely used and supported by:

The NeqSim E300 export is compatible with files generated by PVTsim Nova and produces output that can be read by Eclipse 300 and OPM Flow.

Basic Usage

import neqsim.thermo.util.readwrite.EclipseFluidReadWrite;

// Export fluid to E300 compositional format
EclipseFluidReadWrite.write(fluid, "RESERVOIR_FLUID.e300", 100.0);  // 100°C reservoir temp

// Get as string for inspection
String e300Content = EclipseFluidReadWrite.toE300String(fluid, 100.0);

E300 File Keywords

The exported E300 file contains all EOS parameters needed for compositional simulation:

Keyword Description Units
METRIC Units system (always METRIC) -
NCOMPS Number of components -
EOS Equation of state type SRK/PR
PRCORR Peng-Robinson correction (for PR EOS only) -
RTEMP Reservoir temperature °C
STCOND Standard conditions °C, bara
CNAMES Component names -
TCRIT Critical temperatures K
PCRIT Critical pressures bar
ACF Acentric factors -
OMEGAA EOS parameter a (0.45724 for PR, 0.42748 for SRK) -
OMEGAB EOS parameter b (0.07780 for PR, 0.08664 for SRK) -
MW Molecular weights g/mol
TBOIL Normal boiling points K
VCRIT Critical volumes m³/kmol
ZCRIT Critical Z-factors -
SSHIFT Volume translation -
PARACHOR Parachor values -
ZI Mole fractions -
BIC Binary interaction coefficients -

Round-Trip: Export and Import

import neqsim.thermo.util.readwrite.EclipseFluidReadWrite;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create and tune fluid
SystemInterface tunedFluid = /* ... your tuned fluid ... */;

// Export to E300 file
EclipseFluidReadWrite.write(tunedFluid, "TUNED.e300", 100.0);

// Read it back (e.g., in another session or application)
SystemInterface importedFluid = EclipseFluidReadWrite.read("TUNED.e300");

// Use the imported fluid
importedFluid.setPressure(200.0, "bara");
importedFluid.setTemperature(100.0, "C");
new ThermodynamicOperations(importedFluid).TPflash();

Optional E300 Keywords

Some E300 files include additional keywords that are handled by NeqSim:

Keyword Description Status in NeqSim
BICS BIC at surface conditions Supported (read and write)
SSHIFTS Volume shift at surface conditions Supported (read and write)
LBCCOEF Lohrenz-Bray-Clark viscosity coefficients Supported (read and write)
PEDERSEN Pedersen viscosity correlation flag Supported (read and write)

LBCCOEF Support

NeqSim supports reading and writing the LBCCOEF keyword for the Lohrenz-Bray-Clark (LBC) viscosity correlation:

LBCCOEF
0.1084806 -0.0295031 0.1130421 -0.0553108 0.0093324 /

When reading an E300 file with LBCCOEF:

When writing an E300 file:

PEDERSEN Support

The PEDERSEN keyword is a flag indicating the Pedersen (PFCT) corresponding-states viscosity correlation:

PEDERSEN

When reading: NeqSim applies the PFCT viscosity model to all phases. When writing: If PFCT viscosity model is active, the PEDERSEN keyword is output.

BICS and SSHIFTS Support

These are written automatically when exporting E300 files.

Compatibility Notes

Python Usage

from jpype import JClass

EclipseFluidReadWrite = JClass('neqsim.thermo.util.readwrite.EclipseFluidReadWrite')

# Export to E300 file
EclipseFluidReadWrite.write(fluid, "tuned_fluid.e300", 100.0)

# Read back
imported_fluid = EclipseFluidReadWrite.read("tuned_fluid.e300")

Integration with Whitson PVT Workflows

The export functionality enables seamless integration with PVT software like whitsonPVT:

  1. EOS Tuning - Tune EOS parameters using experimental PVT data in NeqSim
  2. C7+ Characterization - Use Whitson's Gamma model for plus-fraction splitting
  3. Export - Generate simulator-ready PVT files for reservoir engineering
// After EOS tuning and characterization
SystemInterface tunedFluid = /* ... tuned fluid model ... */;

// Export for reservoir simulation workflow
EclipseEOSExporter.toFile(tunedFluid, Path.of("TUNED_PVT.INC"));

API Reference

EclipseFluidReadWrite (Compositional EOS)

Method Description
read(String) Read E300 file into NeqSim fluid
write(SystemInterface, String) Write fluid to E300 file
write(SystemInterface, String, double) Write with reservoir temp (°C)
toE300String(SystemInterface) Export to E300 format string
toE300String(SystemInterface, double) Export with reservoir temp

EclipseEOSExporter (Black-Oil PVT)

Method Description
toString(SystemInterface) Export fluid to Eclipse format string
toString(SystemInterface, ExportConfig) Export with configuration
toString(BlackOilPVTTable, rhoO, rhoG, rhoW) Export PVT table
toFile(SystemInterface, Path) Write to file
toFile(SystemInterface, Path, ExportConfig) Write with configuration

CMGEOSExporter

Method Description
toString(SystemInterface) Export fluid to CMG format string
toString(SystemInterface, ExportConfig) Export with configuration
toString(BlackOilPVTTable, rhoO, rhoG, rhoW) Export PVT table
toFile(SystemInterface, Path) Write to file
toFile(SystemInterface, Path, ExportConfig) Write with configuration

See Also

Chapter 37: Flow Assurance

Flow Assurance

Flow Assurance in NeqSim

Flow assurance is the discipline ensuring that hydrocarbon fluids can be produced, transported, and processed safely and economically throughout the life of a field. NeqSim provides comprehensive tools for predicting and managing flow assurance challenges.

Overview

Flow assurance encompasses the prevention and remediation of:

Documentation Structure

Topic Description
Asphaltene Modeling Overview of asphaltene stability analysis
CPA-Based Asphaltene Calculations Thermodynamic onset pressure/temperature
De Boer Asphaltene Screening Empirical screening correlation
Asphaltene Parameter Fitting Tuning CPA parameters to experimental data
Asphaltene Method Comparison Comparing CPA vs De Boer approaches
Asphaltene Model Validation Validation against SPE-24987 field data

Key Classes

Asphaltene Analysis

Class Package Purpose
AsphalteneCharacterization neqsim.thermo.characterization SARA-based characterization
AsphalteneStabilityAnalyzer neqsim.pvtsimulation.flowassurance High-level CPA analysis API
DeBoerAsphalteneScreening neqsim.pvtsimulation.flowassurance Empirical De Boer screening
AsphalteneMethodComparison neqsim.pvtsimulation.flowassurance Compare multiple methods
AsphalteneOnsetPressureFlash neqsim.thermodynamicoperations Onset pressure calculation
AsphalteneOnsetTemperatureFlash neqsim.thermodynamicoperations Onset temperature calculation
AsphalteneOnsetFitting neqsim.pvtsimulation.util.parameterfitting Fit CPA parameters to experimental onset data
AsphalteneOnsetFunction neqsim.pvtsimulation.util.parameterfitting Levenberg-Marquardt function for fitting

Quick Start

Simple Screening (De Boer Method)

import neqsim.pvtsimulation.flowassurance.DeBoerAsphalteneScreening;

// Create screening for specific conditions
DeBoerAsphalteneScreening screening = new DeBoerAsphalteneScreening(
    350.0,  // Reservoir pressure [bar]
    150.0,  // Bubble point pressure [bar]
    750.0   // In-situ oil density [kg/m³]
);

// Get risk assessment
String risk = screening.evaluateRisk();
double riskIndex = screening.calculateRiskIndex();

System.out.println("Asphaltene Risk: " + risk);
System.out.println("Risk Index: " + riskIndex);

Thermodynamic Analysis (CPA Method)

import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create CPA fluid with asphaltene
SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(373.15, 200.0);
fluid.addComponent("methane", 0.5);
fluid.addComponent("n-heptane", 0.45);
fluid.addComponent("asphaltene", 0.05);
fluid.setMixingRule("classic");

// Calculate onset pressure
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.asphalteneOnsetPressure();

double onsetPressure = fluid.getPressure();
System.out.println("Onset Pressure: " + onsetPressure + " bar");

Parameter Fitting (Tuning to Lab Data)

import neqsim.pvtsimulation.util.parameterfitting.AsphalteneOnsetFitting;

// Create fitter with your fluid system
AsphalteneOnsetFitting fitter = new AsphalteneOnsetFitting(fluid);

// Add experimental onset points (T in Kelvin, P in bar)
fitter.addOnsetPoint(353.15, 350.0);  // 80°C
fitter.addOnsetPoint(373.15, 320.0);  // 100°C
fitter.addOnsetPoint(393.15, 280.0);  // 120°C

// Set initial parameter guesses and run fitting
fitter.setInitialGuess(3500.0, 0.005);  // epsilon/R, kappa
fitter.solve();

// Get fitted CPA parameters
double epsilonR = fitter.getFittedAssociationEnergy();
double kappa = fitter.getFittedAssociationVolume();

Asphaltene Component in NeqSim Database

NeqSim includes a pre-defined asphaltene pseudo-component with CPA parameters suitable for typical asphaltene modeling:

Property Value Unit
Molecular Weight 750 g/mol
Critical Temperature 1049.85 K
Critical Pressure 8.0 bar
Acentric Factor 1.5 -
Association Energy (ε/R) 3500 K
Association Volume (κ) 0.005 -

These parameters can be tuned to match specific experimental data using the AsphalteneOnsetFitting class.

PhaseType.ASPHALTENE

When asphaltene precipitates, NeqSim identifies it using the dedicated PhaseType.ASPHALTENE enum value. This enables:

import neqsim.thermo.phase.PhaseType;

// After flash calculation
if (fluid.hasPhaseType(PhaseType.ASPHALTENE)) {
    PhaseInterface asphaltene = fluid.getPhaseOfType("asphaltene");
    System.out.println("Asphaltene density: " + asphaltene.getDensity("kg/m3") + " kg/m³");
    System.out.println("Asphaltene fraction: " + (asphaltene.getBeta() * 100) + "%");
}

See Asphaltene Modeling for more details on PhaseType.ASPHALTENE.

Asphaltene Modeling

Asphaltene Modeling in NeqSim

Introduction

Asphaltenes are the heaviest and most polar fraction of crude oil, defined operationally as the fraction soluble in aromatic solvents (toluene) but insoluble in paraffinic solvents (n-heptane or n-pentane). Asphaltene precipitation during production can cause:

NeqSim provides multiple approaches to assess asphaltene stability, ranging from simple empirical correlations to rigorous thermodynamic modeling.

Asphaltene Structure and Properties

Molecular Characteristics

Property Typical Range Notes
Molecular Weight 500 - 10,000 g/mol Polydisperse distribution
H/C Ratio 0.9 - 1.2 Lower than other oil fractions
Heteroatom Content 0.5 - 10 wt% N, S, O, metals (V, Ni)
Aromaticity 40 - 60% Polyaromatic core structures

Colloidal Model

Asphaltenes exist in crude oil as colloidal particles stabilized by resins (maltenes). The Colloidal Instability Index (CII) quantifies this balance:

$$ \text{CII} = \frac{\text{Saturates} + \text{Asphaltenes}}{\text{Aromatics} + \text{Resins}} $$

Where:

Resin-to-Asphaltene Ratio

Another stability indicator:

$$ \text{R/A} = \frac{\text{Resins wt\%}}{\text{Asphaltenes wt\%}} $$

SARA Analysis

SARA fractionation separates crude oil into four pseudo-component groups:

Fraction Description Typical Range
Saturates Alkanes and cycloalkanes 40-80%
Aromatics Mono/poly-aromatic hydrocarbons 10-35%
Resins Polar, heteroatom-containing 5-25%
Asphaltenes Heaviest polar fraction 0-15%

Using SARA in NeqSim

import neqsim.thermo.characterization.AsphalteneCharacterization;

// Create characterization from SARA analysis
AsphalteneCharacterization sara = new AsphalteneCharacterization(
    0.45,  // Saturates weight fraction
    0.30,  // Aromatics weight fraction
    0.20,  // Resins weight fraction
    0.05   // Asphaltenes weight fraction
);

// Get stability indicators
double cii = sara.getColloidalInstabilityIndex();
double ra = sara.getResinToAsphalteneRatio();
String stability = sara.evaluateStability();

System.out.println("CII: " + cii);
System.out.println("R/A Ratio: " + ra);
System.out.println("Stability: " + stability);

Precipitation Triggers

Asphaltene precipitation occurs when the solubility parameter of the oil phase changes sufficiently to destabilize the asphaltene colloids:

Pressure Depletion

As pressure drops below the bubble point:

  1. Light components evolve into vapor phase
  2. Remaining liquid becomes denser and more aliphatic
  3. Asphaltene solubility decreases
  4. Maximum instability typically occurs near bubble point

Temperature Changes

Composition Changes

Modeling Approaches in NeqSim

1. Empirical Screening (De Boer)

Fast, conservative screening based on field correlations. See De Boer Screening.

Advantages:

Limitations:

2. Thermodynamic Modeling (CPA EOS)

Rigorous equation of state approach. See CPA Calculations.

Advantages:

Limitations:

PhaseType.ASPHALTENE

NeqSim uses a dedicated PhaseType.ASPHALTENE enum value to distinguish precipitated asphaltenes from other solid phases (wax, hydrate). This enables:

import neqsim.thermo.phase.PhaseType;

// Check for asphaltene precipitation
if (fluid.hasPhaseType(PhaseType.ASPHALTENE)) {
    PhaseInterface asphaltene = fluid.getPhaseOfType("asphaltene");

    // Access asphaltene phase properties
    double density = asphaltene.getDensity("kg/m3");        // ~1150 kg/m³
    double Cp = asphaltene.getCp("kJ/kgK");                 // ~0.9 kJ/kgK
    double viscosity = asphaltene.getViscosity("Pa*s");     // ~10,000 Pa·s
    double thermalCond = asphaltene.getThermalConductivity("W/mK"); // ~0.20 W/mK
    double soundSpeed = asphaltene.getSoundSpeed("m/s");    // ~1745 m/s
}

3. Pedersen Classical Cubic EOS Approach

A simpler approach using classical cubic equations of state (SRK/PR) without association terms, developed by K.S. Pedersen and presented at GOTECH Dubai 2025.

Reference: Pedersen, K.S. (2025). "The Mechanisms Behind Asphaltene Precipitation – Successfully Handled by a Classical Cubic Equation of State." SPE-224534-MS, GOTECH, Dubai.

Theoretical Background

The key insight from Pedersen's work is that asphaltene precipitation can be successfully modeled as a liquid-liquid phase split using classical cubic equations of state. The approach treats asphaltene as a heavy pseudo-component characterized using the same correlations as C7+ fractions.

Critical Property Correlations:

$$T_c = a_0 + a_1 \ln(M) + a_2 M + \frac{a_3}{M}$$

$$\ln(P_c) = b_0 + b_1 \rho^{0.25} + \frac{b_2}{M} + \frac{b_3}{M^2}$$

$$\omega = c_0 + c_1 \ln(M) + c_2 \rho + c_3 M$$

Where:

Basic Usage

import neqsim.thermo.characterization.PedersenAsphalteneCharacterization;
import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create SRK system (classical cubic EOS)
SystemSrkEos fluid = new SystemSrkEos(373.15, 200.0);
fluid.addComponent("methane", 0.40);
fluid.addComponent("n-heptane", 0.45);
fluid.addComponent("nC20", 0.10);

// Create Pedersen asphaltene characterization
PedersenAsphalteneCharacterization asphChar = new PedersenAsphalteneCharacterization();
asphChar.setAsphalteneMW(750.0);        // Molecular weight (g/mol)
asphChar.setAsphalteneDensity(1.10);    // Density (g/cm³)
asphChar.addAsphalteneToSystem(fluid, 0.05);  // Add 0.05 mol

// IMPORTANT: Set mixing rule AFTER adding all components
fluid.setMixingRule("classic");
fluid.init(0);

// Print characterization results
System.out.println(asphChar.toString());

Complete Oil Characterization with TBP Fractions

For realistic oil systems, combine asphaltene characterization with TBP (True Boiling Point) fraction characterization:

import neqsim.thermo.characterization.PedersenAsphalteneCharacterization;
import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create system and set Pedersen TBP model
SystemInterface oil = new SystemSrkEos(373.15, 200.0);
oil.getCharacterization().setTBPModel("PedersenSRK");

// Add light components (defined compounds)
oil.addComponent("nitrogen", 0.005);
oil.addComponent("CO2", 0.02);
oil.addComponent("methane", 0.35);
oil.addComponent("ethane", 0.08);
oil.addComponent("propane", 0.05);
oil.addComponent("i-butane", 0.01);
oil.addComponent("n-butane", 0.02);
oil.addComponent("i-pentane", 0.015);
oil.addComponent("n-pentane", 0.015);
oil.addComponent("n-hexane", 0.02);

// Add C7+ fractions as TBP pseudo-components
// Parameters: name, moles, MW (kg/mol), density (g/cm³)
oil.addTBPfraction("C7", 0.10, 96.0 / 1000.0, 0.738);
oil.addTBPfraction("C8", 0.08, 107.0 / 1000.0, 0.765);
oil.addTBPfraction("C9", 0.06, 121.0 / 1000.0, 0.781);
oil.addTBPfraction("C10", 0.04, 134.0 / 1000.0, 0.792);
oil.addTBPfraction("C11-C15", 0.06, 180.0 / 1000.0, 0.825);
oil.addTBPfraction("C16-C20", 0.03, 260.0 / 1000.0, 0.865);
oil.addTBPfraction("C21+", 0.02, 450.0 / 1000.0, 0.920);

// Add asphaltene using Pedersen characterization
PedersenAsphalteneCharacterization asphChar = new PedersenAsphalteneCharacterization();
asphChar.setAsphalteneMW(850.0);
asphChar.setAsphalteneDensity(1.12);
asphChar.addAsphalteneToSystem(oil, 0.015);

// Set mixing rule and initialize
oil.setMixingRule("classic");
oil.init(0);

// Perform flash calculation
ThermodynamicOperations ops = new ThermodynamicOperations(oil);
ops.TPflash();
oil.prettyPrint();

Tuning to Experimental Data

The class provides tuning multipliers for fitting to experimental onset pressure data:

// Create characterization
PedersenAsphalteneCharacterization asphChar = new PedersenAsphalteneCharacterization();
asphChar.setAsphalteneMW(750.0);
asphChar.setAsphalteneDensity(1.10);

// Apply tuning multipliers to match experimental onset pressure
asphChar.setTcMultiplier(1.03);     // Increase Tc by 3%
asphChar.setPcMultiplier(0.97);     // Decrease Pc by 3%
asphChar.setOmegaMultiplier(1.05);  // Increase ω by 5%

// Or use convenience method for all at once
asphChar.setTuningParameters(1.03, 0.97, 1.05);

// Characterize and add to system
asphChar.characterize();
asphChar.addAsphalteneToSystem(fluid, 0.05);

// Reset to defaults if needed
asphChar.resetTuningParameters();

Tuning Guidelines:

Parameter Effect on Onset Pressure Typical Range
tcMultiplier Higher Tc → lower onset pressure 0.95 - 1.10
pcMultiplier Higher Pc → higher onset pressure 0.85 - 1.15
omegaMultiplier Higher ω → complex effects 0.90 - 1.20

Distributed Asphaltene Pseudo-Components

For heavy oils, represent asphaltene as multiple pseudo-components with distributed MW:

// Add distributed asphaltene (3 pseudo-components)
asphChar.setAsphalteneMW(1000.0);   // Average MW
asphChar.setAsphalteneDensity(1.15);
asphChar.addDistributedAsphaltene(heavyOil, 0.08, 3);

// This creates 3 components: Asph_1_PC, Asph_2_PC, Asph_3_PC
// with MW ranging from 0.5x to 2x the average MW

Typical Property Ranges

Based on the Pedersen correlations, asphaltene components show these typical properties:

Property Light Asphaltene (MW=500) Medium (MW=750) Heavy (MW=1500)
Tc 950-1000 K 990-1010 K 1040-1060 K
Pc 17-19 bar 15-17 bar 14-16 bar
ω 0.9-1.0 0.9-1.0 1.2-1.4
Tb 700-800 K 800-850 K 900-950 K

Advantages:

Limitations:

4. Combined Approach

Use De Boer for initial screening, then CPA or Pedersen method for detailed analysis of flagged cases. See Method Comparison.

Best Practices

For Field Development

  1. Early Screening: Use De Boer on all fluids
  2. Focused Analysis: Apply CPA to flagged samples
  3. Lab Validation: Confirm predictions with HPM/AOP tests
  4. Parameter Tuning: Use AsphalteneOnsetFitting to match measured onset data
  5. Monitoring Strategy: Plan for real-time detection

For Process Design

  1. Operating Envelope: Generate precipitation PT curves
  2. Injection Studies: Model CO₂/gas injection effects
  3. Blending Optimization: Predict compatibility issues
  4. Inhibitor Screening: Evaluate chemical effectiveness
┌─────────────────────────────────────────────────────────────────┐
│                    ASPHALTENE ANALYSIS WORKFLOW                 │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  Step 1: DE BOER SCREENING                                      │
│  • Fast empirical screening                                     │
│  • Input: P_res, P_bub, density                                 │
│  • Output: Risk category (NO/SLIGHT/MODERATE/SEVERE)            │
└─────────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┴───────────────┐
              │                               │
              ▼                               ▼
     ┌──────────────┐              ┌──────────────────────┐
     │  LOW RISK    │              │  ELEVATED RISK       │
     │  • Monitor   │              │  • Proceed to CPA    │
     │  • Document  │              │  analysis            │
     └──────────────┘              └──────────────────────┘
                                              │
                                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  Step 2: CPA THERMODYNAMIC ANALYSIS                             │
│  • Create fluid with asphaltene component                       │
│  • Calculate onset pressure/temperature                         │
│  • Generate precipitation envelope                              │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  Step 3: LABORATORY VALIDATION                                  │
│  • Measure AOP at multiple temperatures                         │
│  • SARA analysis for composition                                │
│  • HPM microscopy for onset detection                           │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  Step 4: PARAMETER TUNING (AsphalteneOnsetFitting)              │
│  • Fit CPA parameters to lab data                               │
│  • Validate predictions at other conditions                     │
│  • Document fitted parameters for field use                     │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  Step 5: FIELD APPLICATION                                      │
│  • Predict onset during depletion/injection                     │
│  • Design mitigation strategies                                 │
│  • Establish monitoring program                                 │
└─────────────────────────────────────────────────────────────────┘

References

  1. De Boer, R.B., et al. (1995). "Screening of Crude Oils for Asphalt Precipitation: Theory, Practice, and the Selection of Inhibitors." SPE Production & Facilities. SPE-24987-PA

  2. Mullins, O.C. (2010). "The Modified Yen Model." Energy & Fuels, 24(4), 2179-2207.

  3. Leontaritis, K.J., and Mansoori, G.A. (1988). "Asphaltene Deposition: A Survey of Field Experiences and Research Approaches." Journal of Petroleum Science and Engineering.

  4. Victorov, A.I., and Firoozabadi, A. (1996). "Thermodynamic Micellization Model of Asphaltene Precipitation from Petroleum Fluids." AIChE Journal.

  5. Kontogeorgis, G.M., and Folas, G.K. (2010). "Thermodynamic Models for Industrial Applications." Wiley.

  6. Li, Z., and Firoozabadi, A. (2010). "Modeling Asphaltene Precipitation by n-Alkanes from Heavy Oils and Bitumens Using Cubic-Plus-Association Equation of State." Energy & Fuels, 24, 1106-1113.

  7. Vargas, F.M., et al. (2009). "Development of a General Method for Modeling Asphaltene Stability." Energy & Fuels, 23, 1140-1146.

  8. Pedersen, K.S. (2025). "The Mechanisms Behind Asphaltene Precipitation – Successfully Handled by a Classical Cubic Equation of State." SPE-224534-MS, GOTECH, Dubai.

  9. Pedersen, K.S., Christensen, P.L. (2007). "Phase Behavior of Petroleum Reservoir Fluids." CRC Press.

  10. Pedersen, K.S., Fredenslund, A., Thomassen, P. (1989). "Properties of Oils and Natural Gases." Gulf Publishing.

Asphaltene CPA

CPA-Based Asphaltene Calculations

Overview

The Cubic Plus Association (CPA) equation of state extends classical cubic equations (SRK or PR) with an association term to handle self-associating and cross-associating compounds. This makes CPA particularly suitable for asphaltene modeling, where polar interactions and molecular aggregation are important.

Theory

CPA Equation of State

The CPA pressure equation combines cubic and association contributions:

$$ P = P_{\text{cubic}} + P_{\text{assoc}} $$

Where:

Association in Asphaltene Modeling

Asphaltenes self-associate through:

CPA captures these through association parameters:

Solid Phase Modeling

Asphaltene precipitation is modeled as a solid phase formation. At the onset conditions, the fugacity of asphaltene in the liquid phase equals that in the solid phase:

$$ f_{\text{asph}}^{L}(T, P, x) = f_{\text{asph}}^{S}(T, P) $$

Implementation Classes

ThermodynamicOperations Methods

import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create CPA fluid system
SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(373.15, 250.0);
fluid.addComponent("methane", 0.40);
fluid.addComponent("propane", 0.10);
fluid.addComponent("n-heptane", 0.45);
fluid.addComponent("asphaltene", 0.05);
fluid.setMixingRule("classic");

ThermodynamicOperations ops = new ThermodynamicOperations(fluid);

// Calculate asphaltene onset pressure
// Searches from current pressure down to find precipitation
ops.asphalteneOnsetPressure();
double onsetP = fluid.getPressure();

// Calculate asphaltene onset temperature at fixed pressure
fluid.setPressure(200.0);
ops.asphalteneOnsetTemperature();
double onsetT = fluid.getTemperature();

AsphalteneOnsetPressureFlash

Direct flash operation for onset pressure calculation:

import neqsim.thermodynamicoperations.flashops.saturationops.AsphalteneOnsetPressureFlash;

// Create flash operation
AsphalteneOnsetPressureFlash flash = new AsphalteneOnsetPressureFlash(fluid);

// Configure search range
flash.setMaxPressure(400.0);  // Start pressure [bar]
flash.setMinPressure(1.0);    // End pressure [bar]
flash.setPressureStep(10.0);  // Coarse search step [bar]
flash.setTolerance(0.1);      // Bisection tolerance [bar]

// Run calculation
flash.run();

// Get results
if (flash.isOnsetFound()) {
    double onsetPressure = flash.getOnsetPressure();
    System.out.println("Onset Pressure: " + onsetPressure + " bar");
} else {
    System.out.println("No onset found in search range");
}

AsphalteneOnsetTemperatureFlash

Temperature-based onset calculation:

import neqsim.thermodynamicoperations.flashops.saturationops.AsphalteneOnsetTemperatureFlash;

// Create flash operation at fixed pressure
AsphalteneOnsetTemperatureFlash flash = new AsphalteneOnsetTemperatureFlash(fluid);

// Configure search range
flash.setMinTemperature(273.15);  // 0°C
flash.setMaxTemperature(473.15);  // 200°C
flash.setTemperatureStep(5.0);    // Search step [K]
flash.setTolerance(0.1);          // Tolerance [K]

// Run calculation
flash.run();

if (flash.isOnsetFound()) {
    double onsetTemp = flash.getOnsetTemperature();
    System.out.println("Onset Temperature: " + (onsetTemp - 273.15) + " °C");
}

AsphalteneStabilityAnalyzer

High-level API combining multiple analysis methods:

import neqsim.pvtsimulation.flowassurance.AsphalteneStabilityAnalyzer;

// Create analyzer with fluid
AsphalteneStabilityAnalyzer analyzer = new AsphalteneStabilityAnalyzer(fluid);

// Configure conditions
analyzer.setReservoirPressure(350.0);      // bar
analyzer.setReservoirTemperature(373.15);  // K
analyzer.setBubblePointPressure(150.0);    // bar

// Quick screening (De Boer)
String screening = analyzer.deBoerScreening();
System.out.println("De Boer: " + screening);

// Thermodynamic onset pressure
double onsetP = analyzer.calculateOnsetPressure();
System.out.println("CPA Onset Pressure: " + onsetP + " bar");

// Comprehensive assessment
String fullReport = analyzer.comprehensiveAssessment();
System.out.println(fullReport);

Fluid Setup for CPA

// Use SystemSrkCPAstatoil for asphaltene calculations
SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(T_kelvin, P_bar);

Adding Components

// Light ends
fluid.addComponent("methane", 0.40);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.05);

// Intermediates
fluid.addComponent("n-heptane", 0.30);
fluid.addComponent("n-decane", 0.10);

// Asphaltene pseudo-component
fluid.addComponent("asphaltene", 0.05);

// Set mixing rule (required for CPA)
fluid.setMixingRule("classic");

Custom Asphaltene Properties

For tuned asphaltene parameters:

// After adding components, modify asphaltene properties
int aspIndex = fluid.getComponentIndex("asphaltene");

// Adjust molecular weight if needed
fluid.getComponent(aspIndex).setMolarMass(750.0 / 1000.0);  // kg/mol

// Adjust critical properties (affects solubility)
fluid.getComponent(aspIndex).setTC(900.0);  // Critical temperature [K]
fluid.getComponent(aspIndex).setPC(15.0);   // Critical pressure [bar]

Generating Precipitation Envelope

Map the full precipitation boundary in P-T space:

import neqsim.pvtsimulation.flowassurance.AsphalteneStabilityAnalyzer;
import java.util.Map;

AsphalteneStabilityAnalyzer analyzer = new AsphalteneStabilityAnalyzer(fluid);

// Generate envelope from Tmin to Tmax
double[][] envelope = analyzer.generatePrecipitationEnvelope(
    280.0,   // Min temperature [K]
    400.0,   // Max temperature [K]
    10.0     // Temperature step [K]
);

// envelope[0] = temperatures
// envelope[1] = onset pressures

for (int i = 0; i < envelope[0].length; i++) {
    double T_C = envelope[0][i] - 273.15;
    double P_bar = envelope[1][i];
    System.out.printf("T = %.1f°C, P_onset = %.1f bar%n", T_C, P_bar);
}

Algorithm Details

Onset Pressure Algorithm

  1. Coarse Search: Decrease pressure in steps, checking for solid phase
  2. Bisection Refinement: When onset bracket found, bisect to tolerance
  3. Solid Detection: Check if solid phase fraction exceeds threshold
P_start (e.g., 400 bar)
    |
    v
[Coarse search: decrease by step size]
    |
    v
Solid phase appears? --> No --> Continue decreasing
    |
    Yes
    v
[Bisection between last two points]
    |
    v
Converged to tolerance --> Report onset pressure

Solid Phase Check

NeqSim uses PhaseType.ASPHALTENE to distinguish asphaltene precipitation from other solid phases (wax, hydrate). This enables accurate phase identification in multi-phase flash calculations.

import neqsim.thermo.phase.PhaseType;

// After flash calculation - check for asphaltene precipitation
// Method 1: Using PhaseType enum (recommended)
if (fluid.hasPhaseType(PhaseType.ASPHALTENE)) {
    PhaseInterface asphaltene = fluid.getPhaseOfType("asphaltene");
    double precipitatedFraction = asphaltene.getBeta();
    System.out.println("Asphaltene precipitated: " + (precipitatedFraction * 100) + "%");
}

// Method 2: Using string-based lookup
if (fluid.hasPhaseType("asphaltene")) {
    PhaseInterface asphaltene = fluid.getPhaseOfType("asphaltene");
    // Access asphaltene phase properties
    double density = asphaltene.getDensity("kg/m3");       // ~1150 kg/m³
    double viscosity = asphaltene.getViscosity("Pa*s");    // ~10,000 Pa·s
    double thermalCond = asphaltene.getThermalConductivity("W/mK"); // ~0.20 W/mK
}

// Method 3: Legacy approach (also checks for generic solid)
private boolean hasAsphaltenePhase(SystemInterface fluid) {
    // Check for specific asphaltene phase type
    if (fluid.hasPhaseType("asphaltene")) {
        return true;
    }
    // Fallback: check solid phase for asphaltene component
    if (fluid.hasPhaseType("solid")) {
        PhaseInterface solid = fluid.getPhaseOfType("solid");
        for (int i = 0; i < solid.getNumberOfComponents(); i++) {
            if (solid.getComponent(i).getComponentName().toLowerCase().contains("asphaltene")) {
                return true;
            }
        }
    }
    return false;
}

Asphaltene Phase Properties

When asphaltene precipitates, it forms a distinct phase with PhaseType.ASPHALTENE. The physical properties are calculated using literature-based correlations:

Property Value Unit Notes
Density ~1150 kg/m³ Literature-based, temperature-independent
Heat Capacity (Cp) ~0.9 kJ/kgK EOS-based calculation
Thermal Conductivity 0.20 W/mK Typical for organic solids
Viscosity ~10,000 Pa·s Arrhenius correlation at 350K
Speed of Sound ~1745 m/s EOS-based calculation

## Tuning to Experimental Data

### Automated Parameter Fitting with AsphalteneOnsetFitting

NeqSim provides the `AsphalteneOnsetFitting` class to automatically tune CPA asphaltene parameters to match experimental onset pressure measurements using the Levenberg-Marquardt optimization algorithm.

```java
import neqsim.pvtsimulation.util.parameterfitting.AsphalteneOnsetFitting;
import neqsim.thermo.system.SystemSrkCPAstatoil;

// Step 1: Create fluid system with asphaltene
SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(373.15, 200.0);
fluid.addComponent("methane", 0.30);
fluid.addTBPfraction("C7", 0.30, 0.100, 0.75);
fluid.addComponent("asphaltene", 0.05);
fluid.setMixingRule("classic");

// Step 2: Create fitter and add experimental onset data
AsphalteneOnsetFitting fitter = new AsphalteneOnsetFitting(fluid);
fitter.addOnsetPoint(353.15, 350.0);  // T=80°C, P_onset=350 bar
fitter.addOnsetPoint(373.15, 320.0);  // T=100°C, P_onset=320 bar
fitter.addOnsetPoint(393.15, 280.0);  // T=120°C, P_onset=280 bar

// Step 3: Set initial parameter guesses
fitter.setInitialGuess(3500.0, 0.005);  // epsilon/R=3500K, kappa=0.005

// Step 4: Configure pressure search range (optional)
fitter.setPressureRange(500.0, 10.0, 10.0);

// Step 5: Run fitting
boolean success = fitter.solve();

// Step 6: Get fitted parameters
if (success) {
    double epsilonR = fitter.getFittedAssociationEnergy();
    double kappa = fitter.getFittedAssociationVolume();
    System.out.println("Fitted ε/R: " + epsilonR + " K");
    System.out.println("Fitted κ: " + kappa);

    // Step 7: Predict onset at new conditions
    double onsetP = fitter.calculateOnsetPressure(400.0);  // T=400K
    System.out.println("Predicted onset at 400K: " + onsetP + " bar");
}

Typical CPA Parameters for Asphaltenes

Based on literature (Li & Firoozabadi, Vargas et al.):

Parameter Typical Range Units Notes
Molar Mass 500 - 1500 g/mol Polydisperse distribution
Association Energy (ε/R) 2500 - 4500 K Controls aggregation strength
Association Volume (κ) 0.001 - 0.05 - Probability of association
Association Scheme 1A (single site) - Mimics π-π stacking
Critical Temperature (Tc) 700 - 900 K Affects phase behavior
Critical Pressure (Pc) 5 - 15 bar Affects phase behavior
Acentric Factor (ω) 1.0 - 2.0 - Shape factor

Starting Guess Recommendations

Oil Type API Gravity ε/R [K] κ
Light oils >35° 3000 0.01
Medium oils 25-35° 3500 0.005
Heavy oils <25° 4000 0.003

Fitting Parameter Types

The fitter supports different parameter types:

import neqsim.pvtsimulation.util.parameterfitting.AsphalteneOnsetFunction.FittingParameterType;

// Fit only association energy
fitter.setParameterType(FittingParameterType.ASSOCIATION_ENERGY);
fitter.setInitialGuess(3500.0);

// Fit only association volume
fitter.setParameterType(FittingParameterType.ASSOCIATION_VOLUME);
fitter.setInitialGuess(0.005);

// Fit both association parameters (default)
fitter.setParameterType(FittingParameterType.ASSOCIATION_PARAMETERS);
fitter.setInitialGuess(3500.0, 0.005);

// Fit binary interaction parameter
fitter.setParameterType(FittingParameterType.BINARY_INTERACTION);
fitter.setInitialGuess(0.0);

// Fit molar mass
fitter.setParameterType(FittingParameterType.MOLAR_MASS);
fitter.setInitialGuess(750.0);

Manual Onset Pressure Matching

For manual tuning without the fitter:

// Target: Match experimental AOP of 180 bar

// Strategy 1: Adjust asphaltene critical properties
// Higher Tc/Pc = lower solubility = higher onset pressure

// Strategy 2: Adjust association parameters
// Stronger association = earlier precipitation

// Strategy 3: Adjust binary interaction parameters
fluid.setMixingRule("classic");  // k_ij values

Sensitivity Analysis

Key parameters affecting onset pressure:

  1. Asphaltene molecular weight: Higher MW → earlier precipitation
  2. Critical temperature: Higher Tc → lower solubility
  3. Association energy: Stronger → earlier aggregation
  4. Binary k_ij with light ends: Higher → less miscibility

Performance Considerations

Calculation Speed

Operation Typical Time Notes
Single flash 10-100 ms Depends on composition complexity
Onset pressure 1-5 seconds Multiple flashes + bisection
Full envelope 10-60 seconds Multiple onset calculations

Optimization Tips

// Reduce search range if approximate onset known
flash.setMaxPressure(250.0);  // Instead of 400
flash.setMinPressure(100.0);  // Instead of 1

// Use larger tolerance for initial screening
flash.setTolerance(1.0);  // Instead of 0.1

// Larger steps for coarse search
flash.setPressureStep(20.0);  // Instead of 10

Common Issues and Solutions

No Onset Found

Convergence Issues

Unrealistic Onset Pressure

Example: Complete Workflow

import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.thermodynamicoperations.ThermodynamicOperations;
import neqsim.pvtsimulation.flowassurance.AsphalteneStabilityAnalyzer;

// 1. Create and characterize fluid
SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(373.15, 350.0);
fluid.addComponent("methane", 0.35);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.05);
fluid.addComponent("n-heptane", 0.40);
fluid.addComponent("n-decane", 0.07);
fluid.addComponent("asphaltene", 0.05);
fluid.setMixingRule("classic");
fluid.init(0);
fluid.init(1);

// 2. Create analyzer
AsphalteneStabilityAnalyzer analyzer = new AsphalteneStabilityAnalyzer(fluid);
analyzer.setReservoirPressure(350.0);
analyzer.setReservoirTemperature(373.15);
analyzer.setBubblePointPressure(150.0);

// 3. Quick screening
System.out.println("=== De Boer Screening ===");
System.out.println(analyzer.deBoerScreening());

// 4. Calculate onset pressure
System.out.println("\n=== CPA Onset Pressure ===");
double onsetP = analyzer.calculateOnsetPressure();
System.out.println("Onset Pressure: " + onsetP + " bar");

// 5. Generate envelope
System.out.println("\n=== Precipitation Envelope ===");
double[][] envelope = analyzer.generatePrecipitationEnvelope(300.0, 400.0, 20.0);
for (int i = 0; i < envelope[0].length; i++) {
    System.out.printf("T = %.1f K, P_onset = %.1f bar%n", 
                      envelope[0][i], envelope[1][i]);
}

// 6. Full report
System.out.println("\n=== Comprehensive Assessment ===");
System.out.println(analyzer.comprehensiveAssessment());

See Also

De Boer Screening

De Boer Asphaltene Screening

Overview

The De Boer correlation is an empirical screening method developed from field observations of asphaltene problems in producing oil fields. It provides a quick, conservative assessment of asphaltene precipitation risk without requiring detailed thermodynamic modeling.

Theory

De Boer Plot

De Boer et al. (1995) analyzed field data and found that asphaltene problems correlate with two parameters:

  1. Undersaturation Pressure ($\Delta P$): The difference between reservoir pressure and bubble point
  2. In-situ Oil Density ($\rho$): Density of the live oil at reservoir conditions

$$ \Delta P = P_{\text{reservoir}} - P_{\text{bubble}} $$

The plot divides the $\Delta P$ vs $\rho$ space into risk zones:

Undersaturation ΔP [bar]
    ^
400 |     SEVERE      |
    |   PROBLEM       |
300 |                 | MODERATE
    |  ─────────────  | PROBLEM
200 |                 |
    |    SLIGHT       |
100 |    PROBLEM      |
    |  ───────────────┼───────────────
  0 |    NO PROBLEM   |
    +─────────────────┼───────────────> ρ [kg/m³]
                    700            800

Risk Zone Boundaries

The correlation uses empirical boundaries:

Zone Description Boundary
No Problem Low risk $\Delta P < f_1(\rho)$
Slight Problem Minor risk $f_1(\rho) < \Delta P < f_2(\rho)$
Moderate Problem Moderate risk $f_2(\rho) < \Delta P < f_3(\rho)$
Severe Problem High risk $\Delta P > f_3(\rho)$

Where $f_i(\rho)$ are linear functions of density.

Physical Interpretation

Implementation

Basic Usage

import neqsim.pvtsimulation.flowassurance.DeBoerAsphalteneScreening;

// Create screening with reservoir data
DeBoerAsphalteneScreening screening = new DeBoerAsphalteneScreening(
    350.0,  // Reservoir pressure [bar]
    150.0,  // Bubble point pressure [bar]
    720.0   // In-situ oil density [kg/m³]
);

// Get risk assessment
String riskLevel = screening.evaluateRisk();
System.out.println("Risk Level: " + riskLevel);

Risk Levels

The method returns one of four risk categories:

public enum RiskLevel {
    NO_PROBLEM,        // No action needed
    SLIGHT_PROBLEM,    // Monitor during production
    MODERATE_PROBLEM,  // Consider mitigation
    SEVERE_PROBLEM     // Mitigation required
}

// Usage
String risk = screening.evaluateRisk();
switch (risk) {
    case "NO_PROBLEM":
        System.out.println("No asphaltene issues expected");
        break;
    case "SLIGHT_PROBLEM":
        System.out.println("Minor issues possible - monitor");
        break;
    case "MODERATE_PROBLEM":
        System.out.println("Significant risk - plan mitigation");
        break;
    case "SEVERE_PROBLEM":
        System.out.println("High risk - mitigation required");
        break;
}

Quantitative Risk Index

For more nuanced assessment:

double riskIndex = screening.calculateRiskIndex();

The risk index interpretation:

Index Value Interpretation
< 0 Stable, no precipitation expected
0 - 0.3 Low risk
0.3 - 0.7 Moderate risk
> 0.7 High risk, likely problems

Detailed Report

String report = screening.performScreening();
System.out.println(report);

Output:

De Boer Asphaltene Screening Results
====================================
Reservoir Pressure: 350.0 bar
Bubble Point Pressure: 150.0 bar
Undersaturation: 200.0 bar
In-situ Density: 720.0 kg/m³

Risk Assessment: MODERATE_PROBLEM
Risk Index: 0.55

Recommendation: Moderate asphaltene risk. 
Consider preventive measures and monitoring plan.

Advanced Usage

Sensitivity Analysis

Evaluate risk over a range of conditions:

import neqsim.pvtsimulation.flowassurance.DeBoerAsphalteneScreening;

// Base case
double bubblePoint = 150.0;
double density = 720.0;

// Pressure depletion scenario
System.out.println("Pressure Depletion Analysis:");
for (double pRes = 400.0; pRes >= 160.0; pRes -= 20.0) {
    DeBoerAsphalteneScreening screening = 
        new DeBoerAsphalteneScreening(pRes, bubblePoint, density);

    double deltaP = pRes - bubblePoint;
    String risk = screening.evaluateRisk();

    System.out.printf("P_res = %.0f bar, ΔP = %.0f bar: %s%n", 
                      pRes, deltaP, risk);
}

Generate Plot Data

Create data for visualization:

double[][] plotData = screening.generatePlotData(
    650.0,   // Min density [kg/m³]
    850.0,   // Max density [kg/m³]
    25.0     // Density step [kg/m³]
);

// plotData[0] = density values
// plotData[1] = undersaturation values for "slight problem" boundary
// plotData[2] = undersaturation values for "moderate problem" boundary
// plotData[3] = undersaturation values for "severe problem" boundary

// Export for plotting
System.out.println("Density,Slight,Moderate,Severe");
for (int i = 0; i < plotData[0].length; i++) {
    System.out.printf("%.1f,%.1f,%.1f,%.1f%n",
        plotData[0][i], plotData[1][i], plotData[2][i], plotData[3][i]);
}

Batch Screening

Screen multiple samples:

// Sample data: [reservoir P, bubble P, density]
double[][] samples = {
    {350.0, 150.0, 720.0},  // Sample A
    {280.0, 100.0, 780.0},  // Sample B
    {400.0, 200.0, 690.0},  // Sample C
    {300.0, 180.0, 750.0}   // Sample D
};

String[] sampleNames = {"A", "B", "C", "D"};

System.out.println("Sample | ΔP [bar] | ρ [kg/m³] | Risk Level");
System.out.println("-------+----------+-----------+----------------");

for (int i = 0; i < samples.length; i++) {
    DeBoerAsphalteneScreening screening = new DeBoerAsphalteneScreening(
        samples[i][0], samples[i][1], samples[i][2]
    );

    double deltaP = samples[i][0] - samples[i][1];
    String risk = screening.evaluateRisk();

    System.out.printf("   %s   |  %5.0f   |   %5.0f   | %s%n",
        sampleNames[i], deltaP, samples[i][2], risk);
}

Input Requirements

Required Data

Parameter Unit How to Obtain
Reservoir Pressure bar Formation test (RFT/MDT)
Bubble Point Pressure bar PVT lab test or correlation
In-situ Density kg/m³ PVT lab or flash calculation

Estimating In-situ Density

If density at reservoir conditions is not available:

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create fluid from composition
SystemSrkEos fluid = new SystemSrkEos(373.15, 350.0);  // Reservoir T, P
fluid.addComponent("methane", 0.40);
fluid.addComponent("n-heptane", 0.55);
fluid.addComponent("n-decane", 0.05);
fluid.setMixingRule("classic");

// Flash to get density
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Get liquid density
double density = fluid.getPhase("oil").getDensity("kg/m3");
System.out.println("In-situ density: " + density + " kg/m³");

Limitations

What De Boer Cannot Do

  1. No onset pressure prediction: Cannot tell when precipitation starts
  2. No quantity estimation: Cannot predict how much precipitates
  3. No temperature effects: Only considers isothermal depletion
  4. No composition sensitivity: Cannot evaluate blending/injection effects
  5. No remediation modeling: Cannot assess inhibitor effectiveness

When to Use CPA Instead

Use thermodynamic modeling (CPA) when:

Validation

Field Data Comparison

De Boer was validated against:

The correlation correctly predicted:

Conservative Bias

The method is intentionally conservative:

Example: Field Development Screening

import neqsim.pvtsimulation.flowassurance.DeBoerAsphalteneScreening;

// Field data for multiple reservoirs
String[] reservoirs = {"Alpha", "Beta", "Gamma", "Delta"};
double[] pRes = {380.0, 320.0, 290.0, 410.0};  // Reservoir pressure [bar]
double[] pBub = {180.0, 150.0, 120.0, 220.0};  // Bubble point [bar]
double[] rho = {710.0, 750.0, 800.0, 680.0};   // In-situ density [kg/m³]

System.out.println("Field Development Asphaltene Screening");
System.out.println("======================================\n");

int highRiskCount = 0;

for (int i = 0; i < reservoirs.length; i++) {
    DeBoerAsphalteneScreening screening = 
        new DeBoerAsphalteneScreening(pRes[i], pBub[i], rho[i]);

    String risk = screening.evaluateRisk();
    double riskIndex = screening.calculateRiskIndex();

    System.out.printf("Reservoir %s:%n", reservoirs[i]);
    System.out.printf("  P_res = %.0f bar, P_bub = %.0f bar, ρ = %.0f kg/m³%n",
                      pRes[i], pBub[i], rho[i]);
    System.out.printf("  Risk: %s (index: %.2f)%n%n", risk, riskIndex);

    if (risk.equals("SEVERE_PROBLEM") || risk.equals("MODERATE_PROBLEM")) {
        highRiskCount++;
    }
}

System.out.printf("Summary: %d of %d reservoirs require further analysis%n",
                  highRiskCount, reservoirs.length);

References

  1. De Boer, R.B., Leerlooyer, K., Eigner, M.R.P., and van Bergen, A.R.D. (1995). "Screening of Crude Oils for Asphalt Precipitation: Theory, Practice, and the Selection of Inhibitors." SPE Production & Facilities, 10(1), 55-61.

  2. Hammami, A., and Ratulowski, J. (2007). "Precipitation and Deposition of Asphaltenes in Production Systems: A Flow Assurance Overview." In Asphaltenes, Heavy Oils, and Petroleomics, Springer.

  3. Akbarzadeh, K., et al. (2007). "Asphaltenes—Problematic but Rich in Potential." Oilfield Review, 19(2), 22-43.

See Also

Method Comparison

Asphaltene Method Comparison

Overview

NeqSim provides two complementary approaches for asphaltene stability analysis:

  1. De Boer Screening: Fast, empirical correlation for initial assessment
  2. CPA Thermodynamic Modeling: Rigorous EOS-based onset calculations

This document explains when to use each method and how to compare their results.

Method Summary

Aspect De Boer CPA
Speed Milliseconds Seconds to minutes
Input Required P_res, P_bub, ρ Full composition + properties
Output Risk category Onset P, T, amounts
Accuracy Conservative screening Predictive (if tuned)
Composition Effects No Yes
Temperature Effects No Yes
Injection Effects No Yes

Decision Framework

When to Use De Boer Only

✅ Early field screening with limited data
✅ Quick portfolio risk ranking
✅ Conservative go/no-go decisions
✅ Baseline risk communication

When to Use CPA Only

✅ Detailed well/facility design
✅ Operating envelope definition
✅ Gas injection impact assessment
✅ Inhibitor effectiveness evaluation

When to Use Both

✅ Field development planning (screen then analyze)
✅ Validating thermodynamic model predictions
✅ Communicating risk to non-technical stakeholders
✅ Comprehensive flow assurance studies

Using AsphalteneMethodComparison

Basic Comparison

import neqsim.pvtsimulation.flowassurance.AsphalteneMethodComparison;
import neqsim.thermo.system.SystemSrkCPAstatoil;

// Create CPA fluid
SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(373.15, 350.0);
fluid.addComponent("methane", 0.40);
fluid.addComponent("n-heptane", 0.50);
fluid.addComponent("asphaltene", 0.10);
fluid.setMixingRule("classic");
fluid.init(0);
fluid.init(1);

// Create comparison
AsphalteneMethodComparison comparison = new AsphalteneMethodComparison(fluid);
comparison.setReservoirPressure(350.0);
comparison.setBubblePointPressure(150.0);
comparison.setInSituDensity(720.0);

// Run comparison
String report = comparison.runComparison();
System.out.println(report);

Sample Output

=================================================
ASPHALTENE STABILITY: METHOD COMPARISON REPORT
=================================================

FLUID INFORMATION
-----------------
Temperature: 100.0 °C
Pressure: 350.0 bar
Number of Components: 3

DE BOER SCREENING RESULTS
-------------------------
Undersaturation: 200.0 bar
In-situ Density: 720.0 kg/m³
Risk Level: MODERATE_PROBLEM
Risk Index: 0.55

CPA THERMODYNAMIC RESULTS
-------------------------
Asphaltene Onset Pressure: 185.0 bar
Above Bubble Point: Yes
Precipitation Expected: Yes
Onset Margin: 35.0 bar above bubble point

COMPARISON SUMMARY
------------------
De Boer Risk: MODERATE
CPA Prediction: Precipitation at 185 bar

Agreement: CONSISTENT
Both methods indicate asphaltene precipitation 
risk during normal production.

RECOMMENDATIONS
---------------
1. Consider asphaltene inhibitor injection
2. Design for periodic wellbore cleanout
3. Monitor production for pressure decline
4. Validate with laboratory AOP test

Quick Summary

For rapid assessment:

String summary = comparison.getQuickSummary();
System.out.println(summary);

Output:

De Boer: MODERATE_PROBLEM | CPA Onset: 185 bar | Agreement: CONSISTENT

Interpretation Guidelines

Consistent Results

De Boer CPA Interpretation
NO_PROBLEM No onset found Low risk, minimal mitigation
SLIGHT_PROBLEM Onset near P_bub Monitor during late life
MODERATE_PROBLEM Onset > P_bub Active mitigation needed
SEVERE_PROBLEM Onset >> P_bub Significant risk, design for it

Inconsistent Results

De Boer CPA Possible Causes
HIGH No onset De Boer conservative; unusual composition
LOW Onset found Light oil with high asphaltene; check composition

When results disagree:

  1. Review input data quality
  2. Check CPA model tuning
  3. Consider laboratory validation
  4. Use more conservative result for design

Example Workflows

Workflow 1: Field Development Screening

// Step 1: De Boer screening of all fluids
List<String> needsDetailedAnalysis = new ArrayList<>();

for (FluidSample sample : allSamples) {
    DeBoerAsphalteneScreening screening = new DeBoerAsphalteneScreening(
        sample.getReservoirPressure(),
        sample.getBubblePoint(),
        sample.getInSituDensity()
    );

    String risk = screening.evaluateRisk();

    if (!risk.equals("NO_PROBLEM")) {
        needsDetailedAnalysis.add(sample.getName());
    }
}

// Step 2: CPA analysis only for flagged samples
for (String sampleName : needsDetailedAnalysis) {
    // Create detailed CPA model
    SystemSrkCPAstatoil fluid = createCPAFluid(sampleName);

    AsphalteneStabilityAnalyzer analyzer = 
        new AsphalteneStabilityAnalyzer(fluid);

    double onsetP = analyzer.calculateOnsetPressure();

    System.out.printf("Sample %s: Onset at %.1f bar%n", 
                      sampleName, onsetP);
}

Workflow 2: Operating Envelope Definition

// Use CPA to define safe operating window
AsphalteneStabilityAnalyzer analyzer = new AsphalteneStabilityAnalyzer(fluid);

// Generate precipitation boundary
double[][] envelope = analyzer.generatePrecipitationEnvelope(
    280.0, 420.0, 10.0  // Temperature range [K]
);

// Define operating envelope with safety margin
double safetyMargin = 20.0;  // bar below onset

System.out.println("Safe Operating Envelope:");
System.out.println("T [°C], Max P [bar]");
for (int i = 0; i < envelope[0].length; i++) {
    double T_C = envelope[0][i] - 273.15;
    double maxSafeP = envelope[1][i] - safetyMargin;
    System.out.printf("%.0f, %.1f%n", T_C, maxSafeP);
}

Workflow 3: Blending Compatibility

// Base fluid
SystemSrkCPAstatoil baseFluid = createFluid("FieldA");

// Blend fluid  
SystemSrkCPAstatoil blendFluid = createFluid("FieldB");

// Test blend ratios
double[] blendRatios = {0.0, 0.25, 0.50, 0.75, 1.0};

System.out.println("Blend Compatibility Study");
System.out.println("-------------------------");

for (double ratio : blendRatios) {
    SystemSrkCPAstatoil mixed = blendFluids(baseFluid, blendFluid, ratio);

    AsphalteneStabilityAnalyzer analyzer = 
        new AsphalteneStabilityAnalyzer(mixed);

    double onsetP = analyzer.calculateOnsetPressure();
    String deBoer = analyzer.deBoerScreening();

    System.out.printf("%.0f%% Field B: Onset = %.1f bar, De Boer = %s%n",
                      ratio * 100, onsetP, deBoer);
}

Model Validation

Laboratory Data Requirements

For CPA model tuning:

Test Purpose Priority
AOP Test Onset pressure at reservoir T Required
HPM Analysis Onset detection and quantification Recommended
SARA Analysis Composition for characterization Required
Titration Onset with n-heptane at ambient Optional
Density For De Boer screening Required

Automated Parameter Tuning

NeqSim provides the AsphalteneOnsetFitting class for automated CPA parameter tuning:

import neqsim.pvtsimulation.util.parameterfitting.AsphalteneOnsetFitting;

// Create fitter with your fluid system
AsphalteneOnsetFitting fitter = new AsphalteneOnsetFitting(fluid);

// Add experimental AOP data from lab tests
fitter.addOnsetPointCelsius(80.0, 350.0);   // 80°C, 350 bar
fitter.addOnsetPointCelsius(100.0, 320.0);  // 100°C, 320 bar
fitter.addOnsetPointCelsius(120.0, 280.0);  // 120°C, 280 bar

// Set initial parameter guesses based on oil type
// Light oils: epsilon/R=3000, kappa=0.01
// Heavy oils: epsilon/R=4000, kappa=0.003
fitter.setInitialGuess(3500.0, 0.005);

// Run Levenberg-Marquardt optimization
boolean success = fitter.solve();

if (success) {
    // Get fitted parameters
    double epsilonR = fitter.getFittedAssociationEnergy();
    double kappa = fitter.getFittedAssociationVolume();

    System.out.printf("Fitted ε/R: %.1f K%n", epsilonR);
    System.out.printf("Fitted κ: %.4f%n", kappa);

    // Predict onset at new temperature
    double predictedAOP = fitter.calculateOnsetPressure(393.15);  // 120°C
    System.out.printf("Predicted AOP at 120°C: %.1f bar%n", predictedAOP);
}

Manual Tuning Process

For manual iteration without the automated fitter:

// Experimental AOP
double measuredAOP = 195.0;  // bar at 100°C

// Initial CPA prediction
AsphalteneStabilityAnalyzer analyzer = 
    new AsphalteneStabilityAnalyzer(fluid);
double predictedAOP = analyzer.calculateOnsetPressure();

// Calculate error
double error = predictedAOP - measuredAOP;
System.out.printf("Initial error: %.1f bar%n", error);

// Adjust asphaltene parameters to match
// Options: molecular weight, critical properties, kij values
// Iterate until error < tolerance

Performance Comparison

Calculation Times (Typical)

Operation Time Notes
De Boer screening < 1 ms Single correlation evaluation
De Boer batch (1000 samples) < 100 ms Highly parallelizable
CPA single flash 10-100 ms Depends on composition
CPA onset pressure 1-5 s Multiple flashes + bisection
CPA full envelope 10-60 s Many onset calculations
Parameter fitting (3 points) 10-60 s Depends on convergence
Full comparison report 2-10 s Both methods + formatting

When Speed Matters

// For real-time monitoring: Use De Boer only
if (isRealTimeMonitoring) {
    DeBoerAsphalteneScreening screening = 
        new DeBoerAsphalteneScreening(pRes, pBub, density);
    return screening.calculateRiskIndex();
}

// For batch screening: Use De Boer first
if (numberOfSamples > 100) {
    // Screen with De Boer
    List<Sample> flagged = deBoerScreen(samples);

    // CPA only on flagged samples
    for (Sample s : flagged) {
        runCPAAnalysis(s);
    }
}

Best Practices

Do's

Start with De Boer for initial assessment
Validate CPA with experimental data before design
Use both for comprehensive studies
Document assumptions in both methods
Apply safety margins to predicted onset

Don'ts

Don't use CPA without tuning for critical decisions
Don't ignore De Boer warnings even if CPA looks safe
Don't over-interpret small onset pressure differences
Don't extrapolate beyond validated conditions

Example: Complete Comparison

import neqsim.pvtsimulation.flowassurance.*;
import neqsim.thermo.system.SystemSrkCPAstatoil;

public class AsphalteneStudy {
    public static void main(String[] args) {
        // Create realistic oil composition
        SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(373.15, 350.0);

        // Light ends
        fluid.addComponent("nitrogen", 0.01);
        fluid.addComponent("CO2", 0.02);
        fluid.addComponent("methane", 0.35);
        fluid.addComponent("ethane", 0.08);
        fluid.addComponent("propane", 0.05);

        // Intermediates
        fluid.addComponent("n-butane", 0.03);
        fluid.addComponent("n-pentane", 0.03);
        fluid.addComponent("n-hexane", 0.05);
        fluid.addComponent("n-heptane", 0.15);
        fluid.addComponent("n-decane", 0.10);

        // Heavy ends
        fluid.addComponent("n-C15", 0.08);
        fluid.addComponent("asphaltene", 0.05);

        fluid.setMixingRule("classic");
        fluid.init(0);
        fluid.init(1);

        // Reservoir conditions
        double pRes = 350.0;    // bar
        double pBub = 150.0;    // bar
        double density = 720.0; // kg/m³

        // Run full comparison
        AsphalteneMethodComparison comparison = 
            new AsphalteneMethodComparison(fluid);
        comparison.setReservoirPressure(pRes);
        comparison.setBubblePointPressure(pBub);
        comparison.setInSituDensity(density);

        // Generate reports
        System.out.println(comparison.runComparison());

        System.out.println("\n" + StringUtils.repeat("=", 50));
        System.out.println("QUICK REFERENCE");
        System.out.println(StringUtils.repeat("=", 50));
        System.out.println(comparison.getQuickSummary());
    }
}

See Also

Parameter Fitting

Asphaltene Parameter Fitting

Overview

The AsphalteneOnsetFitting class provides automated CPA parameter tuning using the Levenberg-Marquardt optimization algorithm. This enables matching CPA model predictions to experimental asphaltene onset pressure (AOP) measurements.

Why Parameter Fitting?

CPA parameters for asphaltenes (association energy ε/R and volume κ) vary between crude oils and must be tuned to experimental data for accurate predictions:

Parameter Effect on Onset Pressure
Higher ε/R Earlier precipitation (higher onset P)
Higher κ Stronger aggregation tendency
Higher MW Reduced solubility

Default parameters provide reasonable estimates, but tuning to measured AOP data improves prediction accuracy significantly.

Quick Start

import neqsim.pvtsimulation.util.parameterfitting.AsphalteneOnsetFitting;
import neqsim.thermo.system.SystemSrkCPAstatoil;

// 1. Create fluid with asphaltene
SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(373.15, 200.0);
fluid.addComponent("methane", 0.30);
fluid.addTBPfraction("C7", 0.50, 0.150, 0.80);
fluid.addComponent("asphaltene", 0.05);
fluid.setMixingRule("classic");

// 2. Create fitter and add experimental data
AsphalteneOnsetFitting fitter = new AsphalteneOnsetFitting(fluid);
fitter.addOnsetPointCelsius(80.0, 350.0);   // T=80°C, P=350 bar
fitter.addOnsetPointCelsius(100.0, 320.0);  // T=100°C, P=320 bar
fitter.addOnsetPointCelsius(120.0, 280.0);  // T=120°C, P=280 bar

// 3. Set initial guesses and run fitting
fitter.setInitialGuess(3500.0, 0.005);  // ε/R [K], κ [-]
boolean success = fitter.solve();

// 4. Get results
if (success) {
    System.out.println("Fitted ε/R: " + fitter.getFittedAssociationEnergy() + " K");
    System.out.println("Fitted κ: " + fitter.getFittedAssociationVolume());
}

Detailed API

Constructor

// Create fitter from existing fluid system
AsphalteneOnsetFitting fitter = new AsphalteneOnsetFitting(fluid);

The fluid system must:

Adding Experimental Data

// Add point with temperature in Kelvin
fitter.addOnsetPoint(373.15, 320.0);  // T [K], P [bar]

// Add point with temperature in Celsius (convenience method)
fitter.addOnsetPointCelsius(100.0, 320.0);  // T [°C], P [bar]

// Add point with measurement uncertainty
fitter.addOnsetPoint(373.15, 320.0, 10.0);  // T [K], P [bar], stdDev [bar]

// Clear all data points
fitter.clearData();

Recommended: Use at least 3 data points at different temperatures for robust fitting.

Setting Initial Guesses

// Two parameters: association energy and volume
fitter.setInitialGuess(3500.0, 0.005);  // ε/R [K], κ [-]

// Single parameter (when fitting only one)
fitter.setInitialGuess(3500.0);
Oil Type API Gravity ε/R [K] κ
Light oil >35° 2500-3500 0.005-0.015
Medium oil 25-35° 3000-4000 0.003-0.008
Heavy oil <25° 3500-4500 0.002-0.005

Fitting Parameter Types

Control which parameters are fitted:

import neqsim.pvtsimulation.util.parameterfitting.AsphalteneOnsetFunction.FittingParameterType;

// Fit both association parameters (default)
fitter.setParameterType(FittingParameterType.ASSOCIATION_PARAMETERS);
fitter.setInitialGuess(3500.0, 0.005);

// Fit only association energy
fitter.setParameterType(FittingParameterType.ASSOCIATION_ENERGY);
fitter.setInitialGuess(3500.0);

// Fit only association volume
fitter.setParameterType(FittingParameterType.ASSOCIATION_VOLUME);
fitter.setInitialGuess(0.005);

// Fit binary interaction parameter
fitter.setParameterType(FittingParameterType.BINARY_INTERACTION);
fitter.setInitialGuess(0.0);

// Fit molar mass
fitter.setParameterType(FittingParameterType.MOLAR_MASS);
fitter.setInitialGuess(750.0);
// Set pressure range for onset calculation
fitter.setPressureRange(
    500.0,  // Start pressure [bar] - search starts here
    10.0,   // Min pressure [bar] - search stops here
    10.0    // Pressure step [bar] - coarse search step
);

Running the Fit

boolean success = fitter.solve();

if (success) {
    // Fitting converged
    double[] params = fitter.getFittedParameters();
} else {
    // Fitting failed - check initial guesses or data quality
}

Getting Results

// Get fitted parameters
double epsilonR = fitter.getFittedAssociationEnergy();
double kappa = fitter.getFittedAssociationVolume();

// Get all fitted parameters as array
double[] params = fitter.getFittedParameters();

// Check if fitting was successful
boolean success = fitter.isSolved();

Predicting Onset at New Conditions

// Calculate onset pressure at a new temperature
double onsetP = fitter.calculateOnsetPressure(393.15);  // T [K]

// Get the tuned fluid system
SystemInterface tunedFluid = fitter.getTunedSystem();

Algorithm Details

Levenberg-Marquardt Optimization

The fitting uses the Levenberg-Marquardt algorithm to minimize:

$$ \chi^2 = \sum_{i=1}^{n} \frac{(P_{\text{calc},i} - P_{\text{exp},i})^2}{\sigma_i^2} $$

Where:

Convergence Criteria

Onset Pressure Calculation

For each temperature, the onset pressure is found by:

  1. Starting at high pressure (single liquid phase)
  2. Decreasing pressure in steps
  3. Detecting when a second phase appears
  4. Bisecting to find exact onset point

Best Practices

Data Quality

✅ Use at least 3 onset points at different temperatures
✅ Include temperature range spanning expected field conditions
✅ Use reliable measurement techniques (HPM, light scattering)
✅ Specify measurement uncertainty for weighted fitting

Initial Guesses

✅ Start with literature values for similar oils
✅ Use the "heavy" guess for conservative prediction
✅ Try multiple initial guesses if fitting fails

Validation

✅ Check that fitted parameters are physically reasonable
✅ Validate predictions at conditions not used in fitting
✅ Compare with De Boer screening for consistency

Troubleshooting

Fitting Fails to Converge

Possible causes:

  1. Poor initial guesses
  2. Inconsistent experimental data
  3. Fluid system not properly initialized

Solutions:

Fitted Parameters Outside Physical Range

Expected ranges:

If outside range:

Poor Prediction Accuracy

Possible causes:

  1. Limited temperature range in training data
  2. Extrapolating beyond calibration range
  3. Composition effects not captured

Solutions:

Example: Complete Fitting Workflow

import neqsim.pvtsimulation.util.parameterfitting.AsphalteneOnsetFitting;
import neqsim.thermo.system.SystemSrkCPAstatoil;

public class AsphalteneParameterTuning {
    public static void main(String[] args) {
        // Create realistic oil composition
        SystemSrkCPAstatoil fluid = new SystemSrkCPAstatoil(373.15, 350.0);
        fluid.addComponent("methane", 0.35);
        fluid.addComponent("ethane", 0.08);
        fluid.addComponent("propane", 0.05);
        fluid.addTBPfraction("C7", 0.35, 0.100, 0.75);
        fluid.addTBPfraction("C15", 0.12, 0.200, 0.85);
        fluid.addComponent("asphaltene", 0.05);
        fluid.setMixingRule("classic");
        fluid.init(0);

        // Create fitter
        AsphalteneOnsetFitting fitter = new AsphalteneOnsetFitting(fluid);

        // Add experimental AOP data from lab tests
        // (Temperature in Celsius, Pressure in bar)
        fitter.addOnsetPointCelsius(60.0, 380.0);   // Cool conditions
        fitter.addOnsetPointCelsius(80.0, 350.0);   // Intermediate
        fitter.addOnsetPointCelsius(100.0, 320.0);  // Reservoir T
        fitter.addOnsetPointCelsius(120.0, 280.0);  // Hot conditions

        // Set initial guesses (medium oil)
        fitter.setInitialGuess(3500.0, 0.005);

        // Configure pressure search range
        fitter.setPressureRange(500.0, 10.0, 10.0);

        // Run fitting
        System.out.println("Starting parameter fitting...");
        boolean success = fitter.solve();

        if (success) {
            System.out.println("\n=== FITTING RESULTS ===");
            System.out.printf("Fitted ε/R: %.1f K%n", fitter.getFittedAssociationEnergy());
            System.out.printf("Fitted κ: %.5f%n", fitter.getFittedAssociationVolume());

            // Validate: predict onset at test temperatures
            System.out.println("\n=== VALIDATION ===");
            double[] testTemps = {333.15, 353.15, 373.15, 393.15};  // 60-120°C
            double[] measuredP = {380.0, 350.0, 320.0, 280.0};

            for (int i = 0; i < testTemps.length; i++) {
                double predP = fitter.calculateOnsetPressure(testTemps[i]);
                double error = predP - measuredP[i];
                System.out.printf("T = %.0f°C: Measured = %.0f bar, Predicted = %.1f bar (Δ = %.1f)%n",
                    testTemps[i] - 273.15, measuredP[i], predP, error);
            }

            // Predict at new conditions
            System.out.println("\n=== PREDICTIONS ===");
            double newOnset = fitter.calculateOnsetPressure(413.15);  // 140°C
            System.out.printf("Predicted AOP at 140°C: %.1f bar%n", newOnset);
        } else {
            System.out.println("Fitting failed - try different initial guesses");
        }
    }
}

See Also

References

  1. Li, Z., and Firoozabadi, A. (2010). "Modeling Asphaltene Precipitation by n-Alkanes from Heavy Oils and Bitumens Using Cubic-Plus-Association Equation of State." Energy & Fuels, 24, 1106-1113.

  2. Vargas, F.M., Gonzalez, D.L., Hirasaki, G.J., and Chapman, W.G. (2009). "Modeling Asphaltene Phase Behavior in Crude Oil Systems Using the Perturbed Chain Form of the Statistical Associating Fluid Theory (PC-SAFT) Equation of State." Energy & Fuels, 23, 1140-1146.

  3. Gonzalez, D.L., Ting, P.D., Hirasaki, G.J., and Chapman, W.G. (2005). "Prediction of Asphaltene Instability under Gas Injection with the PC-SAFT Equation of State." Energy & Fuels, 19, 1230-1234.

Validation

Asphaltene Model Validation

Overview

This document summarizes the validation of NeqSim's asphaltene models against published literature and field data. The validation demonstrates that the implemented models correctly capture the physics of asphaltene precipitation and provide reliable predictions for field screening applications.

Validation Sources

Primary References

  1. De Boer, R.B., et al. (1995)
    "Screening of Crude Oils for Asphalt Precipitation: Theory, Practice, and the Selection of Inhibitors."
    SPE Production & Facilities, 10(1), 55-61. SPE-24987-PA

  2. Akbarzadeh, K., et al. (2007)
    "Asphaltenes—Problematic but Rich in Potential."
    Oilfield Review, 19(2), 22-43.

  3. Hammami, A., et al. (2000)
    "Asphaltene Precipitation from Live Oils: An Experimental Investigation of Onset Conditions and Reversibility."
    Energy & Fuels, 14(1), 14-18.

De Boer Screening Validation

Field Data from SPE-24987-PA

The De Boer correlation was validated against 10 field cases from the original SPE paper:

Fields WITH Asphaltene Problems

Field Country P_res [bar] P_bub [bar] ρ [kg/m³] ΔP [bar]
Hassi Messaoud Algeria 414 172 694 242
Mata-Acema Venezuela 276 138 725 138
Boscan (Light) Venezuela 310 103 720 207
Prinos Greece 483 207 680 276
Ula North Sea 345 145 710 200

Fields WITHOUT Asphaltene Problems

Field Country P_res [bar] P_bub [bar] ρ [kg/m³] ΔP [bar]
Cyrus North Sea 207 138 780 69
Ula (Aquifer) North Sea 241 172 810 69
Brent North Sea 138 103 850 35
Statfjord North Sea 172 138 830 34
Forties North Sea 207 172 790 35

Validation Results

============================================================
VALIDATION SUMMARY: De Boer vs Literature Field Data
============================================================

Confusion Matrix:
                    Actual
                 Problem  No Problem
Predicted Problem    5         0
Predicted OK         0         5

Performance Metrics:
  Accuracy:    100.0% (10/10 correct)
  Sensitivity: 100.0% (detects actual problems)
  Specificity: 100.0% (avoids false alarms)

Key Findings:

Case Study: Hassi Messaoud

The Hassi Messaoud field in Algeria is a classic example of severe asphaltene problems, documented extensively in the literature:

Field Conditions:
  Reservoir Pressure: 414 bar
  Bubble Point: 172 bar
  Undersaturation: 242 bar
  In-situ Density: 694 kg/m³ (~43° API)

NeqSim De Boer Prediction:
  Risk Level: SEVERE_PROBLEM
  Risk Index: 4.38

Field Experience: SEVERE PROBLEMS (confirmed)

The combination of:

creates conditions highly favorable for asphaltene destabilization.

Case Study: North Sea Stable Fields

The Brent, Statfjord, and Forties fields in the North Sea operated for decades without significant asphaltene issues:

Field ΔP [bar] ρ [kg/m³] Risk Index Prediction
Brent 35 850 0.19 NO_PROBLEM
Statfjord 34 830 0.21 NO_PROBLEM
Forties 35 790 0.27 NO_PROBLEM

These fields have:

SARA Analysis Validation

Literature SARA Data

SARA (Saturates, Aromatics, Resins, Asphaltenes) data from Akbarzadeh et al. (2007):

Crude Oil S A R Asp CII R/A Status
Alaska North Slope 0.64 0.22 0.10 0.04 2.13 2.5 Stable
Arabian Light 0.63 0.25 0.09 0.03 1.94 3.0 Stable
Brent Blend 0.58 0.28 0.11 0.03 1.56 3.7 Stable
Mars (GoM) 0.52 0.30 0.13 0.05 1.33 2.6 Stable
Bonny Light 0.60 0.26 0.10 0.04 1.78 2.5 Stable
Maya (Mexico) 0.42 0.28 0.18 0.12 1.17 1.5 Unstable
Boscan (Venezuela) 0.25 0.32 0.26 0.17 0.72 1.5 Unstable

Resin-to-Asphaltene Ratio (R/A)

The R/A ratio proved to be a reliable stability indicator:

Status R/A Range Prediction
Stable 2.5 - 3.7 Correctly identified
Unstable 1.5 Correctly identified

The R/A ratio thresholds:

Physical Behavior Validation

Undersaturation Effect

The De Boer model correctly captures the physics that risk increases with undersaturation:

ΔP [bar] | Risk Index | Risk Level
---------|------------|------------------
    20   |    0.26    | NO_PROBLEM
    60   |    0.79    | NO_PROBLEM
   100   |    1.32    | SLIGHT_PROBLEM
   140   |    1.84    | MODERATE_PROBLEM
   180   |    2.37    | MODERATE_PROBLEM
   220   |    2.89    | SEVERE_PROBLEM
   260   |    3.42    | SEVERE_PROBLEM
   300   |    3.95    | SEVERE_PROBLEM

Verified: Risk increases monotonically with undersaturation

Density Effect

Light oils (low density) are more prone to asphaltene problems:

Density [kg/m³] | Risk Index | Risk Level
----------------|------------|------------------
     650        |   10.00    | SEVERE_PROBLEM
     700        |    3.33    | SEVERE_PROBLEM
     750        |    2.00    | MODERATE_PROBLEM
     800        |    1.43    | SLIGHT_PROBLEM
     850        |    1.11    | SLIGHT_PROBLEM

Verified: Risk decreases monotonically with increasing density

Bubble Point Boundary

At the bubble point (zero undersaturation), risk should be minimal:

At Bubble Point (ΔP = 0):
  Risk Level: NO_PROBLEM
  Risk Index: 0.000

Just Above (ΔP = 10 bar):
  Risk Level: NO_PROBLEM
  Risk Index: 0.100

Verified: Minimal risk at/near bubble point

CPA Validation

Phase Behavior Validation

The CPA model with the asphaltene pseudo-component correctly captures:

  1. Pressure Depletion Effects: As pressure decreases, gas evolves and remaining liquid becomes denser
  2. Temperature Effects: Bubble point increases with temperature (thermodynamically consistent)
  3. Composition Effects: Higher methane content leads to higher bubble points
  4. Alkane Chain Length: Higher carbon number n-alkanes reduce asphaltene solubility

CPA with TBPfraction Validation

Using TBPfraction to create realistic oil densities:

Case Target ρ CPA ρ Status
Light Oil (Hassi-like) ~700 kg/m³ 761 kg/m³ ✅ Reasonable
Heavy Oil (Brent-like) ~850 kg/m³ 955 kg/m³ ✅ Conservative

The CPA model with TBPfraction produces physically reasonable oil densities that match De Boer field data trends.

Parameter Fitting Validation

The AsphalteneOnsetFitting class successfully fits CPA parameters to match experimental onset data using Levenberg-Marquardt optimization.

Typical Fitted Parameter Ranges

Oil Type ε/R [K] κ
Light oils (>35° API) 2500-3500 0.005-0.015
Medium oils (25-35° API) 3000-4000 0.003-0.008
Heavy oils (<25° API) 3500-4500 0.002-0.005

Running Validation Tests

To reproduce these validation results:

# Run all asphaltene validation tests
mvn test -Dtest="*Asphaltene*"

# Run specific De Boer validation
mvn test -Dtest="AsphalteneValidationTest#testDeBoerAgainstPublishedFieldData"

# Run CPA validation tests
mvn test -Dtest="AsphalteneValidationTest#testCPAPhysicalBehavior*"

# Run parameter fitting tests
mvn test -Dtest="AsphalteneOnsetFittingTest"

Conclusions

  1. De Boer Screening: Achieves 100% accuracy on published field data from SPE-24987-PA, correctly identifying all problem and stable fields.

  2. SARA Analysis: R/A ratio achieves 100% accuracy for stability classification on literature crude oil data.

  3. CPA Thermodynamic Model: Correctly captures pressure, temperature, and composition effects on asphaltene phase behavior.

  4. Parameter Fitting: The AsphalteneOnsetFitting class successfully tunes CPA parameters to match experimental onset data.

  5. Physical Behavior: The models correctly capture:

    • Increasing risk with undersaturation
    • Decreasing risk with density
    • Minimal risk at bubble point conditions
  6. Recommendation: Use De Boer for initial screening. For detailed onset pressure predictions, tune CPA model to experimental AOP data using AsphalteneOnsetFitting.

References

  1. De Boer, R.B., Leerlooyer, K., Eigner, M.R.P., and van Bergen, A.R.D. (1995). "Screening of Crude Oils for Asphalt Precipitation: Theory, Practice, and the Selection of Inhibitors." SPE Production & Facilities, 10(1), 55-61. SPE-24987-PA

  2. Akbarzadeh, K., Alboudwarej, H., Svrcek, W.Y., and Yarranton, H.W. (2007). "Asphaltenes—Problematic but Rich in Potential." Oilfield Review, 19(2), 22-43.

  3. Leontaritis, K.J., and Mansoori, G.A. (1988). "Asphaltene Deposition: A Survey of Field Experiences and Research Approaches." Journal of Petroleum Science and Engineering, 1(3), 229-239.

  4. Hammami, A., Phelps, C.H., Monger-McClure, T., and Little, T.M. (2000). "Asphaltene Precipitation from Live Oils: An Experimental Investigation of Onset Conditions and Reversibility." Energy & Fuels, 14(1), 14-18.

  5. Li, Z., and Firoozabadi, A. (2010). "Modeling Asphaltene Precipitation by n-Alkanes from Heavy Oils and Bitumens Using Cubic-Plus-Association Equation of State." Energy & Fuels, 24, 1106-1113.

  6. Vargas, F.M., Gonzalez, D.L., Hirasaki, G.J., and Chapman, W.G. (2009). "Modeling Asphaltene Phase Behavior in Crude Oil Systems Using the Perturbed Chain Form of the Statistical Associating Fluid Theory (PC-SAFT) Equation of State." Energy & Fuels, 23, 1140-1146.

Chapter 38: Gas Quality

Gas Quality Standards

Gas quality standards validated by ISO 6976 tests

The ISO 6976 calorific value and Wobbe index calculations are verified in Standard_ISO6976Test. This page summarizes the tested configurations and equations so you can align custom gas-quality runs with the regression suite.

Base ISO 6976 calculation

testCalculate initializes a dry natural gas at 20 °C and 1 bar with a classic mixing rule and executes Standard_ISO6976.calculate() using volume-based reference conditions (0 °C volume base, 15.55 °C energy base).【F:src/test/java/neqsim/standards/gasquality/Standard_ISO6976Test.java†L23-L47】 The test confirms the gross calorific value (GCV) of 39,614.57 kJ/Sm³ and Wobbe index (WI) of 44.61 MJ/Sm³.

The Wobbe index relation checked in the test is

[ WI = \frac{GCV}{\sqrt{\rho_r}}\ , ]

where (\rho_r) is the relative density. Matching the test values indicates both combustion energy and density normalization are consistent with ISO 6976.

Handling reference condition overrides

testCalculateWithWrongReferenceState shows that if non-standard reference temperatures are provided, the standard falls back to defined bases (15 °C for energy, 0 °C for volume) while still computing GCV and WI.【F:src/test/java/neqsim/standards/gasquality/Standard_ISO6976Test.java†L49-L73】 Use this behavior to guard against user input errors without failing calculations.

Including pseudo-components

testCalculateWithPSeudo adds a TBP pseudo-fraction to the gas and re-runs the calculation to verify heavier fractions contribute to higher heating value (GCV ≈ 42,378 kJ/Sm³).【F:src/test/java/neqsim/standards/gasquality/Standard_ISO6976Test.java†L75-L96】 The setup demonstrates that ISO 6976 evaluation tolerates lumped heavy ends when a classic mixing rule and full flash initialization are applied.

Full-property audit

testCalculate2 and testCalculate3 sweep alternative temperatures and reference pairs to assert a complete property set: compression factor, superior/inferior calorific values, Wobbe indices, relative density, and molar mass.【F:src/test/java/neqsim/standards/gasquality/Standard_ISO6976Test.java†L98-L200】 The tests also run a process Stream to ensure downstream WI reporting matches the standard calculation.

When configuring your own gas-quality evaluations:

Humid Air

Humid air mathematics

This page summarises the equations implemented in the HumidAir utility class for psychrometric calculations. The correlations are based on the ASHRAE Handbook Fundamentals (2017), CoolProp and the IAPWS formulation for the saturation pressure of water.

Saturation pressure of water

For temperatures $T$ above the triple point the saturation vapour pressure $p_{ws}$ in pascal is given by the IAPWS equation of Wagner and Pruss (2002)

$$ \ln\left(\frac{p_{ws}}{p_c}\right) = \frac{T_c}{T}\left(a_1\theta + a_2\theta^{3/2} + a_3\theta^3 + a_4\theta^{7/2} + a_5\theta^4 + a_6\theta^{15/2}\right) $$

where $\theta = 1 - T/T_c$, $T_c = 647.096\ \text{K}$ and $p_c = 22.064\ \text{MPa}$. Below the triple point a sublimation correlation is used.

Humidity ratio

The humidity ratio $W$ relates the mass of water vapour to the mass of dry air

$$ W = \varepsilon \frac{p_w}{p - p_w} $$

where $\varepsilon = M_w/M_{da} \approx 0.621945$, $p$ is the total pressure and $p_w$ the partial pressure of water.

For a given relative humidity $\phi$, the partial pressure is $p_w = \phi p_{ws}$.

Dew point temperature

Given a humidity ratio, the dew point temperature $T_d$ is found by solving $p_{ws}(T_d) = p_w$. The HumidAir implementation uses a simple Newton iteration.

Specific enthalpy

On a dry-air basis the specific enthalpy $h$ in kJ/kg dry air is approximated by

$$ h = 1.006\,t + W (2501 + 1.86\,t) $$

where $t$ is the temperature in degrees Celsius and $W$ is the humidity ratio.

Saturated specific heat

CoolProp provides a correlation for the saturated humid-air specific heat $c_{p,\text{sat}}$ at 1\,atm valid from 250\,K to 300\,K

$$ c_{p,\text{sat}} = 2.146\,27073 \times 10^{3} - 3.289\,17768 \times 10^{1}T + 1.894\,71075 \times 10^{-1}T^2 \

Chapter 39: ISO Standards

Standards Overview

Standards Package

The NeqSim standards package implements international standards for gas and oil quality calculations, enabling compliance verification and sales contract management.

Table of Contents


Overview

Location: neqsim.standards

The standards package provides implementations of:

  1. Gas Quality Standards - ISO 6976, ISO 6974, ISO 6578, ISO 15403, ISO 18453
  2. Oil Quality Standards - ASTM D6377 for vapor pressure
  3. Sales Contracts - Specification verification against contractual limits

Key Applications:


Package Structure

standards/
├── Standard.java                    # Abstract base class
├── StandardInterface.java           # Interface definition
│
├── gasquality/                      # Gas quality standards
│   ├── Standard_ISO6976.java        # Calorific values & Wobbe index
│   ├── Standard_ISO6976_2016.java   # ISO 6976:2016 edition
│   ├── Standard_ISO6974.java        # Gas chromatography composition
│   ├── Standard_ISO6578.java        # LNG density calculation
│   ├── Standard_ISO15403.java       # CNG fuel quality (MON, methane number)
│   ├── Draft_ISO18453.java          # Water dew point (GERG-water)
│   ├── Draft_GERG2004.java          # GERG-2004 EoS properties
│   ├── BestPracticeHydrocarbonDewPoint.java  # HC dew point
│   ├── GasChromotograpyhBase.java   # Gas composition base class
│   ├── SulfurSpecificationMethod.java  # H2S and sulfur content
│   └── UKspecifications_ICF_SI.java # UK ICF/SI specifications
│
├── oilquality/                      # Oil quality standards
│   └── Standard_ASTM_D6377.java     # Reid vapor pressure (RVP)
│
└── salescontract/                   # Contract management
    ├── BaseContract.java            # Contract implementation
    ├── ContractInterface.java       # Contract interface
    └── ContractSpecification.java   # Individual specifications

Sub-Documentation

Detailed guides for each major standard:

Guide Description
ISO 6976 - Calorific Values GCV, LCV, Wobbe index, density from composition
ISO 6578 - LNG Density LNG density calculation method
ISO 15403 - CNG Quality Methane number and MON for vehicle fuel
Dew Point Standards Water and hydrocarbon dew point methods
ASTM D6377 - RVP Reid vapor pressure for crude and condensate
Sales Contracts Contract specification and compliance checking

Core Concepts

StandardInterface

All standards implement StandardInterface:

public interface StandardInterface {
    void calculate();                              // Run calculation
    double getValue(String parameter);             // Get result
    double getValue(String parameter, String unit); // Get result with unit
    String getUnit(String parameter);              // Get unit string
    boolean isOnSpec();                            // Check compliance

    ContractInterface getSalesContract();          // Get attached contract
    void setSalesContract(ContractInterface contract);
    SystemInterface getThermoSystem();             // Get fluid
}

Standard Base Class

Standards extend Standard:

public abstract class Standard extends NamedBaseClass implements StandardInterface {
    protected SystemInterface thermoSystem;
    protected ThermodynamicOperations thermoOps;
    protected ContractInterface salesContract;
    protected String standardDescription;
    private String referenceState = "real";  // or "ideal"
    private double referencePressure = 70.0;
}

Reference States

Most gas quality standards support:

standard.setReferenceState("real");   // Default
standard.setReferenceState("ideal");  // Ideal gas assumption

Quick Start

ISO 6976 - Calorific Values

import neqsim.thermo.system.SystemSrkEos;
import neqsim.standards.gasquality.Standard_ISO6976;

// Create gas composition
SystemInterface gas = new SystemSrkEos(273.15 + 15, 1.01325);
gas.addComponent("methane", 0.90);
gas.addComponent("ethane", 0.05);
gas.addComponent("propane", 0.02);
gas.addComponent("nitrogen", 0.02);
gas.addComponent("CO2", 0.01);
gas.setMixingRule("classic");

// Create standard
// Parameters: system, volumeRefT(°C), energyRefT(°C), referenceType
Standard_ISO6976 iso6976 = new Standard_ISO6976(gas, 15, 15, "volume");
iso6976.setReferenceState("real");

// Calculate
iso6976.calculate();

// Get results
double gcv = iso6976.getValue("GCV");           // Gross calorific value [kJ/m³]
double lcv = iso6976.getValue("LCV");           // Net calorific value [kJ/m³]
double wobbe = iso6976.getValue("SuperiorWobbeIndex");  // Wobbe index [kJ/m³]
double relDens = iso6976.getValue("RelativeDensity");   // Relative density [-]
double Z = iso6976.getValue("CompressionFactor");       // Compressibility [-]
double molarMass = iso6976.getValue("MolarMass");       // g/mol

System.out.printf("GCV = %.2f kJ/m³%n", gcv);
System.out.printf("Wobbe Index = %.2f kJ/m³%n", wobbe);
System.out.printf("Relative Density = %.4f%n", relDens);

LNG Density (ISO 6578)

import neqsim.standards.gasquality.Standard_ISO6578;

// LNG composition
SystemInterface lng = new SystemSrkEos(110, 1.01325);  // -163°C
lng.addComponent("methane", 0.92);
lng.addComponent("ethane", 0.05);
lng.addComponent("propane", 0.02);
lng.addComponent("nitrogen", 0.01);
lng.setMixingRule("classic");

// Calculate density
Standard_ISO6578 iso6578 = new Standard_ISO6578(lng);
iso6578.calculate();

double density = iso6578.getValue("density", "kg/m3");
System.out.printf("LNG Density = %.2f kg/m³%n", density);

Water Dew Point (ISO 18453)

import neqsim.standards.gasquality.Draft_ISO18453;

// Natural gas with water
SystemInterface wetGas = new SystemSrkCPA(273.15 + 20, 70.0);
wetGas.addComponent("methane", 0.95);
wetGas.addComponent("water", 50e-6);  // 50 ppm water
wetGas.setMixingRule("CPA-EoS");

// Calculate water dew point
Draft_ISO18453 waterDewPoint = new Draft_ISO18453(wetGas);
waterDewPoint.calculate();

double wdp = waterDewPoint.getValue("dewPointTemperature");
System.out.printf("Water Dew Point = %.1f °C%n", wdp);

Reid Vapor Pressure (ASTM D6377)

import neqsim.standards.oilquality.Standard_ASTM_D6377;

// Crude oil / condensate
SystemInterface crude = new SystemSrkEos(273.15 + 15, 1.0);
crude.addComponent("methane", 0.02);
crude.addComponent("ethane", 0.03);
crude.addComponent("propane", 0.05);
crude.addComponent("n-butane", 0.08);
crude.addComponent("n-pentane", 0.10);
crude.addTBPfraction("C6", 0.15, 86/1000.0, 0.66);
crude.addTBPfraction("C10", 0.30, 142/1000.0, 0.78);
crude.addTBPfraction("C20", 0.27, 282/1000.0, 0.85);
crude.setMixingRule("classic");

// Calculate RVP
Standard_ASTM_D6377 rvpStandard = new Standard_ASTM_D6377(crude);
rvpStandard.setMethodRVP("VPCR4");  // Options: VPCR4, RVP_ASTM_D6377, RVP_ASTM_D323_82
rvpStandard.calculate();

double rvp = rvpStandard.getValue("RVP", "bara");
double tvp = rvpStandard.getValue("TVP", "bara");
System.out.printf("RVP = %.3f bara%n", rvp);
System.out.printf("TVP = %.3f bara%n", tvp);

Sales Contracts

Creating a Contract

import neqsim.standards.salescontract.BaseContract;
import neqsim.standards.salescontract.ContractInterface;

// Create contract from database
ContractInterface contract = new BaseContract(gas, "Kaarstoe", "Norway");

// Run compliance check
contract.runCheck();

// Get results
String[][] results = contract.getResultTable();
int numSpecs = contract.getSpecificationsNumber();

// Display results
contract.display();

Attaching Contract to Standard

Standard_ISO6976 standard = new Standard_ISO6976(gas);
standard.setSalesContract(contract);
standard.calculate();

// Check if on specification
boolean onSpec = standard.isOnSpec();

Custom Contract Specifications

import neqsim.standards.salescontract.ContractSpecification;

// Create custom specification
ContractSpecification spec = new ContractSpecification(
    "Water Dew Point",           // Name
    "Maximum water dew point",   // Description
    "Norway",                    // Country
    "Kaarstoe",                  // Terminal
    waterDewPointStandard,       // Standard method
    -20.0,                       // Min value
    -8.0,                        // Max value
    "°C",                        // Unit
    15.0,                        // Reference T measurement
    15.0,                        // Reference T combustion
    70.0,                        // Reference pressure
    "At 70 bar"                  // Comments
);

Available Return Parameters

ISO 6976

Parameter Description Unit
GCV / SuperiorCalorificValue Gross calorific value kJ/m³
LCV / InferiorCalorificValue Net calorific value kJ/m³
SuperiorWobbeIndex Superior Wobbe index kJ/m³
InferiorWobbeIndex Inferior Wobbe index kJ/m³
WI Wobbe index (alias) kJ/m³
RelativeDensity Relative density (air=1) -
CompressionFactor Compressibility factor Z -
MolarMass Average molar mass g/mol
DensityIdeal Ideal gas density kg/m³
DensityReal Real gas density kg/m³

ISO 6578

Parameter Description Unit
density LNG density kg/m³

ASTM D6377

Parameter Description Unit
RVP Reid vapor pressure bara
TVP True vapor pressure bara
VPCR4 Vapor pressure at V/L=4 bara

Reference Conditions

Standard Temperature/Pressure

Standard Volume Ref T Energy Ref T Pressure
ISO 6976 0, 15, 20°C 0, 15, 20, 25°C, 60°F 1.01325 bar
ISO 6578 -160 to -140°C - 1.01325 bar
ASTM D6377 37.8°C (100°F) - -

Setting Reference Conditions

// ISO 6976 with specific reference conditions
Standard_ISO6976 standard = new Standard_ISO6976(
    gas,
    15.0,      // Volume reference temperature (°C)
    25.0,      // Energy reference temperature (°C)  
    "volume"   // Reference type: "volume", "mass", or "molar"
);

// Modify reference conditions after creation
standard.setVolRefT(0.0);      // Volume at 0°C
standard.setEnergyRefT(15.0);  // Combustion at 15°C

Best Practices

Composition Normalization

Component Coverage

Reference State Selection

Contract Database


References

  1. ISO 6976:2016 - Natural gas — Calculation of calorific values, density, relative density and Wobbe indices from composition
  2. ISO 6578:2017 - Refrigerated hydrocarbon liquids — Static measurement — Calculation procedure
  3. ISO 15403-1:2006 - Natural gas — Natural gas for use as a compressed fuel for vehicles
  4. ISO 18453:2004 - Natural gas — Correlation between water content and water dew point
  5. ASTM D6377 - Standard Test Method for Determination of Vapor Pressure of Crude Oil
  6. GERG-2004 - The GERG-2004 Wide-Range Equation of State for Natural Gases and Other Mixtures

ISO 6976

ISO 6976 - Calorific Values from Composition

ISO 6976 provides methods for calculating calorific values, density, relative density, and Wobbe indices from natural gas composition.

Table of Contents


Overview

Standard: ISO 6976:2016 (and earlier editions)

Purpose: Calculate physical properties of natural gas from composition analysis without direct measurement.

Scope:

Classes:


Calculated Properties

Calorific Values

Gross Calorific Value (GCV) - Also called Higher Heating Value (HHV): $$H_s = \sum_i x_i \cdot H_{s,i}^{id}$$

Net Calorific Value (LCV) - Also called Lower Heating Value (LHV): $$H_i = \sum_i x_i \cdot H_{i,i}^{id}$$

where:

Wobbe Index

The Wobbe index indicates interchangeability of fuel gases:

$$W_s = \frac{H_s}{\sqrt{d}}$$

where $d$ is the relative density.

Compressibility Factor

Mixture compressibility at reference conditions:

$$Z_{mix} = 1 - \left(\sum_i x_i \sqrt{b_i}\right)^2$$

where $b_i$ is the summation factor for component i.

Relative Density

$$d = \frac{\rho_{gas}}{\rho_{air}} = \frac{M_{gas}}{M_{air}} \cdot \frac{Z_{air}}{Z_{gas}}$$


Reference Conditions

Temperature Options

Reference T Description
0°C (273.15 K) European standard
15°C (288.15 K) ISO standard / metric
15.55°C (60°F) US/UK standard
20°C (293.15 K) Engineering standard
25°C (298.15 K) Thermochemical standard

Reference State

Reference Type


Implementation

Constructor

// Basic constructor (uses default reference conditions)
Standard_ISO6976 standard = new Standard_ISO6976(thermoSystem);

// Full constructor with reference conditions
Standard_ISO6976 standard = new Standard_ISO6976(
    thermoSystem,                    // SystemInterface
    volumeRefT,                      // Volume reference T [°C]
    energyRefT,                      // Energy reference T [°C]
    calculationType                  // "volume", "mass", or "molar"
);

Key Methods

Method Description
calculate() Perform calculations
getValue(param) Get calculated value
getValue(param, unit) Get value with unit conversion
setReferenceState(state) Set "real" or "ideal"
setReferenceType(type) Set "volume", "mass", or "molar"
setVolRefT(T) Set volume reference temperature
setEnergyRefT(T) Set energy reference temperature
getAverageCarbonNumber() Average C number of mixture

Internal Data

Component properties are loaded from database table ISO6976constants:


Usage Examples

Basic Calculation

import neqsim.thermo.system.SystemSrkEos;
import neqsim.standards.gasquality.Standard_ISO6976;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create natural gas composition
SystemInterface gas = new SystemSrkEos(273.15 + 15, 1.01325);
gas.addComponent("methane", 0.9248);
gas.addComponent("ethane", 0.0350);
gas.addComponent("propane", 0.0098);
gas.addComponent("n-butane", 0.0022);
gas.addComponent("i-butane", 0.0034);
gas.addComponent("n-pentane", 0.0006);
gas.addComponent("i-pentane", 0.0005);
gas.addComponent("nitrogen", 0.0107);
gas.addComponent("CO2", 0.0130);
gas.setMixingRule("classic");

// Flash to ensure single phase
ThermodynamicOperations ops = new ThermodynamicOperations(gas);
ops.TPflash();

// Create ISO 6976 standard
Standard_ISO6976 iso6976 = new Standard_ISO6976(gas, 15, 15, "volume");
iso6976.setReferenceState("real");

// Calculate
iso6976.calculate();

// Get results
System.out.println("=== ISO 6976 Results (15°C/15°C, real gas) ===");
System.out.printf("GCV = %.2f kJ/m³%n", iso6976.getValue("GCV"));
System.out.printf("LCV = %.2f kJ/m³%n", iso6976.getValue("LCV"));
System.out.printf("Wobbe Index = %.2f kJ/m³%n", iso6976.getValue("SuperiorWobbeIndex"));
System.out.printf("Relative Density = %.5f%n", iso6976.getValue("RelativeDensity"));
System.out.printf("Z (compressibility) = %.6f%n", iso6976.getValue("CompressionFactor"));
System.out.printf("Molar Mass = %.4f g/mol%n", iso6976.getValue("MolarMass"));
System.out.printf("Density (real) = %.4f kg/m³%n", iso6976.getValue("DensityReal"));

Different Reference Conditions

// Standard conditions for US market (60°F)
Standard_ISO6976 us_standard = new Standard_ISO6976(gas, 15.55, 15.55, "volume");
us_standard.setReferenceState("real");
us_standard.calculate();
System.out.printf("GCV (60°F) = %.2f kJ/m³%n", us_standard.getValue("GCV"));

// European conditions (0°C metering, 25°C combustion)
Standard_ISO6976 eu_standard = new Standard_ISO6976(gas, 0, 25, "volume");
eu_standard.setReferenceState("real");
eu_standard.calculate();
System.out.printf("GCV (0°C/25°C) = %.2f kJ/m³%n", eu_standard.getValue("GCV"));

// Mass-based calorific value
Standard_ISO6976 mass_standard = new Standard_ISO6976(gas, 15, 25, "mass");
mass_standard.calculate();
System.out.printf("GCV (mass) = %.2f kJ/kg%n", mass_standard.getValue("GCV"));

Using in kWh

// Get Wobbe index in kWh/m³
double wobbeKWh = iso6976.getValue("SuperiorWobbeIndex", "kWh");
System.out.printf("Wobbe Index = %.4f kWh/m³%n", wobbeKWh);

With Pseudo-Components (TBP Fractions)

// Gas with heavy ends characterized as TBP fraction
SystemInterface richGas = new SystemSrkEos(273.15 + 15, 1.01325);
richGas.addComponent("methane", 0.85);
richGas.addComponent("ethane", 0.05);
richGas.addComponent("propane", 0.03);
richGas.addTBPfraction("C7+", 0.02, 100.0/1000.0, 0.75);  // MW=100, SG=0.75
richGas.addComponent("CO2", 0.03);
richGas.addComponent("nitrogen", 0.02);
richGas.setMixingRule("classic");

Standard_ISO6976 standard = new Standard_ISO6976(richGas, 15, 15, "volume");
standard.calculate();

// Note: TBP fractions are approximated using n-heptane properties
double gcv = standard.getValue("GCV");

Display Results Table

// Create formatted table
String[][] table = iso6976.createTable("ISO 6976 Results");

// Or display in GUI
iso6976.display();

Return Parameters

Parameter Aliases Description
SuperiorCalorificValue GCV Gross calorific value
InferiorCalorificValue LCV Net calorific value
SuperiorWobbeIndex WI Superior Wobbe index
InferiorWobbeIndex - Inferior Wobbe index
RelativeDensity - Relative density (air=1)
CompressionFactor - Compressibility factor Z
MolarMass - Average molar mass [g/mol]
DensityIdeal - Ideal gas density [kg/m³]
DensityReal - Real gas density [kg/m³]

Unit Options

For calorific values and Wobbe index:


Component Data

Supported Components

The standard includes data for:

Handling Unknown Components

Components not in the ISO 6976 database are approximated:

Component Type Approximated As
HC, TBP, plus n-heptane
alcohol, glycol methanol
Other inert (N₂)

Check for approximations:

ArrayList<String> unknowns = standard.getComponentsNotDefinedByStandard();
if (!unknowns.isEmpty()) {
    System.out.println("Warning: Components approximated: " + unknowns);
}

Versions

Standard_ISO6976 (1995 Edition)

Original implementation based on ISO 6976:1995.

Standard_ISO6976_2016 (2016 Edition)

Updated implementation with:

import neqsim.standards.gasquality.Standard_ISO6976_2016;

Standard_ISO6976_2016 iso2016 = new Standard_ISO6976_2016(gas, 15, 25, "volume");
iso2016.calculate();
double gcv = iso2016.getValue("GCV");

Validation

Reference Air Properties

Temperature Z_air (1995) Z_air (2016)
0°C 0.99941 0.999419
15°C 0.99958 0.999595
20°C 0.99963 0.999645
60°F - 0.999601

Typical Results for Dry Gas

For a typical North Sea gas (mostly methane):


References

  1. ISO 6976:2016 - Natural gas — Calculation of calorific values, density, relative density and Wobbe indices from composition
  2. ISO 6976:1995 - Natural gas — Calculation of calorific values, density and Wobbe index from composition
  3. GPA 2172 - Calculation of Gross Heating Value, Relative Density and Compressibility Factor for Natural Gas Mixtures from Compositional Analysis

ISO 6578

ISO 6578 - LNG Density Calculation

ISO 6578 provides methods for calculating the density of liquefied natural gas (LNG) from composition and temperature.

Table of Contents


Overview

Standard: ISO 6578:2017 - Refrigerated hydrocarbon liquids — Static measurement — Calculation procedure

Purpose: Calculate LNG density for custody transfer and inventory management.

Scope:

Class: Standard_ISO6578


Calculation Method

Principle

LNG density is calculated from:

  1. Ideal mixing - Pure component molar volumes
  2. Excess volume correction - Klosek-McKinley correction factors

Density Equation

$$\rho = \frac{M_{mix}}{V_{mix}}$$

where: $$V_{mix} = \sum_i x_i V_i + \Delta V_{correction}$$

Klosek-McKinley Correction

$$V_{mix} = \sum_i x_i V_i - k_1 x_{N_2} - k_2 x_{CH_4}$$

where:


Implementation

Constructor

import neqsim.standards.gasquality.Standard_ISO6578;

// Create standard
Standard_ISO6578 iso6578 = new Standard_ISO6578(thermoSystem);

Key Methods

Method Description
calculate() Perform density calculation
getValue("density", "kg/m3") Get density in kg/m³
useISO6578VolumeCorrectionFacotrs(boolean) Toggle ISO 6578 vs alternative factors

Internal Data

The class includes tabulated data for:


Usage Examples

Basic LNG Density Calculation

import neqsim.thermo.system.SystemSrkEos;
import neqsim.standards.gasquality.Standard_ISO6578;

// Create LNG composition at storage temperature
SystemInterface lng = new SystemSrkEos(273.15 - 162, 1.01325);  // -162°C
lng.addComponent("methane", 0.9200);
lng.addComponent("ethane", 0.0500);
lng.addComponent("propane", 0.0180);
lng.addComponent("n-butane", 0.0040);
lng.addComponent("i-butane", 0.0030);
lng.addComponent("nitrogen", 0.0050);
lng.setMixingRule("classic");

// Calculate density
Standard_ISO6578 iso6578 = new Standard_ISO6578(lng);
iso6578.calculate();

double density = iso6578.getValue("density", "kg/m3");
System.out.printf("LNG Density at %.1f°C = %.2f kg/m³%n", 
    lng.getTemperature() - 273.15, density);

Density at Different Temperatures

// Temperature sweep
double[] temperatures = {-165, -162, -160, -155, -150};  // °C

for (double T : temperatures) {
    lng.setTemperature(T + 273.15);
    iso6578.calculate();
    double rho = iso6578.getValue("density", "kg/m3");
    System.out.printf("T = %.0f°C: ρ = %.2f kg/m³%n", T, rho);
}

Rich LNG (High Ethane/Propane)

// Rich LNG composition
SystemInterface richLNG = new SystemSrkEos(273.15 - 162, 1.01325);
richLNG.addComponent("methane", 0.8500);
richLNG.addComponent("ethane", 0.0900);
richLNG.addComponent("propane", 0.0400);
richLNG.addComponent("n-butane", 0.0100);
richLNG.addComponent("nitrogen", 0.0100);
richLNG.setMixingRule("classic");

Standard_ISO6578 standard = new Standard_ISO6578(richLNG);
standard.calculate();

double density = standard.getValue("density", "kg/m3");
System.out.printf("Rich LNG Density = %.2f kg/m³%n", density);

// Rich LNG has higher density due to heavier components

Comparing Correction Factor Options

// Using ISO 6578 correction factors (default)
Standard_ISO6578 iso_factors = new Standard_ISO6578(lng);
iso_factors.useISO6578VolumeCorrectionFacotrs(true);
iso_factors.calculate();
double rho_iso = iso_factors.getValue("density", "kg/m3");

// Using alternative correction factors
Standard_ISO6578 alt_factors = new Standard_ISO6578(lng);
alt_factors.useISO6578VolumeCorrectionFacotrs(false);
alt_factors.calculate();
double rho_alt = alt_factors.getValue("density", "kg/m3");

System.out.printf("ISO 6578 factors: %.2f kg/m³%n", rho_iso);
System.out.printf("Alternative factors: %.2f kg/m³%n", rho_alt);

Correction Factors

Klosek-McKinley Correction Factor Tables

The implementation includes two sets of correction factors:

ISO 6578 Factors

Temperature range: 93.15 K to 133.15 K (-180°C to -140°C) Molar mass range: 16 to 30 g/mol

Temperatures: {93.15, 98.15, 103.15, 108.15, 113.15, 118.15, 123.15, 128.15, 133.15} K
Molar Masses: {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30} g/mol

Alternative Factors

Temperature range: 105 K to 135 K Molar mass range: 16 to 25 g/mol

Interpolation

Bicubic interpolation is used for:

Linear interpolation for pure component molar volumes.


Component Data

Supported Components

Component Formula Molar Volume Data
Methane CH₄ Yes
Ethane C₂H₆ Yes
Propane C₃H₈ Yes
n-Butane C₄H₁₀ Yes
i-Butane C₄H₁₀ Yes
n-Pentane C₅H₁₂ Yes
i-Pentane C₅H₁₂ Yes
n-Hexane C₆H₁₄ Yes
Nitrogen N₂ Yes

Molar Volume Data

Pure component molar volumes at reference temperatures:

Temperatures: {93.15, 98.15, 103.15, 108.15, 113.15, 118.15, 123.15, 128.15, 133.15} K

Example - Methane (dm³/mol):
{0.035771, 0.036315, 0.036891, 0.037500, 0.038149, 0.038839, 0.039580, 0.040375, 0.041237}

Typical Results

Lean LNG (High Methane)

Composition Value
Methane 95%
Ethane 3%
Others 2%
Density at -162°C 425-435 kg/m³

Standard LNG

Composition Value
Methane 90-92%
Ethane 5-6%
Propane 2%
Others 1-2%
Density at -162°C 440-460 kg/m³

Rich LNG (High C2+)

Composition Value
Methane 85%
Ethane 9%
Propane 4%
Others 2%
Density at -162°C 470-490 kg/m³

Accuracy Considerations

Temperature Sensitivity

LNG density is strongly temperature dependent:

Composition Sensitivity

Uncertainty

Typical uncertainty: ±0.1% for well-characterized LNG


References

  1. ISO 6578:2017 - Refrigerated hydrocarbon liquids — Static measurement — Calculation procedure
  2. Klosek, J., McKinley, C. (1968). Densities of Liquefied Natural Gas and of Low Molecular Weight Hydrocarbons. Proceedings of the First International Conference on LNG.
  3. GIIGNL Custody Transfer Handbook (5th Edition)

ISO 15403

ISO 15403 - CNG Quality

ISO 15403 specifies requirements for natural gas used as compressed fuel for vehicles (CNG).

Table of Contents


Overview

Standard: ISO 15403-1:2006 - Natural gas — Natural gas for use as a compressed fuel for vehicles

Purpose: Assess natural gas quality for use in vehicle engines by calculating:

Class: Standard_ISO15403


Calculated Parameters

Motor Octane Number (MON)

MON indicates knock resistance. Calculated from gas composition:

$$MON = 137.78 \cdot x_{CH_4} + 29.948 \cdot x_{C_2H_6} - 18.193 \cdot x_{C_3H_8} - 167.062 \cdot (x_{nC_4} + x_{iC_4}) + 181.233 \cdot x_{CO_2} + 26.944 \cdot x_{N_2}$$

where $x_i$ is the mole fraction of component i.

Methane Number (MN)

MN is derived from MON:

$$MN = 1.445 \cdot MON - 103.42$$

Interpretation:


Implementation

Constructor

import neqsim.standards.gasquality.Standard_ISO15403;

// Create from gas composition
Standard_ISO15403 iso15403 = new Standard_ISO15403(thermoSystem);

Key Methods

Method Description
calculate() Calculate MON and MN
getValue("MON") Get Motor Octane Number
getValue("MN") Get Methane Number

Usage Examples

Basic Calculation

import neqsim.thermo.system.SystemSrkEos;
import neqsim.standards.gasquality.Standard_ISO15403;

// CNG composition
SystemInterface cng = new SystemSrkEos(273.15 + 15, 200.0);
cng.addComponent("methane", 0.92);
cng.addComponent("ethane", 0.04);
cng.addComponent("propane", 0.01);
cng.addComponent("n-butane", 0.002);
cng.addComponent("i-butane", 0.003);
cng.addComponent("CO2", 0.015);
cng.addComponent("nitrogen", 0.01);
cng.setMixingRule("classic");

// Calculate methane number
Standard_ISO15403 iso15403 = new Standard_ISO15403(cng);
iso15403.calculate();

double mon = iso15403.getValue("MON");
double mn = iso15403.getValue("MN");

System.out.printf("Motor Octane Number (MON) = %.1f%n", mon);
System.out.printf("Methane Number (MN) = %.1f%n", mn);

Effect of Composition on Methane Number

// Analyze MN sensitivity to C2+ content
double[] ethaneContents = {0.01, 0.03, 0.05, 0.08, 0.10};

System.out.println("Ethane (mol%) | MON   | MN");
System.out.println("--------------|-------|------");

for (double c2 : ethaneContents) {
    SystemInterface gas = new SystemSrkEos(273.15 + 15, 200.0);
    gas.addComponent("methane", 0.97 - c2);
    gas.addComponent("ethane", c2);
    gas.addComponent("CO2", 0.02);
    gas.addComponent("nitrogen", 0.01);
    gas.setMixingRule("classic");

    Standard_ISO15403 std = new Standard_ISO15403(gas);
    std.calculate();

    System.out.printf("%13.0f | %.1f | %.1f%n", 
        c2 * 100, 
        std.getValue("MON"), 
        std.getValue("MN"));
}

CO2 and N2 Effects

// CO2 and N2 improve methane number
SystemInterface leanGas = new SystemSrkEos(273.15 + 15, 200.0);
leanGas.addComponent("methane", 0.85);
leanGas.addComponent("ethane", 0.05);
leanGas.addComponent("propane", 0.02);

// Base case - no inerts
leanGas.addComponent("CO2", 0.0);
leanGas.addComponent("nitrogen", 0.0);
leanGas.setMixingRule("classic");

Standard_ISO15403 baseStd = new Standard_ISO15403(leanGas);
baseStd.calculate();
System.out.printf("Base case MN: %.1f%n", baseStd.getValue("MN"));

// With 5% CO2
SystemInterface withCO2 = leanGas.clone();
withCO2.addComponent("CO2", 0.05);
Standard_ISO15403 co2Std = new Standard_ISO15403(withCO2);
co2Std.calculate();
System.out.printf("With 5%% CO2 MN: %.1f%n", co2Std.getValue("MN"));

// With 5% N2
SystemInterface withN2 = leanGas.clone();
withN2.addComponent("nitrogen", 0.05);
Standard_ISO15403 n2Std = new Standard_ISO15403(withN2);
n2Std.calculate();
System.out.printf("With 5%% N2 MN: %.1f%n", n2Std.getValue("MN"));

Methane Number Concepts

Component Effects

Component Effect on MN
Methane Increases MN
Ethane Slight decrease
Propane Moderate decrease
Butanes Strong decrease
CO₂ Increases MN
N₂ Increases MN
H₂ Decreases MN

Typical Values

Gas Type Typical MN
Pure methane 100
Lean natural gas 85-95
Associated gas 70-85
Biogas 130-140
LNG regasified 75-90

Specifications

Region Minimum MN
Europe (typical) 65-70
Germany (DIN 51624) 70
California 80

Technical Notes

Correlation Validity

The correlation is valid for:

Limitations

  1. Does not account for C5+ hydrocarbons
  2. Hydrogen effects not included in correlation
  3. Best for pipeline-quality natural gas

Alternative Methods

For more complex gases, consider:


References

  1. ISO 15403-1:2006 - Natural gas — Natural gas for use as a compressed fuel for vehicles — Part 1: Designation of the quality
  2. DIN 51624 - Automotive fuels - Compressed natural gas - Requirements and test methods
  3. SAE J1616 - Recommended Practice for Compressed Natural Gas Vehicle Fuel

Dew Point

Dew Point Standards

Standards for calculating water and hydrocarbon dew points of natural gas.

Table of Contents


Overview

Dew point specifications are critical for:

Available Implementations:


Water Dew Point (ISO 18453)

Standard

ISO 18453:2004 - Natural gas — Correlation between water content and water dew point

Purpose

Calculate the temperature at which water vapor in natural gas begins to condense at a given pressure.

Implementation

Class: Draft_ISO18453

Uses the GERG-water equation of state which is specifically designed for water in natural gas systems.

Constructor

import neqsim.standards.gasquality.Draft_ISO18453;

// Create standard from any fluid
Draft_ISO18453 waterDewPoint = new Draft_ISO18453(thermoSystem);

How It Works

  1. Converts input fluid to GERG-water EoS if not already
  2. Sets pressure to reference pressure (default 70 bar)
  3. Performs water dew point temperature flash
  4. Returns temperature where water just begins to condense

Key Methods

Method Description
calculate() Perform dew point calculation
getValue("dewPointTemperature") Get dew point in °C
getValue("pressure") Get reference pressure in bar
setReferencePressure(P) Set reference pressure
isOnSpec() Check against sales contract specification

Example

import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.standards.gasquality.Draft_ISO18453;

// Natural gas with water
SystemInterface wetGas = new SystemSrkCPAstatoil(273.15 + 20, 50.0);
wetGas.addComponent("methane", 0.90);
wetGas.addComponent("ethane", 0.05);
wetGas.addComponent("propane", 0.02);
wetGas.addComponent("CO2", 0.02);
wetGas.addComponent("water", 100e-6);  // 100 ppm water
wetGas.setMixingRule("CPA_Statoil");

// Calculate water dew point
Draft_ISO18453 iso18453 = new Draft_ISO18453(wetGas);
iso18453.setReferencePressure(70.0);  // 70 bar reference
iso18453.calculate();

double wdp = iso18453.getValue("dewPointTemperature");
System.out.printf("Water Dew Point at 70 bar = %.1f °C%n", wdp);

Specification Checking

// Check against contract specification
iso18453.getSalesContract().setWaterDewPointTemperature(-8.0);  // Max -8°C

if (iso18453.isOnSpec()) {
    System.out.println("Gas meets water dew point specification");
} else {
    System.out.println("Gas FAILS water dew point specification");
}

Hydrocarbon Dew Point

Purpose

Calculate the temperature at which hydrocarbon liquids begin to condense from natural gas (cricondentherm).

Implementation

Class: BestPracticeHydrocarbonDewPoint

Uses SRK equation of state with Peneloux volume correction (mixing rule 2) for hydrocarbon phase behavior.

Constructor

import neqsim.standards.gasquality.BestPracticeHydrocarbonDewPoint;

// Create from any fluid (water is automatically removed)
BestPracticeHydrocarbonDewPoint hcDewPoint = 
    new BestPracticeHydrocarbonDewPoint(thermoSystem);

How It Works

  1. Creates new SRK-EoS system from input (excludes water)
  2. Sets reference pressure (default 50 bar)
  3. Performs dew point temperature flash
  4. Returns hydrocarbon dew point temperature

Key Methods

Method Description
calculate() Perform dew point calculation
getValue("hydrocarbondewpointTemperature") Get dew point in °C
getValue("pressure") Get reference pressure in bar
isOnSpec() Check against specification

Example

import neqsim.thermo.system.SystemSrkEos;
import neqsim.standards.gasquality.BestPracticeHydrocarbonDewPoint;

// Rich natural gas
SystemInterface richGas = new SystemSrkEos(273.15 + 20, 50.0);
richGas.addComponent("methane", 0.85);
richGas.addComponent("ethane", 0.06);
richGas.addComponent("propane", 0.03);
richGas.addComponent("n-butane", 0.02);
richGas.addComponent("n-pentane", 0.01);
richGas.addComponent("n-hexane", 0.005);
richGas.addComponent("n-heptane", 0.003);
richGas.addComponent("n-octane", 0.002);
richGas.addComponent("nitrogen", 0.02);
richGas.setMixingRule("classic");

// Calculate hydrocarbon dew point
BestPracticeHydrocarbonDewPoint hcDP = new BestPracticeHydrocarbonDewPoint(richGas);
hcDP.calculate();

double hcdp = hcDP.getValue("hydrocarbondewpointTemperature");
System.out.printf("Hydrocarbon Dew Point at 50 bar = %.1f °C%n", hcdp);

Multiple Pressures

// Calculate HCDP curve at multiple pressures
double[] pressures = {20, 30, 40, 50, 60, 70, 80};

System.out.println("Pressure (bar) | HC Dew Point (°C)");
System.out.println("---------------|-----------------");

for (double P : pressures) {
    richGas.setPressure(P);
    BestPracticeHydrocarbonDewPoint hcDP = new BestPracticeHydrocarbonDewPoint(richGas);
    hcDP.calculate();
    double hcdp = hcDP.getValue("hydrocarbondewpointTemperature");
    System.out.printf("%14.0f | %16.1f%n", P, hcdp);
}

Usage Examples

Combined Water and HC Dew Points

import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.standards.gasquality.Draft_ISO18453;
import neqsim.standards.gasquality.BestPracticeHydrocarbonDewPoint;

// Create wet gas with heavy ends
SystemInterface gas = new SystemSrkCPAstatoil(273.15 + 20, 70.0);
gas.addComponent("methane", 0.88);
gas.addComponent("ethane", 0.05);
gas.addComponent("propane", 0.02);
gas.addComponent("n-butane", 0.01);
gas.addComponent("n-pentane", 0.005);
gas.addComponent("n-hexane", 0.002);
gas.addComponent("CO2", 0.02);
gas.addComponent("water", 50e-6);
gas.setMixingRule("CPA_Statoil");

// Water dew point
Draft_ISO18453 waterDP = new Draft_ISO18453(gas);
waterDP.setReferencePressure(70.0);
waterDP.calculate();
double wdp = waterDP.getValue("dewPointTemperature");

// Hydrocarbon dew point
BestPracticeHydrocarbonDewPoint hcDP = new BestPracticeHydrocarbonDewPoint(gas);
hcDP.calculate();
double hcdp = hcDP.getValue("hydrocarbondewpointTemperature");

System.out.println("=== Dew Point Analysis ===");
System.out.printf("Water Dew Point (at 70 bar): %.1f °C%n", wdp);
System.out.printf("Hydrocarbon Dew Point (at 50 bar): %.1f °C%n", hcdp);

Effect of Water Content on WDP

// Analyze water dew point vs water content
double[] waterContents = {10, 20, 50, 100, 200, 500};  // ppm

System.out.println("Water Content (ppm) | Water Dew Point (°C)");
System.out.println("--------------------|--------------------");

for (double ppm : waterContents) {
    SystemInterface gas = new SystemSrkCPAstatoil(273.15 + 20, 70.0);
    gas.addComponent("methane", 0.95);
    gas.addComponent("ethane", 0.03);
    gas.addComponent("CO2", 0.01);
    gas.addComponent("water", ppm * 1e-6);
    gas.setMixingRule("CPA_Statoil");

    Draft_ISO18453 waterDP = new Draft_ISO18453(gas);
    waterDP.setReferencePressure(70.0);
    waterDP.calculate();
    double wdp = waterDP.getValue("dewPointTemperature");

    System.out.printf("%19.0f | %19.1f%n", ppm, wdp);
}

Comparison of Methods

Water Dew Point Approaches

Aspect ISO 18453 (GERG-water) CPA-EoS
Model GERG-water specific General CPA
Accuracy Optimized for NG Good general accuracy
Speed Fast Moderate
Water association Empirical Explicit

Hydrocarbon Dew Point Approaches

Aspect Best Practice (SRK) PR-EoS GERG-2004
Heavy ends Good Good Limited
Accuracy Typical ±2-3°C Typical ±2-3°C Best for lean gas
Speed Fast Fast Moderate

Typical Specifications

European Gas Specifications

Parameter Typical Limit
Water dew point < -8°C at 70 bar
HC dew point < -2°C at 1-70 bar

US Pipeline Specifications

Parameter Typical Limit
Water dew point < -7°C (20°F) at max operating P
HC dew point < -4°C (25°F) at cricondenbar

Accuracy Considerations

Factors Affecting Water Dew Point

Factors Affecting HC Dew Point

Recommendations

  1. Use CPA or GERG-water for water dew point
  2. Characterize C6+ fraction carefully for HC dew point
  3. Report dew point with reference pressure
  4. Consider measurement at multiple pressures for HC dew point curve

References

  1. ISO 18453:2004 - Natural gas — Correlation between water content and water dew point
  2. Folas, G.K., et al. (2007). High-pressure vapor-liquid equilibria of systems containing ethylene glycol, water and methane. Fluid Phase Equilibria.
  3. ISO 23874:2006 - Natural gas — Gas chromatographic requirements for hydrocarbon dewpoint calculation
  4. GERG Technical Monograph TM14 (2007) - The GERG-2004 Wide-Range Equation of State for Natural Gases

ASTM D6377

ASTM D6377 - Reid Vapor Pressure

ASTM D6377 provides methods for determining vapor pressure of crude oil and petroleum products.

Table of Contents


Overview

Standard: ASTM D6377 - Standard Test Method for Determination of Vapor Pressure of Crude Oil: VPCRx (Expansion Method)

Purpose: Determine the vapor pressure of crude oil and condensates for:

Class: Standard_ASTM_D6377


Vapor Pressure Definitions

True Vapor Pressure (TVP)

The equilibrium pressure of vapor above a liquid at a specified temperature when vapor/liquid ratio approaches zero.

$$TVP = P_{bubble}(T)$$

Reid Vapor Pressure (RVP)

The vapor pressure measured at 100°F (37.8°C) in a standardized apparatus with vapor/liquid volume ratio of 4:1.

VPCR4 (Vapor Pressure at V/L = 4)

The pressure at which 80% by volume is vapor at 37.8°C (100°F).

VPCR Relationship

Different VPCR ratios are used in various standards:


Implementation

Constructor

import neqsim.standards.oilquality.Standard_ASTM_D6377;

// Create standard from fluid
Standard_ASTM_D6377 rvpStandard = new Standard_ASTM_D6377(thermoSystem);

Available Methods

Method Name Description
VPCR4 Vapor pressure at V/L = 4 (default)
VPCR4_no_water VPCR4 excluding water
RVP_ASTM_D6377 RVP correlation from D6377
RVP_ASTM_D323_73_79 RVP per D323 (1973/1979)
RVP_ASTM_D323_82 RVP per D323 (1982)

Key Methods

Method Description
calculate() Perform vapor pressure calculations
getValue("RVP", "bara") Get Reid vapor pressure
getValue("TVP", "bara") Get true vapor pressure
getValue("VPCR4", "bara") Get VPCR4
setMethodRVP(method) Select RVP calculation method
getMethodRVP() Get current method

Usage Examples

Basic RVP Calculation

import neqsim.thermo.system.SystemSrkEos;
import neqsim.standards.oilquality.Standard_ASTM_D6377;

// Create condensate/crude composition
SystemInterface crude = new SystemSrkEos(273.15 + 15, 1.01325);
crude.addComponent("methane", 0.01);
crude.addComponent("ethane", 0.02);
crude.addComponent("propane", 0.04);
crude.addComponent("n-butane", 0.06);
crude.addComponent("i-butane", 0.03);
crude.addComponent("n-pentane", 0.08);
crude.addComponent("i-pentane", 0.05);
crude.addComponent("n-hexane", 0.10);
crude.addTBPfraction("C7", 0.15, 100.0/1000.0, 0.72);
crude.addTBPfraction("C10", 0.20, 142.0/1000.0, 0.78);
crude.addTBPfraction("C20", 0.26, 282.0/1000.0, 0.85);
crude.setMixingRule("classic");

// Calculate RVP
Standard_ASTM_D6377 rvpStandard = new Standard_ASTM_D6377(crude);
rvpStandard.setMethodRVP("VPCR4");
rvpStandard.calculate();

// Get results
double tvp = rvpStandard.getValue("TVP", "bara");
double rvp = rvpStandard.getValue("RVP", "bara");
double vpcr4 = rvpStandard.getValue("VPCR4", "bara");

System.out.println("=== Vapor Pressure Results ===");
System.out.printf("True Vapor Pressure (TVP) = %.4f bara%n", tvp);
System.out.printf("Reid Vapor Pressure (RVP) = %.4f bara%n", rvp);
System.out.printf("VPCR4 = %.4f bara%n", vpcr4);

Comparing Different RVP Methods

// Calculate using all available methods
String[] methods = {"VPCR4", "RVP_ASTM_D6377", "RVP_ASTM_D323_73_79", "RVP_ASTM_D323_82"};

System.out.println("Method                | RVP (bara)");
System.out.println("----------------------|----------");

for (String method : methods) {
    Standard_ASTM_D6377 std = new Standard_ASTM_D6377(crude);
    std.setMethodRVP(method);
    std.calculate();
    double rvp = std.getValue("RVP", "bara");
    System.out.printf("%-21s | %.4f%n", method, rvp);
}

Effect of Light Ends on RVP

// Analyze RVP sensitivity to light ends
double[] methaneContent = {0.0, 0.005, 0.01, 0.02, 0.05};

System.out.println("Methane (mol%) | RVP (bara)");
System.out.println("---------------|----------");

for (double ch4 : methaneContent) {
    SystemInterface fluid = new SystemSrkEos(273.15 + 15, 1.0);
    fluid.addComponent("methane", ch4);
    fluid.addComponent("ethane", 0.02);
    fluid.addComponent("propane", 0.04);
    fluid.addComponent("n-butane", 0.08);
    fluid.addComponent("n-pentane", 0.10);
    fluid.addTBPfraction("C7", 0.20, 100/1000.0, 0.72);
    fluid.addTBPfraction("C15", 0.56 - ch4, 200/1000.0, 0.80);
    fluid.setMixingRule("classic");

    Standard_ASTM_D6377 std = new Standard_ASTM_D6377(fluid);
    std.calculate();
    double rvp = std.getValue("RVP", "bara");

    System.out.printf("%14.1f | %.4f%n", ch4 * 100, rvp);
}

Wet vs Dry RVP

// Calculate with and without water
SystemInterface wetCrude = crude.clone();
wetCrude.addComponent("water", 0.01);  // 1% water

Standard_ASTM_D6377 wetStd = new Standard_ASTM_D6377(wetCrude);
wetStd.calculate();

double vpcr4Wet = wetStd.getValue("VPCR4", "bara");
double vpcr4Dry = wetStd.getValue("VPCR4_no_water", "bara");

System.out.printf("VPCR4 (with water) = %.4f bara%n", vpcr4Wet);
System.out.printf("VPCR4 (dry basis) = %.4f bara%n", vpcr4Dry);

Method Selection

VPCR4 (Default)

Best for:

RVP_ASTM_D6377

Correlation from ASTM D6377: $$RVP = 0.834 \times VPCR4$$

RVP_ASTM_D323_82

Correlation from ASTM D323 (1982 edition): $$RVP = \frac{0.752 \times (100 \times VPCR4) + 6.07}{100}$$

RVP_ASTM_D323_73_79

For comparison with historical data using D323 (1973/1979 editions). Uses VPCR4 without water contribution.


Correlations

TVP to RVP

Approximate relationship: $$RVP \approx 0.75 \times TVP + constant$$

The constant depends on crude composition.

Temperature Dependence

For estimation at temperatures other than 37.8°C:

$$\log_{10}(P_{vap}) = A - \frac{B}{T + C}$$

Antoine-type equation where A, B, C are crude-specific.

RVP Specifications

Product Typical RVP Limit
Crude oil (export) < 0.7 bara (10 psia)
Stabilized condensate < 0.5 bara (7 psia)
Gasoline (summer) < 0.62 bara (9 psi)
Gasoline (winter) < 0.90 bara (13 psi)

Technical Details

Calculation Procedure

  1. Set temperature to 37.8°C (100°F)
  2. Perform bubble point flash to get TVP
  3. Perform flash at vapor/liquid volume ratio = 4
  4. Apply correlation for RVP estimation

Reference Conditions

Parameter Value
Temperature 37.8°C (100°F)
V/L ratio 4:1 (80% vapor by volume)
Pressure Equilibrium

Equation of State

Uses SRK-EoS for phase equilibrium calculations.


Accuracy Considerations

Factors Affecting Accuracy

  1. Light end characterization - Accurate C1-C4 composition critical
  2. Heavy end representation - TBP fractions affect liquid volume
  3. Water content - Can significantly affect measured RVP
  4. Sample handling - Light end loss during sampling

Typical Uncertainty

Method Uncertainty
VPCR4 calculation ±0.02 bara
RVP correlation ±0.03-0.05 bara

Recommendations

  1. Ensure accurate light ends (C1-C5) analysis
  2. Use consistent method for comparison
  3. Report method used with results
  4. Consider water content effects

References

  1. ASTM D6377 - Standard Test Method for Determination of Vapor Pressure of Crude Oil: VPCRx (Expansion Method)
  2. ASTM D323 - Standard Test Method for Vapor Pressure of Petroleum Products (Reid Method)
  3. ASTM D5191 - Standard Test Method for Vapor Pressure of Petroleum Products and Liquid Fuels (Mini Method)
  4. API MPMS Chapter 8 - Sampling

Sales Contracts

Sales Contracts

The sales contract system enables specification verification and compliance checking for natural gas quality.

Table of Contents


Overview

Location: neqsim.standards.salescontract

Purpose:

Classes:


Architecture

Class Hierarchy

ContractInterface
    │
    └── BaseContract
            │
            ├── ArrayList<ContractSpecification>
            │       │
            │       └── StandardInterface (method)
            │
            └── Database connection (gascontractspecifications)

Workflow

1. Create Contract (from database or manually)
        ↓
2. Add Specifications (linked to standards)
        ↓
3. Run Compliance Check
        ↓
4. Generate Results Table
        ↓
5. Display/Export Results

Creating Contracts

From Database

import neqsim.standards.salescontract.BaseContract;
import neqsim.standards.salescontract.ContractInterface;

// Load contract from database by terminal and country
ContractInterface contract = new BaseContract(
    thermoSystem,    // Gas composition
    "Kaarstoe",      // Terminal name
    "Norway"         // Country
);

Programmatic Creation

// Create empty contract
ContractInterface contract = new BaseContract();

// Or with basic water dew point spec
ContractInterface contract = new BaseContract(thermoSystem);

Contract Specifications

ContractSpecification Class

Each specification contains:

Field Description
name Specification name
specification Description
country Country code
terminal Terminal/delivery point
standard StandardInterface method
minValue Minimum acceptable value
maxValue Maximum acceptable value
unit Unit of measurement
referenceTemperatureMeasurement Reference T for measurement
referenceTemperatureCombustion Reference T for combustion
referencePressure Reference pressure
comments Additional notes

Creating Specifications

import neqsim.standards.salescontract.ContractSpecification;
import neqsim.standards.gasquality.Draft_ISO18453;

// Create water dew point specification
StandardInterface waterDPMethod = new Draft_ISO18453(thermoSystem);

ContractSpecification waterSpec = new ContractSpecification(
    "Water Dew Point",           // Name
    "Maximum water dew point",   // Specification description
    "Norway",                    // Country
    "Kaarstoe",                  // Terminal
    waterDPMethod,               // Calculation method
    -20.0,                       // Minimum value
    -8.0,                        // Maximum value
    "°C",                        // Unit
    15.0,                        // Reference T measurement
    15.0,                        // Reference T combustion
    70.0,                        // Reference pressure (bar)
    "At 70 bar"                  // Comments
);

Available Standard Methods

Method Name Class Purpose
ISO18453 Draft_ISO18453 Water dew point
ISO6974 Standard_ISO6974 Gas composition
ISO6976 Standard_ISO6976 Calorific values, Wobbe
BestPracticeHydrocarbonDewPoint BestPracticeHydrocarbonDewPoint HC dew point
SulfurSpecificationMethod SulfurSpecificationMethod H2S and sulfur
UKspecifications UKspecifications_ICF_SI UK ICF/SI specs

Usage Examples

Basic Contract Check

import neqsim.thermo.system.SystemSrkCPAstatoil;
import neqsim.standards.salescontract.BaseContract;

// Create gas composition
SystemInterface gas = new SystemSrkCPAstatoil(273.15 + 15, 70.0);
gas.addComponent("methane", 0.90);
gas.addComponent("ethane", 0.05);
gas.addComponent("propane", 0.02);
gas.addComponent("CO2", 0.02);
gas.addComponent("nitrogen", 0.005);
gas.addComponent("water", 30e-6);  // 30 ppm water
gas.setMixingRule("CPA_Statoil");

// Load contract from database
BaseContract contract = new BaseContract(gas, "Kaarstoe", "Norway");

// Run compliance check
contract.runCheck();

// Get results
String[][] results = contract.getResultTable();
int numSpecs = contract.getSpecificationsNumber();

System.out.printf("Checked %d specifications%n", numSpecs);

// Display results
contract.display();

Custom Contract

import neqsim.standards.salescontract.*;
import neqsim.standards.gasquality.*;

// Create empty contract
BaseContract contract = new BaseContract();

// Add water dew point specification
Draft_ISO18453 waterMethod = new Draft_ISO18453(gas);
waterMethod.setReferencePressure(70.0);
ContractSpecification waterSpec = new ContractSpecification(
    "Water DP", "Water dew point max", "Export", "Platform",
    waterMethod, -100, -8, "°C", 15, 15, 70, ""
);
contract.addSpecification(waterSpec);

// Add Wobbe index specification
Standard_ISO6976 wobbeMethod = new Standard_ISO6976(gas, 15, 25, "volume");
ContractSpecification wobbeSpec = new ContractSpecification(
    "Wobbe Index", "Wobbe index range", "Export", "Platform",
    wobbeMethod, 47300, 51500, "kJ/m³", 15, 25, 1.01325, ""
);
contract.addSpecification(wobbeSpec);

// Add GCV specification
ContractSpecification gcvSpec = new ContractSpecification(
    "GCV", "Gross calorific value", "Export", "Platform",
    wobbeMethod, 37500, 43000, "kJ/m³", 15, 25, 1.01325, ""
);
contract.addSpecification(gcvSpec);

// Run check
contract.runCheck();

Attaching Contract to Standard

import neqsim.standards.gasquality.Draft_ISO18453;

// Create standard with contract
Draft_ISO18453 waterDP = new Draft_ISO18453(gas);
waterDP.setSalesContract(contract);

// Calculate
waterDP.calculate();

// Check specification compliance
if (waterDP.isOnSpec()) {
    System.out.println("PASS: Water dew point within specification");
} else {
    System.out.println("FAIL: Water dew point out of specification");
}

Contract Results Table

// After running check
String[][] results = contract.getResultTable();

// Results table structure:
// [row][0] = Specification name
// [row][1] = Measured value
// [row][2] = Min specification
// [row][3] = Max specification
// [row][4] = Unit
// [row][5] = Pass/Fail status

System.out.println("Specification | Value | Min | Max | Unit | Status");
System.out.println("--------------|-------|-----|-----|------|-------");

for (int i = 0; i < contract.getSpecificationsNumber(); i++) {
    System.out.printf("%13s | %5s | %3s | %3s | %4s | %6s%n",
        results[i][0], results[i][1], results[i][2],
        results[i][3], results[i][4], results[i][5]);
}

Database Integration

Database Table Structure

Table: gascontractspecifications

Column Description
NAME Specification name
SPECIFICATION Description
COUNTRY Country code
TERMINAL Delivery point
METHOD Calculation method name
MINVALUE Minimum value
MAXVALUE Maximum value
UNIT Unit string
ReferenceTdegC Reference temperature
ReferencePbar Reference pressure
Comments Additional notes

Method Name Mapping

Database Method Class
ISO18453 Draft_ISO18453
ISO6974 Standard_ISO6974
ISO6976 Standard_ISO6976
BestPracticeHydrocarbonDewPoint BestPracticeHydrocarbonDewPoint
SulfurSpecificationMethod SulfurSpecificationMethod
UKspecifications UKspecifications_ICF_SI

Querying Contracts

// Contracts are loaded by terminal and country
BaseContract norwegianContract = new BaseContract(gas, "Kaarstoe", "Norway");
BaseContract ukContract = new BaseContract(gas, "StFergus", "UK");
BaseContract belgianContract = new BaseContract(gas, "Zeebrugge", "Belgium");

Typical Specifications

Norwegian Pipeline Gas

Parameter Min Max Unit Reference
Water dew point - -8 °C 70 bar
HC dew point - -2 °C cricondentherm
GCV 36.5 44.0 MJ/Sm³ 15°C/15°C
Wobbe index 47.0 52.0 MJ/Sm³ 15°C/15°C
CO₂ - 2.5 mol% -
H₂S - 5 mg/Sm³ -

UK NTS Gas

Parameter Min Max Unit Reference
GCV 36.9 42.3 MJ/m³ 15°C/15°C
Wobbe index 47.2 51.41 MJ/m³ 15°C/15°C
ICF - 0.48 - -
SI - 0.60 - -

Best Practices

Contract Management

  1. Store specifications in database for consistency
  2. Version control specification changes
  3. Document reference conditions clearly
  4. Include measurement uncertainty allowances

Compliance Checking

  1. Run checks before custody transfer
  2. Log all specification results
  3. Alert on near-limit values
  4. Track specification trends over time

Integration

  1. Link to online analyzers for real-time checking
  2. Interface with SCADA systems
  3. Generate automatic compliance reports
  4. Archive historical compliance data

References

  1. EASEE-gas Common Business Practice 2005-001 - Harmonisation of Natural Gas Quality
  2. EN 16726 - Gas infrastructure - Quality of gas - Group H
  3. ISO 13686 - Natural gas — Quality designation
  4. GTS (Dutch) - Gas quality specifications
  5. National Grid (UK) - Gas Ten Year Statement

Chapter 40: Future Infrastructure

Future Infrastructure

NeqSim Future Infrastructure Overview

This document provides an overview of the foundational infrastructure added to NeqSim to support the future of process simulation.

Vision

The future of process simulation involves:

  1. Living Digital Twins - Models that evolve with assets from concept to decommissioning
  2. Real-Time Advisory - Predictive simulations supporting operations
  3. AI + Physics Integration - ML efficiency with thermodynamic rigor
  4. Safety Analysis - Automated what-if scenario generation
  5. Rapid Screening - Cloud-scale concept evaluation
  6. Sustainability - Emissions tracking and reporting
  7. Composable Architecture - Modular, extensible design
  8. Engineer Empowerment - High-level APIs for non-programmers

Module Overview

neqsim/process/
├── processmodel/
│   └── lifecycle/           # Living Digital Twins
│       ├── ProcessSystemState.java
│       └── ModelMetadata.java
├── advisory/                # Real-Time Advisory
│   └── PredictionResult.java
├── ml/
│   └── surrogate/          # AI + Physics Integration
│       ├── SurrogateModelRegistry.java
│       └── PhysicsConstraintValidator.java
├── safety/
│   └── scenario/           # Safety Analysis
│       └── AutomaticScenarioGenerator.java
├── sustainability/         # Emissions Tracking
│   └── EmissionsTracker.java
└── util/
    └── optimization/       # Rapid Screening
        └── BatchStudy.java

Quick Start

Lifecycle Management

// Export model state for version control
ProcessSystemState state = process.exportState();
state.setVersion("1.0.0");
state.saveToFile("model_v1.0.0.json");

// Track model lifecycle
ModelMetadata metadata = state.getMetadata();
metadata.setLifecyclePhase(LifecyclePhase.OPERATION);
metadata.recordValidation("Matched field data", "TEST-001");

Emissions Tracking

// Calculate emissions (includes Expander power generation)
EmissionsReport report = process.getEmissions();
System.out.println(report.getSummary());

// With location-specific grid factor
EmissionsReport norwayReport = process.getEmissions(0.05);

// Export to JSON for external tools
report.exportToJSON("emissions.json");
String json = report.toJson();

Safety Scenarios

// Generate what-if scenarios
AutomaticScenarioGenerator generator = new AutomaticScenarioGenerator(process);
generator.addFailureModes(FailureMode.COOLING_LOSS, FailureMode.COMPRESSOR_TRIP);

// Run all scenarios automatically
List<ScenarioRunResult> results = generator.runAllSingleFailures();

// Get execution summary
String summary = generator.summarizeResults(results);
System.out.println(summary);

// Or run manually
List<ProcessSafetyScenario> scenarios = generator.generateSingleFailures();
for (ProcessSafetyScenario scenario : scenarios) {
    ProcessSystem copy = process.copy();
    scenario.applyTo(copy);
    copy.run();
    // Analyze...
}

Batch Studies

// Run parameter study with extended parameter paths
BatchStudy study = BatchStudy.builder(process)
    .vary("compressor.outletPressure", 30.0, 80.0, 6)
    .vary("heater.outletTemperature", 50.0, 150.0, 5)
    .addObjective("power", Objective.MINIMIZE, p -> p.getTotalPower())
    .addObjective("emissions", Objective.MINIMIZE, p -> p.getTotalCO2Emissions())
    .parallelism(8)
    .build();

BatchStudyResult result = study.run();

// Export results
result.exportToCSV("results.csv");
result.exportToJSON("results.json");

// Pareto front analysis
List<CaseResult> paretoFront = result.getParetoFront("power", "emissions");

ML Integration

// Register surrogate model
SurrogateModelRegistry.getInstance()
    .register("flash-calc", myNeuralNetwork);

// Validate AI actions
PhysicsConstraintValidator validator = new PhysicsConstraintValidator(process);
ValidationResult check = validator.validate(aiProposedAction);
if (!check.isValid()) {
    System.out.println("Rejected: " + check.getRejectionReason());
}

Advisory Systems

// Create prediction result
PredictionResult prediction = new PredictionResult(Duration.ofHours(2));
prediction.addPredictedValue("pressure", new PredictedValue(50.0, 2.5, "bara"));

if (prediction.hasViolations()) {
    System.out.println(prediction.getAdvisoryRecommendation());
}

Documentation

Detailed documentation for each module:

Module Documentation
Lifecycle Management lifecycle/README.md
Sustainability sustainability/README.md
Advisory Systems advisory/README.md
ML Integration ml/README.md
Safety Scenarios safety/README.md
Batch Studies optimization/README.md

Architecture Principles

1. Backward Compatibility

All new features are additive. Existing code continues to work unchanged.

2. Progressive Enhancement

Start with simple APIs, access advanced features when needed.

3. Physics First

ML models include automatic fallback to physics calculations.

4. Safety by Design

All AI actions are validated against physical constraints.

5. Sustainability Built-In

Emissions tracking is first-class, not an afterthought.

Integration Points

External ML Platforms

Real-Time Systems

Cloud Platforms

Future Roadmap

Phase 1 ✅

Phase 2 (Current) ✅

Phase 3 (Planned)

Phase 4 (Vision)

Contributing

When extending these modules:

  1. Follow existing patterns for consistency
  2. Include comprehensive JavaDoc
  3. Add unit tests for new functionality
  4. Update documentation

API Reference

Future Infrastructure API Reference

Quick reference for the future infrastructure APIs added to NeqSim.

ProcessSystem Convenience Methods

New methods added directly to ProcessSystem for easy access:

State Management

// Export current state
ProcessSystemState state = process.exportState();

// Save state to file
process.exportStateToFile("checkpoint.json");

// Load and apply state from file
process.loadStateFromFile("checkpoint.json");

Emissions Tracking

// Calculate emissions with default grid factor (0.4 kg CO2/kWh)
EmissionsReport report = process.getEmissions();

// Calculate emissions with custom grid factor
EmissionsReport norwayReport = process.getEmissions(0.05);

// Get total CO2 emissions directly (kg/hr)
double totalCO2 = process.getTotalCO2Emissions();

Safety Scenarios

// Generate single-failure scenarios
List<ProcessSafetyScenario> scenarios = process.generateSafetyScenarios();

// Generate combination scenarios (up to n simultaneous failures)
List<ProcessSafetyScenario> combinations = process.generateCombinationScenarios(2);

Batch Studies

// Create batch study builder
BatchStudy.Builder builder = process.createBatchStudy();

ProcessSystemState

State snapshot for checkpointing and version control.

Factory Method

ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);

Configuration

state.setVersion("1.2.3");
state.setDescription("Post-tuning checkpoint");
state.setCreatedBy("engineer@company.com");

Persistence

// Save to file
state.saveToFile("model_v1.2.3.json");

// Load from file
ProcessSystemState loaded = ProcessSystemState.loadFromFile("model_v1.2.3.json");

// Validate integrity
boolean valid = loaded.validateIntegrity();

Accessors

String version = state.getVersion();
String name = state.getProcessName();
Instant timestamp = state.getTimestamp();
String json = state.toJson();

Application

// Create new ProcessSystem from state
ProcessSystem restored = state.toProcessSystem();

// Apply state to existing ProcessSystem
state.applyTo(existingProcess);

ModelMetadata

Lifecycle and calibration tracking.

Lifecycle Phases

public enum LifecyclePhase {
    CONCEPT,        // Early screening
    DESIGN,         // Detailed engineering
    COMMISSIONING,  // Construction/startup
    OPERATION,      // Live digital twin
    LATE_LIFE,      // Decommissioning
    ARCHIVED        // No longer active
}

Calibration Status

public enum CalibrationStatus {
    UNCALIBRATED,
    CALIBRATED,
    IN_PROGRESS,
    FRESHLY_CALIBRATED,
    NEEDS_RECALIBRATION
}

Usage

ModelMetadata metadata = new ModelMetadata();
metadata.setAssetId("PLATFORM-A");
metadata.setAssetName("Gas Processing Platform A");
metadata.setLifecyclePhase(LifecyclePhase.OPERATION);
metadata.setResponsibleEngineer("jane.doe@company.com");

// Record validation
metadata.recordValidation("Matched well test", "TEST-001");

// Record modification
metadata.recordModification("Updated compressor curves");

// Update calibration
metadata.updateCalibration(CalibrationStatus.FRESHLY_CALIBRATED, 0.02);

// Check revalidation need
boolean needsRevalidation = metadata.needsRevalidation(90); // days

EmissionsTracker

CO2 equivalent emissions tracking.

Constructor

EmissionsTracker tracker = new EmissionsTracker(process);

Configuration

tracker.setGridEmissionFactor(0.05);  // kg CO2/kWh (Norway)

Calculation

EmissionsReport report = tracker.calculateEmissions();

Emission Categories

Category Description
COMPRESSION Power consumed by compressors
EXPANSION Power generated by expanders (negative)
PUMPING Power consumed by pumps
HEATING Power consumed by electric heaters
COOLING Power consumed by coolers
FLARING Direct CO2 from flaring
VENTING Direct methane/CO2 emissions

EmissionsReport Methods

// Total emissions
double kgPerHr = report.getTotalCO2e("kg/hr");
double tonPerYr = report.getTotalCO2e("ton/yr");

// Power consumption
double kW = report.getTotalPower("kW");
double MW = report.getTotalPower("MW");

// Export
report.exportToCSV("emissions.csv");
report.exportToJSON("emissions.json");
String json = report.toJson();  // Get as JSON string
String summary = report.getSummary();

PredictionResult

Look-ahead prediction output.

Constructor

PredictionResult result = new PredictionResult(
    Duration.ofHours(2),  // horizon
    "Base Case"           // scenario name
);

Adding Predictions

result.addPredictedValue(
    "separator.pressure",
    new PredictedValue(52.5, 2.1, "bara")  // mean, stddev, unit
);

PredictedValue Constructors

// With standard deviation
PredictedValue value = new PredictedValue(50.0, 2.5, "bara");

// With explicit bounds
PredictedValue value = new PredictedValue(50.0, 45.0, 55.0, "bara", 0.95);

// Deterministic
PredictedValue value = PredictedValue.deterministic(50.0, "bara");

Violation Handling

// Add violation
result.addViolation(new ConstraintViolation(...));

// Check for violations
if (result.hasViolations()) {
    String summary = result.getViolationSummary();
    String advice = result.getAdvisoryRecommendation();
}

Status

public enum PredictionStatus {
    SUCCESS,
    WARNING,
    FAILED,
    DATA_QUALITY_ISSUE
}

result.setStatus(PredictionStatus.SUCCESS);

SurrogateModelRegistry

ML model management.

Singleton Access

SurrogateModelRegistry registry = SurrogateModelRegistry.getInstance();

Registration

registry.register("flash-model", surrogateModel);
registry.register("flash-model", surrogateModel, metadata);

Prediction

// Direct prediction
double[] result = registry.get("flash-model").orElseThrow().predict(input);

// With automatic fallback
double[] result = registry.predictWithFallback(
    "flash-model",
    input,
    this::physicsCalculation
);

Management

registry.saveModel("flash-model", "models/flash.ser");
registry.loadModel("flash-model", "models/flash.ser");
Optional<SurrogateMetadata> meta = registry.getMetadata("flash-model");

PhysicsConstraintValidator

AI action validation.

Constructor

PhysicsConstraintValidator validator = new PhysicsConstraintValidator(process);

Adding Limits

validator.addPressureLimit("separator", 10.0, 80.0, "bara");
validator.addTemperatureLimit("heater-outlet", 0.0, 300.0, "C");
validator.addFlowLimit("feed", 0.0, 1000.0, "kg/hr");

Configuration

validator.setMassBalanceTolerance(0.01);   // 1%
validator.setEnergyBalanceTolerance(0.05); // 5%
validator.setEnforceMassBalance(true);
validator.setEnforceEnergyBalance(true);

Validation

Map<String, Double> proposedAction = new HashMap<>();
proposedAction.put("heater.duty", 5000000.0);

ValidationResult result = validator.validate(proposedAction);

if (result.isValid()) {
    // Safe to apply
} else {
    String reason = result.getRejectionReason();
    List<ConstraintViolation> violations = result.getViolations();
}

// Validate current state
ValidationResult currentState = validator.validateCurrentState();

AutomaticScenarioGenerator

Safety scenario generation and execution.

Constructor

AutomaticScenarioGenerator generator = new AutomaticScenarioGenerator(process);

Failure Mode Configuration

// Add specific modes
generator.addFailureModes(
    FailureMode.COOLING_LOSS,
    FailureMode.VALVE_STUCK_CLOSED
);

// Or enable all
generator.enableAllFailureModes();

Generation

// Single failures
List<ProcessSafetyScenario> single = generator.generateSingleFailures();

// Combinations
List<ProcessSafetyScenario> combos = generator.generateCombinations(2);

Scenario Execution

// Run all single-failure scenarios
List<ScenarioRunResult> results = generator.runAllSingleFailures();

// Run specific scenarios
List<ScenarioRunResult> results = generator.runScenarios(scenarios);

// Get execution summary
String summary = generator.summarizeResults(results);

ScenarioRunResult

ScenarioRunResult result = results.get(0);
boolean success = result.isSuccessful();
String error = result.getErrorMessage();
Map<String, Double> values = result.getResultValues();
long timeMs = result.getExecutionTimeMs();

Analysis

List<EquipmentFailure> failures = generator.getIdentifiedFailures();
String summary = generator.getFailureModeSummary();

BatchStudy

Parallel parameter studies.

Builder Pattern

BatchStudy study = BatchStudy.builder(baseCase)
    .name("ParameterStudy")
    .vary("pressure", 20.0, 80.0, 7)
    .vary("temperature", 50.0, 100.0, 5)
    .addObjective("power", Objective.MINIMIZE, p -> getPower(p))
    .addObjective("emissions", Objective.MINIMIZE, p -> getEmissions(p))
    .parallelism(8)
    .stopOnFailure(false)
    .build();

Supported Parameter Paths

Property Equipment Types
duty Heater, Cooler
outletPressure Valve, Compressor, Pump
outletTemperature Heater, Cooler
percentValveOpening, cv Valve
polytropicEfficiency, isentropicEfficiency Compressor
temperature, flowRate Stream
internalDiameter Separator

Execution

BatchStudyResult result = study.run();

Result Analysis

int total = result.getTotalCases();
int completed = result.getCompletedCases();
int failed = result.getFailedCases();
Duration runtime = result.getTotalRuntime();

CaseResult best = result.getBestCase("power");
List<CaseResult> successful = result.getSuccessfulCases();

// Export results
result.exportToCSV("results.csv");
result.exportToJSON("results.json");
String json = result.toJson();

// Pareto front analysis
List<CaseResult> pareto = result.getParetoFront("power", "emissions");

Package Summary

Package Classes Purpose
lifecycle ProcessSystemState, ModelMetadata State management, versioning
sustainability EmissionsTracker, EmissionsReport CO2e tracking
advisory PredictionResult, PredictedValue Look-ahead predictions
ml.surrogate SurrogateModelRegistry, PhysicsConstraintValidator ML integration
safety.scenario AutomaticScenarioGenerator Safety scenario generation
util.optimization BatchStudy, BatchStudyResult Parallel studies

Chapter 41: Integration & APIs

Digital Twins

Integration Guides

Documentation for integrating NeqSim with external systems and platforms.


Overview

This folder contains guides for integrating NeqSim with machine learning platforms, model predictive control systems, real-time data systems, and P&ID tools.


Documentation Index

Machine Learning and AI

Document Description
ai_validation_framework.md AI-friendly validation framework with structured error handling
ai_platform_integration.md AI platform integration guide
ml_integration.md Machine learning integration

Control Systems

Document Description
mpc_integration.md Model Predictive Control integration
neqsim_industrial_mpc_integration.md Industrial MPC integration

Real-Time Systems

Document Description
REAL_TIME_INTEGRATION_GUIDE.md Real-time systems integration
QRA_INTEGRATION_GUIDE.md Quantitative Risk Assessment integration

P&ID and Design

Document Description
dexpi-reader.md DEXPI P&ID import/export and diagram generation

AI Platform Integration

AI Platform Integration Guide

This document describes the NeqSim extensions designed for integration with AI-based production optimization platforms and real-time digital twin systems.

Overview

Modern AI-based production optimization platforms typically require:

NeqSim provides dedicated packages to support these requirements.

Table of Contents

  1. Streaming Data
  2. Virtual Flow Meters
  3. Soft Sensors
  4. Uncertainty Quantification
  5. ML Integration
  6. Online Calibration
  7. Well Allocation
  8. Event System
  9. Data Export

Streaming Data

Package: neqsim.process.streaming

The streaming package enables real-time data publishing from NeqSim simulations to external platforms.

Key Classes

TimestampedValue

Represents a value with timestamp, unit, and quality indicator.

import neqsim.process.streaming.TimestampedValue;

// Create a timestamped value
TimestampedValue value = new TimestampedValue(
    100.5,                          // value
    "bara",                         // unit
    Instant.now(),                  // timestamp
    TimestampedValue.Quality.GOOD   // quality
);

// Access properties
double val = value.getValue();
String unit = value.getUnit();
Instant ts = value.getTimestamp();
TimestampedValue.Quality quality = value.getQuality();

Quality Levels:

ProcessDataPublisher

Publishes process data from a ProcessSystem with subscription support.

import neqsim.process.streaming.ProcessDataPublisher;
import neqsim.process.processmodel.ProcessSystem;

// Create publisher linked to process system
ProcessSystem process = new ProcessSystem();
// ... add equipment ...
ProcessDataPublisher publisher = new ProcessDataPublisher(process);

// Subscribe to updates
publisher.subscribeToUpdates("Inlet.pressure", value -> {
    System.out.println("Pressure: " + value.getValue() + " " + value.getUnit());
});

// Publish current state
publisher.publishFromProcessSystem();

// Get state vector for ML models
double[] stateVector = publisher.getStateVector();

StreamingDataInterface

Interface for custom streaming implementations:

public interface StreamingDataInterface {
    void subscribeToUpdates(String tagId, Consumer<TimestampedValue> callback);
    void publishBatch(Map<String, TimestampedValue> values);
    double[] getStateVector();
    List<TimestampedValue> getHistory(String tagId, Duration period);
}

Virtual Flow Meters

Package: neqsim.process.measurementdevice.vfm

Virtual Flow Meters calculate multiphase flow rates using thermodynamic models when physical meters are unavailable or unreliable.

VirtualFlowMeter

import neqsim.process.measurementdevice.vfm.VirtualFlowMeter;
import neqsim.process.measurementdevice.vfm.VFMResult;

// Create VFM from a stream
StreamInterface wellStream = new Stream("Well-A", fluid);
wellStream.run();

VirtualFlowMeter vfm = new VirtualFlowMeter("VFM-Well-A", wellStream);

// Calculate flow rates with uncertainty
VFMResult result = vfm.calculate();

// Access results
double gasRate = result.getGasFlowRate();      // Sm3/day
double oilRate = result.getOilFlowRate();      // Sm3/day
double waterRate = result.getWaterFlowRate();  // Sm3/day
double gor = result.getGOR();                  // Sm3/Sm3
double waterCut = result.getWaterCut();        // fraction

// Get uncertainties
UncertaintyBounds gasUncertainty = result.getGasFlowRateUncertainty();
double lower95 = gasUncertainty.getLower95();
double upper95 = gasUncertainty.getUpper95();

Calibration

VFMs can be calibrated using well test data:

// Create calibration from well test
VFMCalibration calibration = new VFMCalibration();
calibration.setWellTestGasRate(50000);   // Sm3/day
calibration.setWellTestOilRate(500);     // Sm3/day
calibration.setWellTestWaterRate(100);   // Sm3/day
calibration.setWellTestDate(Instant.now());

vfm.setCalibration(calibration);

VFMResult Builder

VFMResult result = VFMResult.builder()
    .gasFlowRate(45000)
    .oilFlowRate(450)
    .waterFlowRate(95)
    .gasFlowRateUncertainty(new UncertaintyBounds(42000, 48000, 2000))
    .timestamp(Instant.now())
    .quality(VFMResult.Quality.GOOD)
    .build();

Soft Sensors

Package: neqsim.process.measurementdevice.vfm

Soft sensors estimate unmeasured properties from available measurements using thermodynamic models.

SoftSensor

import neqsim.process.measurementdevice.vfm.SoftSensor;

// Create soft sensor for GOR estimation
SoftSensor gorSensor = new SoftSensor("GOR-Sensor", stream, SoftSensor.PropertyType.GOR);

// Get estimated value
double gor = gorSensor.getMeasuredValue();
String unit = gorSensor.getUnit();

// Get sensitivity to input changes
double sensitivity = gorSensor.getSensitivity("pressure");

Available Property Types:


Uncertainty Quantification

Package: neqsim.process.util.uncertainty

Propagates measurement uncertainties through thermodynamic calculations.

UncertaintyAnalyzer

import neqsim.process.util.uncertainty.UncertaintyAnalyzer;
import neqsim.process.util.uncertainty.UncertaintyResult;

// Create analyzer for a process system
ProcessSystem process = new ProcessSystem();
// ... configure process ...

UncertaintyAnalyzer analyzer = new UncertaintyAnalyzer(process);

// Define input uncertainties (relative)
analyzer.setInputUncertainty("inlet_pressure", 0.01);      // 1%
analyzer.setInputUncertainty("inlet_temperature", 0.005);  // 0.5%
analyzer.setInputUncertainty("inlet_flowrate", 0.02);      // 2%

// Perform analytical (linear) uncertainty propagation
UncertaintyResult result = analyzer.analyzeAnalytical();

// Get output uncertainties
Map<String, Double> outputUncertainties = result.getOutputUncertainties();

// Get sensitivity matrix
SensitivityMatrix sensMatrix = result.getSensitivityMatrix();
double dP_dT = sensMatrix.getSensitivity("outlet_pressure", "inlet_temperature");

Monte Carlo Analysis

For nonlinear systems, use Monte Carlo:

// Configure Monte Carlo
analyzer.setMonteCarloSamples(10000);

// Run Monte Carlo uncertainty propagation
UncertaintyResult mcResult = analyzer.analyzeMonteCarlo(1000);

// Get percentiles
double p05 = mcResult.getPercentile("outlet_pressure", 0.05);
double p95 = mcResult.getPercentile("outlet_pressure", 0.95);

SensitivityMatrix

import neqsim.process.util.uncertainty.SensitivityMatrix;

// Get Jacobian matrix
SensitivityMatrix matrix = new SensitivityMatrix(inputNames, outputNames);

// Calculate sensitivities via finite differences
for (String input : inputNames) {
    for (String output : outputNames) {
        double sensitivity = calculateNumericalDerivative(input, output);
        matrix.setSensitivity(output, input, sensitivity);
    }
}

// Propagate input variances to output variances
Map<String, Double> inputVariances = Map.of("pressure", 0.01, "temperature", 0.0025);
Map<String, Double> outputVariances = matrix.propagateUncertainty(inputVariances);

ML Integration

Package: neqsim.process.integration.ml

Interfaces for combining physics models with machine learning corrections.

HybridModelAdapter

Wraps a NeqSim model with ML corrections:

import neqsim.process.integration.ml.HybridModelAdapter;
import neqsim.process.integration.ml.MLCorrectionInterface;

// Create hybrid adapter
ProcessSystem physicsModel = new ProcessSystem();
// ... configure model ...

HybridModelAdapter hybrid = new HybridModelAdapter(physicsModel);

// Add ML correction (implement MLCorrectionInterface)
MLCorrectionInterface mlCorrection = new MyNeuralNetworkCorrection();
hybrid.setCorrection(mlCorrection);

// Set combination strategy
hybrid.setCombinationStrategy(HybridModelAdapter.CombinationStrategy.ADDITIVE);

// Run hybrid model
hybrid.run();

// Get corrected outputs
double correctedPressure = hybrid.getCorrectedOutput("outlet_pressure");

Combination Strategies:

MLCorrectionInterface

Implement this interface to connect external ML models:

public interface MLCorrectionInterface {
    // Get correction for a specific output
    double getCorrection(String outputName, Map<String, Double> inputs);

    // Update model with new training data
    void update(Map<String, Double> inputs, Map<String, Double> targets);

    // Get model confidence (0-1)
    double getConfidence();
}

FeatureExtractor

Extract features from streams for ML models:

import neqsim.process.integration.ml.FeatureExtractor;

// Create feature extractor
FeatureExtractor extractor = new FeatureExtractor();

// Extract features from a stream
StreamInterface stream = process.getStream("inlet");
double[] features = extractor.extractFeatures(stream);

// Get feature names
String[] featureNames = extractor.getFeatureNames();

// Normalize features
double[] normalized = extractor.normalize(features);

Online Calibration

Package: neqsim.process.calibration

Continuously calibrates models using real-time data.

OnlineCalibrator

import neqsim.process.calibration.OnlineCalibrator;
import neqsim.process.calibration.CalibrationResult;
import neqsim.process.calibration.CalibrationQuality;

// Create calibrator for a process system
ProcessSystem process = new ProcessSystem();
OnlineCalibrator calibrator = new OnlineCalibrator(process);

// Configure tunable parameters
calibrator.setTunableParameters(Arrays.asList(
    "separator_efficiency",
    "heat_exchanger_UA",
    "compressor_polytropic_efficiency"
));

// Set deviation threshold for triggering recalibration
calibrator.setDeviationThreshold(0.1);  // 10%

// Record measurements and predictions
Map<String, Double> measurements = Map.of(
    "outlet_pressure", 45.2,
    "outlet_temperature", 35.5
);
Map<String, Double> predictions = Map.of(
    "outlet_pressure", 44.8,
    "outlet_temperature", 36.1
);

// Check if recalibration is needed
boolean needsRecalibration = calibrator.recordDataPoint(measurements, predictions);

// Perform incremental update (fast, for real-time)
CalibrationResult incrementalResult = calibrator.incrementalUpdate(measurements, predictions);

// Or perform full recalibration (thorough, periodic)
CalibrationResult fullResult = calibrator.fullRecalibration();

// Check calibration quality
CalibrationQuality quality = calibrator.getQualityMetrics();
System.out.println("Quality Score: " + quality.getOverallScore());
System.out.println("Rating: " + quality.getRating());
System.out.println("Needs Recalibration: " + quality.needsRecalibration());

CalibrationResult

CalibrationResult result = calibrator.fullRecalibration();

if (result.isSuccessful()) {
    Map<String, Double> params = result.getCalibratedParameters();
    double improvement = result.getImprovementPercent();
    System.out.println("Improved by " + improvement + "%");
}

CalibrationQuality

CalibrationQuality quality = calibrator.getQualityMetrics();

// Metrics
double rmse = quality.getRootMeanSquareError();
double r2 = quality.getR2Score();
int samples = quality.getSampleCount();
double coverage = quality.getCoveragePercent();

// Overall assessment
double score = quality.getOverallScore();  // 0-100
CalibrationQuality.Rating rating = quality.getRating();  // EXCELLENT, GOOD, FAIR, POOR

// Check calibration age
Duration age = quality.getCalibrationAge();

Well Allocation

Package: neqsim.process.equipment.well.allocation

Allocates commingled production back to individual wells.

WellProductionAllocator

import neqsim.process.equipment.well.allocation.WellProductionAllocator;
import neqsim.process.equipment.well.allocation.AllocationResult;

// Create allocator
WellProductionAllocator allocator = new WellProductionAllocator("Field-A-Allocation");

// Add wells with test data
WellProductionAllocator.WellData wellA = allocator.addWell("Well-A");
wellA.setTestRates(500, 50000, 100);  // oil, gas, water (Sm3/day)
wellA.setVFMRates(480, 48000, 95);
wellA.setChokePosition(0.75);
wellA.setProductivityIndex(10.0);
wellA.setReservoirPressure(250);

WellProductionAllocator.WellData wellB = allocator.addWell("Well-B");
wellB.setTestRates(300, 30000, 200);
wellB.setVFMRates(290, 29000, 195);
wellB.setChokePosition(0.60);
wellB.setProductivityIndex(8.0);
wellB.setReservoirPressure(245);

// Set allocation method
allocator.setAllocationMethod(WellProductionAllocator.AllocationMethod.VFM_BASED);

// Allocate total production
AllocationResult result = allocator.allocate(
    780,   // total oil (Sm3/day)
    78000, // total gas (Sm3/day)
    290    // total water (Sm3/day)
);

// Get allocated rates per well
double wellAOil = result.getOilRate("Well-A");
double wellAGas = result.getGasRate("Well-A");
double wellAGOR = result.getGOR("Well-A");
double wellAWC = result.getWaterCut("Well-A");
double uncertainty = result.getUncertainty("Well-A");

// Check allocation balance
boolean balanced = result.isBalanced();
double error = result.getAllocationError();

Allocation Methods:


Event System

Package: neqsim.process.util.event

Publish-subscribe system for process events.

ProcessEventBus

import neqsim.process.util.event.ProcessEventBus;
import neqsim.process.util.event.ProcessEvent;
import neqsim.process.util.event.ProcessEventListener;

// Get event bus instance
ProcessEventBus eventBus = ProcessEventBus.getInstance();

// Subscribe to all events
eventBus.subscribe(event -> {
    System.out.println("Event: " + event.getDescription());
});

// Subscribe to specific event types
eventBus.subscribe(ProcessEvent.EventType.ALARM, event -> {
    // Handle alarm
    sendAlarmNotification(event);
});

// Publish events
eventBus.publish(ProcessEvent.info("Compressor-1", "Startup complete"));
eventBus.publish(ProcessEvent.warning("Separator-1", "Level approaching high limit"));
eventBus.publish(ProcessEvent.alarm("Valve-V101", "Emergency shutdown activated"));

// Publish threshold crossing
eventBus.publish(ProcessEvent.thresholdCrossed(
    "Pressure-PT101", "pressure", 52.5, 50.0, true  // value, threshold, above
));

// Publish model deviation
eventBus.publish(ProcessEvent.modelDeviation(
    "VFM-Well-A", "gas_rate", 48500, 50000  // measured, predicted
));

ProcessEvent Properties

ProcessEvent event = ProcessEvent.alarm("Source", "Description");

// Set custom properties
event.setProperty("priority", 1);
event.setProperty("acknowledged", false);
event.setProperty("operator", "John");

// Get properties
int priority = event.getProperty("priority", Integer.class);

// Standard properties
String eventId = event.getEventId();
ProcessEvent.EventType type = event.getType();
String source = event.getSource();
Instant timestamp = event.getTimestamp();
ProcessEvent.Severity severity = event.getSeverity();

Event History

// Get recent events
List<ProcessEvent> recent = eventBus.getRecentEvents(100);

// Get events by type
List<ProcessEvent> alarms = eventBus.getEventsByType(ProcessEvent.EventType.ALARM, 50);

// Get events by severity
List<ProcessEvent> critical = eventBus.getEventsBySeverity(ProcessEvent.Severity.ERROR, 20);

Data Export

Package: neqsim.process.util.export

Export simulation data for external analysis and ML training.

TimeSeriesExporter

import neqsim.process.util.export.TimeSeriesExporter;

// Create exporter
ProcessSystem process = new ProcessSystem();
TimeSeriesExporter exporter = new TimeSeriesExporter(process);

// Collect snapshots during simulation
for (int step = 0; step < 1000; step++) {
    process.run();
    exporter.collectSnapshot();
    Thread.sleep(1000);  // 1 second intervals
}

// Export to JSON (AI platform format)
String json = exporter.exportToJson();
Files.writeString(Path.of("timeseries.json"), json);

// Export to CSV for ML training
String csv = exporter.exportToCsv();
Files.writeString(Path.of("training_data.csv"), csv);

// Export as feature matrix for ML
double[][] features = exporter.exportAsMatrix();

ProcessSnapshot

import neqsim.process.util.export.ProcessSnapshot;

// Create snapshot
ProcessSnapshot snapshot = new ProcessSnapshot("snap-001");

// Add measurements
snapshot.setMeasurement("inlet_pressure", 50.0, "bara");
snapshot.setMeasurement("inlet_temperature", 25.0, "C");
snapshot.setMeasurement("outlet_flowrate", 1000.0, "kg/hr");

// Serialize
String json = snapshot.toJson();

// Restore
ProcessSnapshot restored = ProcessSnapshot.fromJson(json);

ProcessDelta

Efficiently sync state changes:

import neqsim.process.util.export.ProcessDelta;

// Create delta between snapshots
ProcessSnapshot before = exporter.createSnapshot("before");
process.run();
ProcessSnapshot after = exporter.createSnapshot("after");

ProcessDelta delta = ProcessDelta.between(before, after);

// Get changes
Map<String, Double> changes = delta.getChangedValues();
double pressureChange = delta.getChange("outlet_pressure");

// Apply delta to another snapshot
ProcessSnapshot updated = delta.applyTo(before);

Example: Complete Integration

// Create process system
SystemInterface fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.setMixingRule("classic");

Stream inlet = new Stream("Inlet", fluid);
inlet.setFlowRate(10000, "kg/hr");
inlet.run();

ProcessSystem process = new ProcessSystem();
process.add(inlet);

// Setup streaming
ProcessDataPublisher publisher = new ProcessDataPublisher(process);

// Setup VFM
VirtualFlowMeter vfm = new VirtualFlowMeter("VFM-1", inlet);

// Setup online calibration
OnlineCalibrator calibrator = new OnlineCalibrator(process);
calibrator.setTunableParameters(Arrays.asList("efficiency"));
calibrator.setDeviationThreshold(0.05);

// Setup event bus
ProcessEventBus eventBus = ProcessEventBus.getInstance();
eventBus.subscribe(ProcessEvent.EventType.MODEL_DEVIATION, event -> {
    // Trigger recalibration on significant deviation
    if (calibrator.getQualityMetrics().needsRecalibration()) {
        calibrator.fullRecalibration();
    }
});

// Setup data export
TimeSeriesExporter exporter = new TimeSeriesExporter(process);

// Real-time loop
while (running) {
    // Run simulation step
    process.run();

    // Publish streaming data
    publisher.publishFromProcessSystem();

    // Get VFM estimate
    VFMResult vfmResult = vfm.calculate();

    // Check for deviations
    if (Math.abs(vfmResult.getGasFlowRate() - measuredGasRate) / measuredGasRate > 0.1) {
        eventBus.publish(ProcessEvent.modelDeviation(
            "VFM-1", "gas_rate", measuredGasRate, vfmResult.getGasFlowRate()
        ));
    }

    // Record for calibration
    calibrator.recordDataPoint(measurements, predictions);

    // Collect for export
    exporter.collectSnapshot();

    Thread.sleep(1000);
}

// Export training data
String trainingData = exporter.exportToCsv();

Performance Considerations

  1. Streaming Frequency: ProcessDataPublisher can handle 1000+ updates/second
  2. History Buffer: Default 1000 points; adjust via setMaxHistorySize()
  3. Monte Carlo Samples: Use 1000-10000 for uncertainty analysis
  4. Calibration: Incremental updates are O(1); full recalibration is O(n)
  5. Event Bus: Async delivery recommended for high-frequency events

Thread Safety

Integration with External Systems

For integration with AI-based production optimization platforms:

  1. Use ProcessDataPublisher to stream real-time data
  2. Export training data via TimeSeriesExporter in JSON format
  3. Implement MLCorrectionInterface to connect external ML models
  4. Use HybridModelAdapter to combine physics with ML corrections
  5. Subscribe to ProcessEventBus for real-time alerts and triggers

AI Validation Framework

NeqSim AI-Friendly Validation Framework

Overview

This document describes the AI-friendly validation and integration framework added to NeqSim. The framework provides structured validation, error remediation hints, and API discovery for AI/ML agents working with NeqSim thermodynamic simulations.

Architecture

neqsim.util.validation/
├── ValidationResult.java          # Structured validation container
├── SimulationValidator.java       # Static validation facade
├── AIIntegrationHelper.java       # Unified AI entry point
└── contracts/
    ├── ModuleContract.java        # Base contract interface
    ├── ThermodynamicSystemContract.java
    ├── StreamContract.java
    ├── SeparatorContract.java
    └── ProcessSystemContract.java

neqsim.util.annotation/
├── AIExposable.java               # Method discovery annotation
├── AIParameter.java               # Parameter documentation annotation
└── AISchemaDiscovery.java         # Reflection-based API discovery

Key Components

1. ValidationResult

A structured container for validation issues with severity levels:

ValidationResult result = SimulationValidator.validate(fluid);
if (!result.isValid()) {
    System.out.println(result.getReport()); // Human-readable
    for (ValidationIssue issue : result.getIssues()) {
        String fix = issue.getRemediation(); // AI-parseable hint
    }
}

Severity Levels:

2. SimulationValidator

Static facade for validating any NeqSim object:

// Validate any object
ValidationResult result = SimulationValidator.validate(object);

// Validate outputs after execution
ValidationResult postRun = SimulationValidator.validateOutput(process);

// Combined validate-and-run
ValidationResult combined = SimulationValidator.validateAndRun(stream);

3. Module Contracts

Pre/post-condition checking for specific NeqSim types:

ThermodynamicSystemContract contract = ThermodynamicSystemContract.getInstance();
ValidationResult pre = contract.checkPreconditions(system);
ValidationResult post = contract.checkPostconditions(system);

Available Contracts:

4. Equipment-Level Validation

All process equipment classes implement validateSetup() to check equipment-specific configuration:

// Validate individual equipment
Separator separator = new Separator("V-100");
ValidationResult result = separator.validateSetup();

if (!result.isValid()) {
    System.out.println("Configuration issues:");
    result.getErrors().forEach(System.out::println);
}

Equipment Validation Checks:

Equipment Validations
Stream Fluid set, temperature > 0 K
Separator Inlet stream connected
Mixer At least one inlet stream added
Splitter Inlet stream connected, split fractions sum to 1.0
Tank Has fluid or input stream connected
DistillationColumn Feed streams connected, condenser/reboiler configured
Recycle Inlet and outlet streams set, tolerance > 0
Adjuster Target and adjustment variables set, tolerance > 0
TwoPortEquipment Inlet stream connected

5. ProcessSystem Validation

ProcessSystem provides aggregate validation across all equipment:

ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(compressor);

// Quick check before running
if (process.isReadyToRun()) {
    process.run();
} else {
    // Get combined validation result
    ValidationResult result = process.validateSetup();
    System.out.println(result.getReport());
}

// Get per-equipment validation
Map<String, ValidationResult> allResults = process.validateAll();
for (Map.Entry<String, ValidationResult> entry : allResults.entrySet()) {
    if (!entry.getValue().isValid()) {
        System.out.println(entry.getKey() + ": " + entry.getValue().getErrors());
    }
}

ProcessSystem Validation Methods:

Method Returns Description
validateSetup() ValidationResult Combined result for all equipment
validateAll() Map<String, ValidationResult> Per-equipment results
isReadyToRun() boolean True if no CRITICAL errors

5b. ProcessModel Validation

ProcessModel provides aggregate validation across all contained ProcessSystems:

ProcessModel model = new ProcessModel();
model.add("GasProcessing", gasProcess);
model.add("OilProcessing", oilProcess);

// Quick check before running
if (model.isReadyToRun()) {
    model.run();
} else {
    // Get formatted validation report
    System.out.println(model.getValidationReport());
}

// Get per-process validation
Map<String, ValidationResult> allResults = model.validateAll();
for (Map.Entry<String, ValidationResult> entry : allResults.entrySet()) {
    if (!entry.getValue().isValid()) {
        System.out.println(entry.getKey() + ": " + entry.getValue().getErrors());
    }
}

ProcessModel Validation Methods:

Method Returns Description
validateSetup() ValidationResult Combined result for all processes
validateAll() Map<String, ValidationResult> Per-process results
isReadyToRun() boolean True if no CRITICAL errors
getValidationReport() String Human-readable formatted report

6. AIIntegrationHelper

Unified entry point connecting validation with RL/ML infrastructure:

AIIntegrationHelper helper = AIIntegrationHelper.forProcess(process);

// Check readiness
if (helper.isReady()) {
    ExecutionResult result = helper.safeRun();
    System.out.println(result.toAIReport());
}

// Get API documentation for agent
String docs = helper.getAPIDocumentation();

// Create RL environment
RLEnvironment env = helper.createRLEnvironment();

7. AI Annotations

Annotations for exposing methods to AI agents:

@AIExposable(
    description = "Add a chemical component to the system",
    category = "composition",
    example = "addComponent(\"methane\", 0.9)",
    priority = 100,
    safe = false
)
public void addComponent(
    @AIParameter(name = "name", description = "Component name") String name,
    @AIParameter(name = "moles", minValue = 0.0, maxValue = 1.0) double moles
) { ... }

8. AISchemaDiscovery

Discovers annotated methods via reflection:

AISchemaDiscovery discovery = new AISchemaDiscovery();

// Discover methods in a class
List<MethodSchema> methods = discovery.discoverMethods(SystemSrkEos.class);

// Generate prompt for AI
String prompt = discovery.generateMethodPrompt(methods);

// Get quick-start documentation
String quickStart = discovery.getQuickStartPrompt();

Integration with Existing NeqSim ML Infrastructure

The framework integrates with NeqSim's existing ML capabilities:

Component Package Integration
RLEnvironment neqsim.process.ml Create from AIIntegrationHelper
GymEnvironment neqsim.process.ml Compatible state/action vectors
ProcessLinkedMPC neqsim.process.mpc Validate MPC process systems
ProductionOptimizer neqsim.process.util.optimization Validate optimizer inputs
SurrogateModelRegistry neqsim.process.ml.surrogate Physics constraint checking

Exception Remediation

Enhanced exceptions with remediation hints:

try {
    process.run();
} catch (InvalidInputException e) {
    String fix = e.getRemediation(); // "Provide valid values: ..."
} catch (TooManyIterationsException e) {
    String fix = e.getRemediation(); // "Increase max iterations or adjust initial estimate"
    int tried = e.getMaxIterations();
}

Enhanced Exceptions:

Usage Examples

Basic Validation

// Create fluid
SystemInterface fluid = new SystemSrkEos(298.15, 10.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.1);
fluid.setMixingRule("classic");

// Validate before flash
ValidationResult result = SimulationValidator.validate(fluid);
if (!result.isValid()) {
    System.out.println("Issues found:");
    System.out.println(result.getReport());
}

Process System Validation

ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(compressor);

AIIntegrationHelper helper = AIIntegrationHelper.forProcess(process);
if (helper.isReady()) {
    ExecutionResult result = helper.safeRun();
    if (result.isSuccess()) {
        // Process ran successfully
    }
} else {
    // Get structured issues for AI to fix
    String[] issues = helper.getIssuesAsText();
    for (String issue : issues) {
        System.out.println(issue);
    }
}

RL Integration

ProcessSystem process = buildProcess();
AIIntegrationHelper helper = AIIntegrationHelper.forProcess(process);

// Validate before creating RL environment
if (helper.isReady()) {
    RLEnvironment env = helper.createRLEnvironment();

    // RL training loop
    StateVector obs = env.reset();
    while (!done) {
        ActionVector action = agent.selectAction(obs);
        RLEnvironment.StepResult result = env.step(action);
        obs = result.observation;
        done = result.done;
    }
}

Test Coverage

All components have comprehensive unit tests:

Test Class Tests Coverage
ValidationResultTest 13 Core validation logic
SimulationValidatorTest 16 Static facade methods
ModuleContractTest 14 Contract implementations
AISchemaDiscoveryTest 13 Annotation discovery
AIIntegrationHelperTest 15 Integration helper
EquipmentValidationTest 41 Equipment and ProcessModel validateSetup() methods

Run tests:

./mvnw test -Dtest="ValidationResultTest,SimulationValidatorTest,ModuleContractTest,AISchemaDiscoveryTest,AIIntegrationHelperTest,EquipmentValidationTest"

Future Enhancements

  1. ~~Standardize validateSetup() across all ProcessEquipmentBaseClass subclasses~~ ✅ Implemented
  2. Apply @AIExposable annotations to core NeqSim methods (addComponent, setMixingRule, TPflash, etc.)
  3. MPC validation contracts for ProcessLinkedMPC
  4. Surrogate model validation integration with SurrogateModelRegistry
  5. Real-time validation during simulation stepping
  6. Custom validation rules via pluggable validators

Chapter 42: Chemical Reactions

Chemical Reactions

Chemical Reactions Package

The chemicalreactions package provides tools for chemical equilibrium calculations and reaction kinetics.

Table of Contents


Overview

Location: neqsim.chemicalreactions

Purpose:


Package Structure

chemicalreactions/
├── ChemicalReactionOperations.java   # Main operations class
│
├── chemicalequilibrium/              # Equilibrium calculations
│   ├── ChemicalEquilibrium.java      # Base class
│   ├── ChemEq.java                   # Equilibrium solver
│   ├── LinearProgrammingChemicalEquilibrium.java
│   └── ReferencePotComparator.java
│
├── chemicalreaction/                 # Reaction definitions
│   ├── ChemicalReaction.java         # Single reaction
│   └── ChemicalReactionList.java     # Reaction set
│
└── kinetics/                         # Kinetics models
    └── Kinetics.java                 # Kinetic rate calculations

Chemical Equilibrium

Theory

Chemical equilibrium is achieved when the Gibbs energy is minimized:

$$\min G = \sum_i n_i \mu_i$$

Subject to element balance constraints:

$$\sum_i a_{ji} n_i = b_j \quad \text{for each element } j$$

Where:

Basic Usage

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

// Create reactive system
SystemInterface reactive = new SystemSrkEos(700.0, 10.0);
reactive.addComponent("methane", 1.0);
reactive.addComponent("water", 2.0);
reactive.addComponent("CO2", 0.0);
reactive.addComponent("CO", 0.0);
reactive.addComponent("hydrogen", 0.0);
reactive.setMixingRule("classic");

// Enable chemical reactions
reactive.setChemicalReactions(true);

// Perform equilibrium calculation
ThermodynamicOperations ops = new ThermodynamicOperations(reactive);
ops.calcChemicalEquilibrium();

// Display results
for (int i = 0; i < reactive.getNumberOfComponents(); i++) {
    System.out.println(reactive.getComponent(i).getName() + 
        ": " + reactive.getComponent(i).getNumberOfmable() + " mol");
}

Supported Reactions

Steam Reforming

CH₄ + H₂O ⇌ CO + 3H₂
CO + H₂O ⇌ CO₂ + H₂

Combustion

CH₄ + 2O₂ → CO₂ + 2H₂O
C₂H₆ + 3.5O₂ → 2CO₂ + 3H₂O

Acid Gas Reactions

CO₂ + H₂O ⇌ H₂CO₃
H₂S + H₂O ⇌ HS⁻ + H₃O⁺
NH₃ + H₂O ⇌ NH₄⁺ + OH⁻

Amine Reactions

CO₂ + 2RNH₂ ⇌ RNHCOO⁻ + RNH₃⁺
CO₂ + RNH₂ + H₂O ⇌ RNH₃⁺ + HCO₃⁻

ChemicalReactionOperations

Main Class

import neqsim.chemicalreactions.ChemicalReactionOperations;

ChemicalReactionOperations reactionOps = new ChemicalReactionOperations(fluid);

// Add reactions
reactionOps.addReaction("methane_reforming");
reactionOps.addReaction("water_gas_shift");

// Calculate equilibrium
reactionOps.calcChemicalEquilibrium();

// Get equilibrium constants
double Keq = reactionOps.getEquilibriumConstant("methane_reforming");

Reaction Kinetics

For rate-limited reactions, use kinetic models.

Kinetic Rate Expression

General rate expression:

$$r = k \cdot \prod_i C_i^{n_i}$$

Where:

Temperature Dependence (Arrhenius)

$$k = A \cdot \exp\left(-\frac{E_a}{RT}\right)$$

Where:

Usage

import neqsim.chemicalreactions.kinetics.Kinetics;

Kinetics kinetics = new Kinetics(fluid);

// Set reaction parameters
kinetics.setPreExponentialFactor(1.0e10);  // 1/s
kinetics.setActivationEnergy(80000.0);      // J/mol

// Calculate rate at current conditions
double rate = kinetics.getReactionRate();

Reactive Flash Calculations

Combine phase equilibrium with chemical equilibrium.

TP Flash with Reactions

// Set up reactive system
SystemInterface fluid = new SystemSrkEos(500.0, 20.0);
fluid.addComponent("methane", 1.0);
fluid.addComponent("oxygen", 0.5);
fluid.addComponent("CO2", 0.0);
fluid.addComponent("water", 0.0);
fluid.setMixingRule("classic");
fluid.setChemicalReactions(true);

// Reactive TP flash
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();

// Results include both phase and chemical equilibrium
System.out.println("Number of phases: " + fluid.getNumberOfPhases());
for (int i = 0; i < fluid.getNumberOfComponents(); i++) {
    System.out.println(fluid.getComponent(i).getName() + 
        ": " + fluid.getComponent(i).getz() + " mol/mol");
}

Gibbs Energy Minimization

Linear Programming Method

For complex systems, use linear programming approach.

import neqsim.chemicalreactions.chemicalequilibrium.LinearProgrammingChemicalEquilibrium;

LinearProgrammingChemicalEquilibrium lpEquil = 
    new LinearProgrammingChemicalEquilibrium(fluid);
lpEquil.solve();

// Get equilibrium composition
double[] composition = lpEquil.getEquilibriumComposition();

Database Integration

Chemical reactions and their parameters are stored in the database.

Reaction Database

Field Description
name Reaction identifier
reactants Reactant species
products Product species
stoichiometry Stoichiometric coefficients
deltaH Enthalpy of reaction
deltaG Gibbs energy of reaction
Keq_A, Keq_B, Keq_C Equilibrium constant correlation

Loading Reactions

// Load reactions from database
fluid.createChemicalReactions(true);

// Or specify specific reactions
fluid.addChemicalReaction("steam_reforming");
fluid.addChemicalReaction("water_gas_shift");

Example: Ammonia Synthesis

// Ammonia synthesis: N₂ + 3H₂ ⇌ 2NH₃
SystemInterface syngas = new SystemSrkEos(673.15, 200.0);  // 400°C, 200 bar
syngas.addComponent("nitrogen", 1.0);
syngas.addComponent("hydrogen", 3.0);
syngas.addComponent("ammonia", 0.0);
syngas.setMixingRule("classic");
syngas.setChemicalReactions(true);

ThermodynamicOperations ops = new ThermodynamicOperations(syngas);
ops.calcChemicalEquilibrium();

double NH3fraction = syngas.getComponent("ammonia").getz();
double conversion = 2 * NH3fraction / 
    (syngas.getComponent("nitrogen").getz() + NH3fraction);

System.out.println("NH₃ mole fraction: " + NH3fraction);
System.out.println("N₂ conversion: " + (conversion * 100) + "%");

Example: CO₂ Capture with Amine

// CO₂ absorption in MEA solution
SystemInterface solution = new SystemElectrolyteCPA(313.15, 1.01325);
solution.addComponent("CO2", 0.05);
solution.addComponent("water", 0.75);
solution.addComponent("MEA", 0.20);
solution.setMixingRule("CPA_Statoil");
solution.setChemicalReactions(true);

// Flash with reactions
ThermodynamicOperations ops = new ThermodynamicOperations(solution);
ops.TPflash();

// Get CO₂ loading
double CO2loading = solution.getComponent("CO2").getx() / 
    solution.getComponent("MEA").getx();
System.out.println("CO₂ loading: " + CO2loading + " mol CO₂/mol MEA");

Best Practices

  1. Initialize products with small but non-zero amounts
  2. Check element balance before and after equilibrium
  3. Use appropriate thermodynamic model (electrolyte models for ionic reactions)
  4. Verify equilibrium constants against literature
  5. Consider kinetic limitations for slow reactions

Limitations


Chapter 43: Statistics & Fitting

Statistics Overview

Statistics Package

The NeqSim statistics package provides tools for parameter fitting, uncertainty quantification, and data analysis for thermodynamic model development and validation.

Table of Contents


Overview

The statistics package supports:

  1. Parameter Fitting - Nonlinear regression using Levenberg-Marquardt algorithm
  2. Monte Carlo Simulation - Uncertainty propagation and confidence intervals
  3. Data Analysis - Smoothing, filtering, and statistical analysis
  4. Experimental Data Management - Sample sets and experimental equipment modeling

Location: neqsim.statistics

Key Applications:


Package Structure

statistics/
├── parameterfitting/                    # Core parameter fitting framework
│   ├── StatisticsBaseClass.java        # Abstract base for all fitting
│   ├── StatisticsInterface.java        # Interface definition
│   ├── SampleSet.java                  # Collection of experimental points
│   ├── SampleValue.java                # Single experimental data point
│   ├── BaseFunction.java               # Abstract objective function
│   ├── FunctionInterface.java          # Function interface
│   ├── NumericalDerivative.java        # Numerical differentiation
│   └── nonlinearparameterfitting/      # Nonlinear optimization
│       ├── LevenbergMarquardt.java     # L-M optimizer (least squares)
│       ├── LevenbergMarquardtAbsDev.java   # Absolute deviation
│       ├── LevenbergMarquardtBiasDev.java  # Bias deviation
│       └── LevenbergMarquardtFunction.java # Example function
│
├── montecarlosimulation/               # Uncertainty quantification
│   └── MonteCarloSimulation.java       # MC simulation runner
│
├── dataanalysis/                       # Data processing
│   └── datasmoothing/
│       └── DataSmoother.java           # Savitzky-Golay smoothing
│
├── experimentalsamplecreation/         # Sample generation
│   ├── readdatafromfile/               # File I/O for experimental data
│   └── samplecreator/
│       ├── SampleCreator.java          # Base sample creator
│       └── wettedwallcolumnsamplecreator/  # Specialized creator
│
└── experimentalequipmentdata/          # Equipment modeling
    ├── ExperimentalEquipmentData.java
    └── wettedwallcolumndata/           # Wetted wall column

Sub-Documentation

Detailed guides for each major subsystem:

Guide Description
Parameter Fitting Levenberg-Marquardt optimization, creating objective functions, bounds
Monte Carlo Simulation Uncertainty propagation, confidence intervals, distribution sampling
Data Analysis Data smoothing, filtering, statistical measures

Core Concepts

Sample Values

A SampleValue represents one experimental data point:

// Experimental value with uncertainty
double experimentalValue = 0.5;    // Measured value (e.g., pressure)
double standardDeviation = 0.05;  // Experimental uncertainty
double[] independentVariables = {300.0, 0.1};  // e.g., temperature, composition

SampleValue sample = new SampleValue(
    experimentalValue, 
    standardDeviation, 
    independentVariables
);

Sample Sets

A SampleSet is a collection of experimental points:

SampleSet sampleSet = new SampleSet();
sampleSet.add(sample1);
sampleSet.add(sample2);
sampleSet.add(sample3);

// Or from array
SampleValue[] samples = {sample1, sample2, sample3};
SampleSet sampleSet = new SampleSet(samples);

Objective Functions

Functions extend BaseFunction or LevenbergMarquardtFunction:

public class MyObjectiveFunction extends LevenbergMarquardtFunction {

    @Override
    public double calcValue(double[] dependentValues) {
        // params[0], params[1], ... are the fitting parameters
        // dependentValues are the independent variables (T, P, x, ...)

        double T = dependentValues[0];
        double x = dependentValues[1];

        // Calculate model prediction
        double predicted = params[0] * Math.exp(-params[1] / T) * x;

        return predicted;
    }

    @Override
    public void setFittingParams(int i, double value) {
        params[i] = value;
    }
}

Quick Start

Basic Parameter Fitting

import neqsim.statistics.parameterfitting.*;
import neqsim.statistics.parameterfitting.nonlinearparameterfitting.*;
import java.util.ArrayList;

// 1. Create objective function
MyObjectiveFunction function = new MyObjectiveFunction();

// 2. Set initial parameter guess
double[] initialGuess = {1.0, 500.0};  // Two parameters to fit
function.setInitialGuess(initialGuess);

// 3. Create experimental samples
ArrayList<SampleValue> samples = new ArrayList<>();

double[] x1 = {300.0, 0.1};  // T=300K, x=0.1
SampleValue s1 = new SampleValue(0.05, 0.005, x1);  // exp=0.05 ± 0.005
s1.setFunction(function);
samples.add(s1);

double[] x2 = {350.0, 0.2};
SampleValue s2 = new SampleValue(0.12, 0.01, x2);
s2.setFunction(function);
samples.add(s2);

// Add more samples...

// 4. Create sample set and optimizer
SampleSet sampleSet = new SampleSet(samples);
LevenbergMarquardt optimizer = new LevenbergMarquardt();
optimizer.setSampleSet(sampleSet);

// 5. Solve
optimizer.solve();

// 6. Get results
double[] fittedParams = sampleSet.getSample(0).getFunction().getFittingParams();
System.out.println("Fitted parameters: " + Arrays.toString(fittedParams));

// 7. Display results
optimizer.displayCurveFit();
optimizer.displayResult();

With Monte Carlo Uncertainty

// After solving...
optimizer.runMonteCarloSimulation(100);  // 100 Monte Carlo runs

// This generates samples with normally distributed perturbations
// around experimental values and re-fits, providing parameter distributions

Integration with Thermodynamic Models

Fitting Binary Interaction Parameters

public class KijFittingFunction extends LevenbergMarquardtFunction {

    @Override
    public double calcValue(double[] dependentValues) {
        double temperature = dependentValues[0];
        double pressure = dependentValues[1];
        double x_exp = dependentValues[2];  // Experimental composition

        // Set up thermodynamic system
        system.setTemperature(temperature);
        system.setPressure(pressure);

        // Set kij from fitting parameters
        ((PhaseEos) system.getPhase(0)).getMixingRule()
            .setBinaryInteractionParameter(0, 1, params[0]);
        ((PhaseEos) system.getPhase(1)).getMixingRule()
            .setBinaryInteractionParameter(0, 1, params[0]);

        // Flash calculation
        thermoOps.TPflash();

        // Return calculated liquid composition
        return system.getPhase(1).getComponent(0).getx();
    }

    @Override
    public void setFittingParams(int i, double value) {
        params[i] = value;
    }
}

Fitting CPA Association Parameters

public class CPAFittingFunction extends LevenbergMarquardtFunction {

    @Override
    public double calcValue(double[] dependentValues) {
        double T = dependentValues[0];
        double P = dependentValues[1];

        // params[0] = epsilon (association energy)
        // params[1] = beta (association volume)

        system.getComponent("water").setAssociationEnergy(params[0]);
        system.getComponent("water").setAssociationVolume(params[1]);

        thermoOps.TPflash();

        return system.getPhase(1).getDensity("kg/m3");
    }

    @Override
    public void setFittingParams(int i, double value) {
        params[i] = value;
    }
}

Statistical Measures

After fitting, several statistics are available:

// Solve first
optimizer.solve();

// Chi-square statistic
double chiSquare = optimizer.calcChiSquare();

// Absolute deviation statistics
optimizer.calcAbsDev();

// Covariance matrix
optimizer.calcCoVarianceMatrix();

// Parameter standard deviations
optimizer.calcParameterStandardDeviation();

// Parameter correlation matrix
optimizer.calcCorrelationMatrix();

// 95% confidence intervals
optimizer.calcParameterUncertainty();

Best Practices

Initial Guess Selection

Good initial guesses are critical for convergence:

Experimental Uncertainties

Proper uncertainty specification affects:

Parameter Bounds

Set physical bounds to prevent unphysical solutions:

double[][] bounds = {
    {0.0, 1.0},     // Parameter 0: between 0 and 1
    {100.0, 1000.0} // Parameter 1: between 100 and 1000
};
function.setBounds(bounds);

Convergence Criteria

optimizer.setMaxNumberOfIterations(100);  // Increase if needed

References

  1. Press, W.H., et al. (2007). Numerical Recipes: The Art of Scientific Computing. Cambridge University Press.
  2. Marquardt, D.W. (1963). An Algorithm for Least-Squares Estimation of Nonlinear Parameters. SIAM J. Appl. Math.
  3. Savitzky, A., Golay, M.J.E. (1964). Smoothing and Differentiation of Data by Simplified Least Squares Procedures. Anal. Chem.

Chapter 44: Utilities

Utilities Overview

Utilities Package

The util package provides common utilities for database access, unit conversion, serialization, exceptions, and threading.

Table of Contents


Overview

Location: neqsim.util

Purpose:


Package Structure

util/
├── NamedBaseClass.java           # Base class with name property
├── NamedInterface.java           # Named interface
├── NeqSimLogging.java            # Logging utilities
├── NeqSimThreadPool.java         # Thread pool management
├── ExcludeFromJacocoGeneratedReport.java
│
├── database/                     # Database access
│   ├── NeqSimDataBase.java       # Main database class
│   ├── NeqSimContractDataBase.java
│   ├── NeqSimExperimentDatabase.java
│   └── NeqSimFluidDataBase.java
│
├── exception/                    # Custom exceptions
│   ├── InvalidInputException.java
│   ├── ThermoException.java
│   └── NotImplementedException.java
│
├── generator/                    # Code generation
│   └── PropertyGenerator.java
│
├── manifest/                     # Manifest handling
│   └── ManifestHandler.java
│
├── python/                       # Python integration
│   └── PythonIntegration.java
│
├── serialization/                # Serialization utilities
│   └── SerializationManager.java
│
├── unit/                         # Unit conversion
│   ├── Units.java
│   └── UnitConverter.java
│
└── util/                         # General utilities
    └── Utilities.java

Database Access

NeqSimDataBase

Main class for database connectivity.

import neqsim.util.database.NeqSimDataBase;

// Get database connection
try (NeqSimDataBase db = new NeqSimDataBase()) {
    // Execute query
    ResultSet rs = db.getResultSet(
        "SELECT * FROM comp WHERE compname = 'methane'"
    );

    while (rs.next()) {
        double Tc = rs.getDouble("TC");
        double Pc = rs.getDouble("PC");
        double omega = rs.getDouble("ACF");
    }
}

Database Configuration

// Set database path (for embedded Derby)
NeqSimDataBase.setDataBaseType("Derby");
NeqSimDataBase.setConnectionString("jdbc:derby:NeqSimDatabase");

// Or use PostgreSQL
NeqSimDataBase.setDataBaseType("PostgreSQL");
NeqSimDataBase.setConnectionString("jdbc:postgresql://localhost:5432/neqsim");
NeqSimDataBase.setUsername("user");
NeqSimDataBase.setPassword("password");

Predefined Queries

// Component data
NeqSimFluidDataBase.getComponentData("methane");

// Binary interaction parameters
NeqSimFluidDataBase.getInteractionParameters("methane", "ethane", "SRK");

// Experiment data
NeqSimExperimentDatabase.getExperimentData("VLE_CH4_CO2");

Unit Conversion

For comprehensive unit conversion documentation, see Unit Conversion Guide.

Quick Reference

import neqsim.util.unit.Units;
import neqsim.util.unit.PressureUnit;
import neqsim.util.unit.TemperatureUnit;

// Direct unit conversion
PressureUnit pu = new PressureUnit(50.0, "bara");
double p_psia = pu.getValue("psia");

TemperatureUnit tu = new TemperatureUnit(25.0, "C");
double t_K = tu.getValue("K");

// In fluid properties
double T_C = fluid.getTemperature("C");
fluid.setTemperature(25.0, "C");

double P_bara = fluid.getPressure("bara");
fluid.setPressure(50.0, "bara");

double flow = stream.getFlowRate("kg/hr");
stream.setFlowRate(1000.0, "kg/hr");

Supported Units

Property Units
Temperature K, C, F, R
Pressure Pa, bara, barg, psia, psig, atm, mmHg, kPa, MPa
Flow rate kg/s, kg/hr, lb/hr, Sm3/hr, MSm3/day, mol/s, kmol/hr
Volume m3, L, ft3, bbl, gal
Density kg/m3, g/cm3, lb/ft3
Viscosity Pa.s, cP, mPa.s
Energy J, kJ, MJ, cal, BTU
Power W, kW, MW, hp

// Enthalpy double H_kJ = fluid.getEnthalpy("kJ/kg");


---

## Serialization

NeqSim provides multiple serialization options for saving and loading simulations.

### Process System Serialization

```java
// Save process system to compressed .neqsim file
ProcessSystem process = new ProcessSystem("My Process");
// ... add equipment ...
process.saveToNeqsim("myprocess.neqsim");

// Load from file (auto-runs after loading)
ProcessSystem loaded = ProcessSystem.loadFromNeqsim("myprocess.neqsim");

// Auto-detect format by extension
process.saveAuto("myprocess.neqsim");  // Compressed
process.saveAuto("myprocess.json");    // JSON state

Process Model Serialization (Multi-Process)

// Save ProcessModel containing multiple ProcessSystems
ProcessModel model = new ProcessModel();
model.add("upstream", upstreamProcess);
model.add("downstream", downstreamProcess);

model.saveToNeqsim("field_model.neqsim");

// Load (auto-runs after loading)
ProcessModel loaded = ProcessModel.loadFromNeqsim("field_model.neqsim");

JSON State for Version Control

// Export to Git-friendly JSON format
ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);
state.setVersion("1.0.0");
state.saveToFile("process_v1.0.0.json");

// Load and validate
ProcessSystemState loaded = ProcessSystemState.loadFromFile("process_v1.0.0.json");
if (loaded.validate().isValid()) {
    ProcessSystem restored = loaded.toProcessSystem();
}

Deep Copy via Serialization

// Clone using serialization (deep copy)
SystemInterface clone = fluid.clone();

// Or for process equipment
ProcessEquipmentInterface copy = equipment.copy();

For full documentation: See Process Serialization Guide


Exceptions

Custom Exceptions

import neqsim.util.exception.*;

// Invalid input
if (temperature < 0) {
    throw new InvalidInputException("Temperature", 
        "Temperature must be positive");
}

// Thermodynamic calculation failure
try {
    ops.TPflash();
} catch (ThermoException e) {
    System.err.println("Flash calculation failed: " + e.getMessage());
}

// Not implemented feature
throw new NotImplementedException("This feature", 
    "Will be available in next release");

Exception Handling Pattern

try {
    ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
    ops.TPflash();
} catch (InvalidInputException e) {
    // Handle invalid inputs
    logger.error("Invalid input: " + e.getMessage());
} catch (ThermoException e) {
    // Handle calculation failures
    logger.error("Calculation failed: " + e.getMessage());
} catch (Exception e) {
    // Handle unexpected errors
    logger.error("Unexpected error", e);
}

Threading

NeqSimThreadPool

Manage parallel calculations.

import neqsim.util.NeqSimThreadPool;

// Configure thread pool
NeqSimThreadPool.setNumberOfThreads(8);

// Submit tasks
Future<Double> result1 = NeqSimThreadPool.submit(() -> {
    // Parallel calculation
    return calculateProperty1();
});

Future<Double> result2 = NeqSimThreadPool.submit(() -> {
    return calculateProperty2();
});

// Get results
double prop1 = result1.get();
double prop2 = result2.get();

Parallel Flash Calculations

// Run multiple flashes in parallel
List<SystemInterface> fluids = prepareFluids();

List<Future<SystemInterface>> futures = fluids.stream()
    .map(f -> NeqSimThreadPool.submit(() -> {
        ThermodynamicOperations ops = new ThermodynamicOperations(f);
        ops.TPflash();
        return f;
    }))
    .collect(Collectors.toList());

// Collect results
for (Future<SystemInterface> future : futures) {
    SystemInterface result = future.get();
    // Process result
}

Logging

NeqSimLogging

Configure logging.

import neqsim.util.NeqSimLogging;

// Set log level
NeqSimLogging.setLogLevel(Level.DEBUG);

// Log messages
NeqSimLogging.info("Process started");
NeqSimLogging.debug("Temperature: " + T);
NeqSimLogging.error("Calculation failed", exception);

Log4j2 Configuration

NeqSim uses Log4j2. Configure via log4j2.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Console"/>
    </Root>
    <Logger name="neqsim" level="debug"/>
  </Loggers>
</Configuration>

Python Integration

Direct Java Access

import jpype
import jpype.imports
from jpype.types import *

# Start JVM
jpype.startJVM(classpath=['neqsim.jar'])

from neqsim.thermo.system import SystemSrkEos
from neqsim.thermodynamicoperations import ThermodynamicOperations

# Create fluid
fluid = SystemSrkEos(300.0, 50.0)
fluid.addComponent("methane", 0.9)
fluid.addComponent("ethane", 0.1)
fluid.setMixingRule("classic")

# Flash
ops = ThermodynamicOperations(fluid)
ops.TPflash()

print(f"Density: {fluid.getDensity('kg/m3'):.2f} kg/m³")

Named Objects

NamedBaseClass

Base class for named objects.

public class MyEquipment extends NamedBaseClass {
    public MyEquipment(String name) {
        super(name);
    }
}

// Usage
MyEquipment eq = new MyEquipment("E-100");
String name = eq.getName();
eq.setName("E-101");

Best Practices

  1. Close database connections - Use try-with-resources
  2. Handle units explicitly - Always specify units in API calls
  3. Use thread pool for parallel calculations
  4. Serialize for persistence - Save/load complex objects
  5. Log appropriately - Use debug for details, info for important events

Chapter 45: Math Library

Math Library

Mathematical Library Package

The mathlib package provides mathematical utilities, nonlinear solvers, and numerical methods.

Table of Contents


Overview

Location: neqsim.mathlib

Purpose:


Package Structure

mathlib/
├── generalmath/                  # General mathematical utilities
│   ├── GeneralMath.java          # Common math functions
│   ├── TDMAsolve.java            # Tridiagonal matrix solver
│   └── SplineInterpolation.java  # Spline interpolation
│
└── nonlinearsolver/              # Nonlinear equation solvers
    ├── NonLinearSolver.java      # Base solver
    ├── NewtonRaphson.java        # Newton-Raphson method
    ├── Brent.java                # Brent's method
    ├── Bisection.java            # Bisection method
    └── NumericalDerivative.java  # Numerical derivatives

Nonlinear Solvers

Newton-Raphson Method

Iterative method for finding roots of functions.

$$x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}$$

import neqsim.mathlib.nonlinearsolver.NewtonRaphson;

// Define function to solve: f(x) = x² - 2 (find √2)
Function<Double, Double> f = x -> x * x - 2.0;
Function<Double, Double> df = x -> 2.0 * x;

NewtonRaphson solver = new NewtonRaphson();
solver.setFunction(f);
solver.setDerivative(df);
solver.setInitialGuess(1.0);
solver.setTolerance(1e-10);
solver.setMaxIterations(100);

double root = solver.solve();
System.out.println("√2 = " + root);  // 1.4142135623...

Brent's Method

Robust root-finding combining bisection, secant, and inverse quadratic interpolation.

import neqsim.mathlib.nonlinearsolver.Brent;

Function<Double, Double> f = x -> x * x * x - x - 2.0;

Brent solver = new Brent();
solver.setFunction(f);
solver.setBracket(1.0, 2.0);  // Root is in [1, 2]
solver.setTolerance(1e-10);

double root = solver.solve();
System.out.println("Root: " + root);

Bisection Method

Simple but robust root-finding.

import neqsim.mathlib.nonlinearsolver.Bisection;

Function<Double, Double> f = x -> Math.sin(x) - 0.5;

Bisection solver = new Bisection();
solver.setFunction(f);
solver.setBracket(0.0, Math.PI);
solver.setTolerance(1e-8);

double root = solver.solve();
System.out.println("arcsin(0.5) = " + root);  // π/6 ≈ 0.5236

Numerical Derivatives

Forward Difference

$$f'(x) \approx \frac{f(x+h) - f(x)}{h}$$

Central Difference

$$f'(x) \approx \frac{f(x+h) - f(x-h)}{2h}$$

import neqsim.mathlib.nonlinearsolver.NumericalDerivative;

Function<Double, Double> f = x -> Math.exp(x);

NumericalDerivative deriv = new NumericalDerivative();
deriv.setFunction(f);
deriv.setStepSize(1e-6);

double df = deriv.centralDifference(1.0);
System.out.println("d/dx(e^x) at x=1: " + df);  // ≈ e ≈ 2.718

General Math

TDMAsolve (Thomas Algorithm)

Efficient solver for tridiagonal systems.

$$\begin{bmatrix} b_1 & c_1 \ a_2 & b_2 & c_2 \ & \ddots & \ddots & \ddots \ & & a_{n-1} & b_{n-1} & c_{n-1} \ & & & a_n & b_n \end{bmatrix} \begin{bmatrix} x_1 \ x_2 \ \vdots \ x_{n-1} \ x_n \end{bmatrix} = \begin{bmatrix} d_1 \ d_2 \ \vdots \ d_{n-1} \ d_n \end{bmatrix}$$

import neqsim.mathlib.generalmath.TDMAsolve;

// Coefficients
double[] a = {0, 1, 1, 1};    // Lower diagonal
double[] b = {4, 4, 4, 4};    // Main diagonal
double[] c = {1, 1, 1, 0};    // Upper diagonal
double[] d = {5, 5, 5, 5};    // Right-hand side

double[] x = TDMAsolve.solve(a, b, c, d);

Spline Interpolation

Cubic spline interpolation for smooth curves.

import neqsim.mathlib.generalmath.SplineInterpolation;

double[] xData = {0, 1, 2, 3, 4, 5};
double[] yData = {0, 1, 4, 9, 16, 25};  // y = x²

SplineInterpolation spline = new SplineInterpolation(xData, yData);

// Interpolate at any point
double y = spline.interpolate(2.5);  // ≈ 6.25

Common Math Functions

import neqsim.mathlib.generalmath.GeneralMath;

// Safe logarithm (handles near-zero)
double logVal = GeneralMath.safeLog(x);

// Polynomial evaluation
double[] coeffs = {1, 2, 3};  // 1 + 2x + 3x²
double polyVal = GeneralMath.polynomial(x, coeffs);

// Linear interpolation
double y = GeneralMath.linearInterpolate(x, x1, y1, x2, y2);

Matrix Operations

For matrix operations, NeqSim uses external libraries:

import org.ejml.simple.SimpleMatrix;

// Matrix multiplication
SimpleMatrix A = new SimpleMatrix(new double[][] {
    {1, 2}, {3, 4}
});
SimpleMatrix B = new SimpleMatrix(new double[][] {
    {5, 6}, {7, 8}
});
SimpleMatrix C = A.mult(B);

// Solve linear system Ax = b
double[][] bData = { {1}, {2} };
SimpleMatrix b = new SimpleMatrix(bData);
SimpleMatrix x = A.solve(b);

// Eigenvalue decomposition
SimpleEVD evd = A.eig();

Usage in NeqSim

Flash Calculations

Newton-Raphson used in flash convergence:

// Simplified flash iteration
while (error > tolerance) {
    // Calculate fugacities
    double[] fugL = calculateLiquidFugacity();
    double[] fugV = calculateVaporFugacity();

    // Newton-Raphson update for K-values
    for (int i = 0; i < nc; i++) {
        K[i] = K[i] * fugL[i] / fugV[i];
    }

    // Rachford-Rice equation
    beta = solveRachfordRice(K, z);
    error = calculateError();
}

Phase Envelope

Continuation methods for phase boundary tracking:

// Predictor-corrector method
while (pressure < maxPressure) {
    // Predict next point
    double[] predicted = predictNextPoint(direction, stepSize);

    // Correct using Newton-Raphson
    double[] corrected = correctPoint(predicted);

    // Update direction for next step
    direction = updateDirection(corrected);
}

Optimization

Minimization

// Golden section search for minimum
Function<Double, Double> f = x -> (x - 2) * (x - 2) + 1;

double a = 0, b = 5;
double tolerance = 1e-6;
double phi = (1 + Math.sqrt(5)) / 2;

while ((b - a) > tolerance) {
    double x1 = b - (b - a) / phi;
    double x2 = a + (b - a) / phi;

    if (f.apply(x1) < f.apply(x2)) {
        b = x2;
    } else {
        a = x1;
    }
}
double minimum = (a + b) / 2;  // ≈ 2.0

Multidimensional Optimization

For parameter fitting, NeqSim uses:


Convergence Criteria

Absolute Tolerance

$$|x_{n+1} - x_n| < \epsilon$$

Relative Tolerance

$$\frac{|x_{n+1} - x_n|}{|x_n|} < \epsilon$$

Function Value Tolerance

$$|f(x_n)| < \epsilon$$


Best Practices

  1. Choose appropriate solver - Newton for fast convergence, Brent for robustness
  2. Provide good initial guess - Improves convergence
  3. Set reasonable tolerances - Balance accuracy vs speed
  4. Check convergence - Verify solver actually converged
  5. Handle edge cases - Division by zero, negative values for log