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
- Varying Molecular Weight / Gas Composition
- Multi-Map MW Interpolation
- Multi-Speed Compressors
- Single-Speed Compressors
- Surge Curves
- Stone Wall (Choke) Curves
- Setting Curves Manually
- Distance to Operating Limits
- Speed Calculation from Operating Point ⭐ NEW
- Anti-Surge Control
- API Reference
- Python Examples
- Automatic Curve Generation ⭐ NEW
- Compressor Curve Templates ⭐ NEW
- Template Selection Guide
- Using the CompressorChartGenerator
- Template API Reference
- Dynamic Simulation Features ⭐ NEW
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:
- Reference temperature and pressure
- Reference gas composition (molecular weight)
- Reference density and compressibility
When the actual operating fluid differs from the reference:
- Flow capacity changes
- Head capacity changes
- Surge and stone wall limits shift
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:
- $H$ = polytropic head (kJ/kg)
- $Q$ = volumetric flow (m³/s)
- $N$ = rotational speed (rev/s)
- $D$ = impeller outer diameter (m)
- $c_s$ = sound speed of the gas (m/s)
How It Works
- Reference curves are converted to dimensionless form using the reference fluid’s sound speed
- At runtime, the actual fluid’s sound speed is calculated
- Curves are scaled back to physical units using the actual sound speed
- 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
- The Khader 2015 method assumes similar gas behavior (ideal gas-like scaling)
- Very different fluids (e.g., switching from natural gas to pure CO2) may require new base curves
- Single-phase gas is assumed; liquids or two-phase flows are not handled
- Accuracy depends on the validity of the similarity assumptions for your specific application
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:
- Automatic MW detection: The compressor sets the inlet stream on the chart during
run() - No manual MW calls needed:
setOperatingMW()is called automatically - Composition changes tracked: If gas composition changes, just run again
When to Use Multi-Map Interpolation
Use this approach when:
- You have performance data measured at several gas compositions (e.g., MW = 18, 20, 22 g/mol)
- The gas composition varies significantly during operation
- You want direct interpolation between measured maps rather than theoretical scaling
How It Works
- Add multiple maps: Each map is defined at a specific molecular weight
- Compressor run(): Automatically sets inlet stream on the chart
- Chart uses actual MW: Gets MW from
inletStream.getFluid().getMolarMass() - 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:
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();
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:
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);
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:
- Surge is a single point (minimum stable flow at the fixed speed)
- Stone wall is a single point (maximum flow at the fixed speed)
- Speed cannot be adjusted to move away from operating limits
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:
- Spline interpolation for smooth curve fitting
- Safe linear extrapolation beyond curve limits
- Support for single-point surge (single-speed compressors)
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:
- Spline interpolation for smooth curve fitting
- Safe linear extrapolation beyond curve limits
- Support for single-point stone wall (single-speed compressors)
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:
-
Fan-law Initial Guess: Uses the relationship $H \propto N^2$ to estimate: \(N_{guess} = N_{ref} \times \sqrt{\frac{H_{target}}{H_{ref}}}\)
- 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
- Bounds Protection: Prevents divergence:
- Lower bound: 50% of minimum curve speed
- Upper bound: 150% of maximum curve speed
- 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:
- Head scaling: $H \propto N^2$ (head scales with speed squared)
- Flow scaling: $Q \propto N$ (flow scales linearly with speed)
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 State Machine: Track operating states (STOPPED, STARTING, RUNNING, SURGE_PROTECTION, etc.)
- Event-Driven Control: Listeners for surge approach, speed limits, power limits, state changes
- Driver Modeling: Electric motors, gas turbines, steam turbines with power limits and efficiency curves
- Inertia Modeling: Realistic acceleration/deceleration with rate limits
- Startup/Shutdown Profiles: Sequenced startup and shutdown procedures
- Operating History: Track and export operating points for post-simulation analysis
- Performance Degradation: Model fouling and performance reduction over time
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()))
Related Documentation
- Compressor Equipment - Basic compressor usage
- Anti-Surge Control - Anti-surge system details
- Process Simulation - Process simulation overview