Optimizer Plugin Architecture
New to process optimization? Start with the Optimization Overview to understand when to use which optimizer.
Overview
The Optimizer Plugin Architecture provides a flexible, extensible framework for evaluating equipment capacity constraints and optimizing process throughput. It enables automated bottleneck detection, lift curve generation, and integration with reservoir simulators like Eclipse.
Related Documentation
| Document | Description |
|---|---|
| Optimization Overview | When to use which optimizer |
| Production Optimization Guide | ProductionOptimizer examples |
| Multi-Objective Optimization | Pareto fronts and trade-offs |
| Flow Rate Optimization | FlowRateOptimizer and lift curves |
| Capacity Constraint Framework | Equipment constraints |
Key Components
| Component | Description | Location |
|---|---|---|
| EquipmentCapacityStrategy | Interface for equipment-specific constraint evaluation | neqsim.process.equipment.capacity |
| EquipmentCapacityStrategyRegistry | Singleton registry with auto-discovery | neqsim.process.equipment.capacity |
| ProcessOptimizationEngine | Unified API for process optimization | neqsim.process.util.optimizer |
| EclipseVFPExporter | Eclipse VFP table generation | neqsim.process.util.optimizer |
| Driver Package | Driver curves for compressors | neqsim.process.equipment.compressor.driver |
| OperatingEnvelope | Compressor operating envelope tracking | neqsim.process.equipment.compressor |
Architecture Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ ProcessOptimizationEngine │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ findMaximumThroughput() │ evaluateAllConstraints() │ generateLiftCurve()││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ EquipmentCapacityStrategyRegistry (Singleton) ││
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││
│ │ │ Compressor │ │ Separator │ │ Pump │ │ Valve │ ││
│ │ │ Strategy │ │ Strategy │ │ Strategy │ │ Strategy │ ││
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ ││
│ │ ┌─────────────┐ ┌─────────────┐ ││
│ │ │ Pipe │ │ HeatExchgr │ + Custom Strategies (register) ││
│ │ │ Strategy │ │ Strategy │ ││
│ │ └─────────────┘ └─────────────┘ ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ CapacityConstraint ││
│ │ name │ unit │ type │ designValue │ maxValue │ valueSupplier │ severity ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ EclipseVFPExporter │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ VFPPROD │ │ VFPINJ │ │ VFPEXP │ │
│ │ (Production) │ │ (Injection) │ │ (Export) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Quick Start
Basic Usage: Evaluate Process Constraints
import neqsim.process.util.optimizer.ProcessOptimizationEngine;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;
// Create process
SystemInterface gas = new SystemSrkEos(288.15, 50.0);
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.10);
gas.addComponent("propane", 0.05);
gas.setMixingRule("classic");
Stream feed = new Stream("feed", gas);
feed.setFlowRate(100000, "kg/hr");
feed.setPressure(50.0, "bara");
feed.setTemperature(288.15, "K");
Separator separator = new Separator("HP Separator", feed);
Compressor compressor = new Compressor("Export Compressor", separator.getGasOutStream());
compressor.setOutletPressure(120.0);
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(compressor);
process.run();
// Create optimization engine
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(process);
// Evaluate all constraints
ProcessOptimizationEngine.ConstraintReport report = engine.evaluateAllConstraints();
// Print equipment status
for (ProcessOptimizationEngine.EquipmentConstraintStatus status : report.getEquipmentStatuses()) {
System.out.println(status.getEquipmentName() + ": " +
String.format("%.1f%%", status.getUtilization() * 100) + " utilization");
if (!status.isWithinLimits()) {
System.out.println(" WARNING: Exceeds limits!");
}
}
// Find bottleneck
String bottleneck = engine.findBottleneckEquipment();
System.out.println("Bottleneck equipment: " + bottleneck);
Find Maximum Throughput
// Find maximum flow rate for given pressure constraints
double inletPressure = 50.0; // bara
double outletPressure = 40.0; // bara
double minFlow = 10000.0; // kg/hr
double maxFlow = 500000.0; // kg/hr
ProcessOptimizationEngine.OptimizationResult result =
engine.findMaximumThroughput(inletPressure, outletPressure, minFlow, maxFlow);
System.out.println("Optimal flow rate: " + result.getOptimalFlowRate() + " kg/hr");
System.out.println("Feasible: " + result.isFeasible());
System.out.println("Bottleneck: " + result.getBottleneckEquipment());
System.out.println("Total power: " + result.getTotalPower() + " kW");
// Get constraint violations if any
for (String violation : result.getConstraintViolations()) {
System.out.println(" Violation: " + violation);
}
Equipment Capacity Strategies
Strategy Interface
Each equipment type has a dedicated strategy that understands its specific constraints:
public interface EquipmentCapacityStrategy {
// Check if strategy supports this equipment
boolean supports(ProcessEquipmentInterface equipment);
// Get strategy priority (higher = more specific)
int getPriority();
// Evaluate current capacity utilization (0.0 to 1.0+)
double evaluateCapacity(ProcessEquipmentInterface equipment);
// Get all constraints for equipment
Map<String, CapacityConstraint> getConstraints(ProcessEquipmentInterface equipment);
// Get violated constraints
List<CapacityConstraint> getViolations(ProcessEquipmentInterface equipment);
// Get the limiting constraint
CapacityConstraint getBottleneckConstraint(ProcessEquipmentInterface equipment);
// Check if within hard limits (safety)
boolean isWithinHardLimits(ProcessEquipmentInterface equipment);
// Check if within soft limits (design)
boolean isWithinSoftLimits(ProcessEquipmentInterface equipment);
}
Built-in Strategies
1. CompressorCapacityStrategy
Evaluates compressor constraints including:
| Constraint | Type | Description |
|---|---|---|
speed |
HARD | Rotational speed vs max/min limits |
power |
HARD | Shaft power vs driver capacity |
surgeMargin |
HARD | Distance to surge line |
stonewallMargin |
SOFT | Distance to stonewall |
dischargeTemperature |
HARD | Outlet temperature vs limits |
import neqsim.process.equipment.capacity.CompressorCapacityStrategy;
// Create with custom limits
CompressorCapacityStrategy strategy = new CompressorCapacityStrategy(
0.10, // minSurgeMargin (10%)
0.05, // minStonewallMargin (5%)
200.0 // maxDischargeTemp (°C)
);
// Evaluate compressor
Map<String, CapacityConstraint> constraints = strategy.getConstraints(compressor);
// Check surge margin
CapacityConstraint surgeConstraint = constraints.get("surgeMargin");
if (surgeConstraint != null) {
System.out.println("Surge margin: " + surgeConstraint.getCurrentValue() + "%");
System.out.println("Minimum required: " + surgeConstraint.getMinValue() + "%");
}
2. SeparatorCapacityStrategy
Evaluates separator constraints:
| Constraint | Type | Description |
|---|---|---|
liquidLevel |
SOFT | Liquid level vs max allowed |
gasLoadFactor |
SOFT | Gas velocity/terminal velocity ratio |
import neqsim.process.equipment.capacity.SeparatorCapacityStrategy;
SeparatorCapacityStrategy strategy = new SeparatorCapacityStrategy(
0.80, // maxLiquidLevel (80%)
0.10 // maxGasLoadFactor (K-factor)
);
Map<String, CapacityConstraint> constraints = strategy.getConstraints(separator);
3. PumpCapacityStrategy
Evaluates pump constraints:
| Constraint | Type | Description |
|---|---|---|
power |
HARD | Motor power vs rating |
npshMargin |
HARD | NPSH available - required |
flowRate |
SOFT | Flow vs minimum flow |
import neqsim.process.equipment.capacity.PumpCapacityStrategy;
PumpCapacityStrategy strategy = new PumpCapacityStrategy(
1.0, // minNpshMargin (1.0 m)
1.1 // maxPowerFactor (110% overload allowed)
);
4. ValveCapacityStrategy
Evaluates valve constraints:
| Constraint | Type | Description |
|---|---|---|
valveOpening |
SOFT | Opening % vs min/max range |
pressureDropRatio |
SOFT | ΔP/inlet pressure ratio |
5. PipeCapacityStrategy
Evaluates pipe/pipeline constraints:
| Constraint | Type | Description |
|---|---|---|
velocity |
SOFT | Superficial velocity vs erosional |
pressureDrop |
SOFT | Pressure drop vs allowable |
6. HeatExchangerCapacityStrategy
Evaluates heat exchanger constraints:
| Constraint | Type | Description |
|---|---|---|
duty |
SOFT | Heat transfer duty vs design |
outletTemperature |
SOFT | Outlet temperature |
Custom Strategy Registration
Register custom strategies for specialized equipment:
import neqsim.process.equipment.capacity.EquipmentCapacityStrategyRegistry;
import neqsim.process.equipment.capacity.EquipmentCapacityStrategy;
// Create custom strategy
public class MyCustomEquipmentStrategy implements EquipmentCapacityStrategy {
@Override
public boolean supports(ProcessEquipmentInterface equipment) {
return equipment instanceof MyCustomEquipment;
}
@Override
public int getPriority() {
return 100; // High priority for specific equipment
}
@Override
public double evaluateCapacity(ProcessEquipmentInterface equipment) {
MyCustomEquipment eq = (MyCustomEquipment) equipment;
return eq.getCurrentLoad() / eq.getMaxLoad();
}
@Override
public Map<String, CapacityConstraint> getConstraints(ProcessEquipmentInterface equipment) {
Map<String, CapacityConstraint> constraints = new HashMap<>();
MyCustomEquipment eq = (MyCustomEquipment) equipment;
constraints.put("customConstraint",
new CapacityConstraint("customConstraint", "units", ConstraintType.HARD)
.setDesignValue(100.0)
.setMaxValue(120.0)
.setValueSupplier(() -> eq.getCurrentValue()));
return constraints;
}
// ... implement other methods
}
// Register with registry
EquipmentCapacityStrategyRegistry registry = EquipmentCapacityStrategyRegistry.getInstance();
registry.registerStrategy(new MyCustomEquipmentStrategy());
Driver Package
The driver package provides compressor driver models with performance curves.
DriverCurve Interface
public interface DriverCurve {
// Get available power at current conditions
double getMaxAvailablePower();
// Get rated power
double getRatedPower();
// Calculate efficiency at given load
double getEfficiency(double loadFraction);
// Calculate fuel/energy consumption
double getFuelConsumption(double power);
// Calculate speed change during transients
double calculateSpeedChange(double currentSpeed, double targetSpeed,
double power, double timeStep);
}
GasTurbineDriver
Models gas turbine drivers with ambient derating:
import neqsim.process.equipment.compressor.driver.GasTurbineDriver;
// Create gas turbine driver
GasTurbineDriver driver = new GasTurbineDriver();
driver.setRatedPower(15000.0); // 15 MW rated
driver.setRatedSpeed(10000.0); // 10,000 RPM
driver.setRatedEfficiency(0.35); // 35% thermal efficiency
driver.setIsoConditionsTemperature(288.15); // ISO 15°C
driver.setIsoConditionsAltitude(0.0); // Sea level
// Set current ambient conditions
driver.setAmbientTemperature(303.15); // 30°C (hot day)
driver.setAltitude(500.0); // 500m elevation
// Get derated power
double availablePower = driver.getMaxAvailablePower();
System.out.println("Available power (derated): " + availablePower + " kW");
// Output: ~13,500 kW (derated from 15,000 due to high temp and altitude)
// Calculate fuel consumption
double fuelGas = driver.getFuelConsumption(10000.0); // At 10 MW load
System.out.println("Fuel gas: " + fuelGas + " kg/hr");
ElectricMotorDriver
Models electric motor drivers with VFD support:
import neqsim.process.equipment.compressor.driver.ElectricMotorDriver;
// Create electric motor
ElectricMotorDriver motor = new ElectricMotorDriver();
motor.setRatedPower(5000.0); // 5 MW
motor.setRatedSpeed(3000.0); // 3000 RPM (2-pole, 50 Hz)
motor.setRatedEfficiency(0.96); // 96% efficiency
motor.setVariableSpeedDrive(true); // VFD installed
motor.setMinSpeed(600.0); // 20% min speed with VFD
motor.setMaxSpeed(3600.0); // 120% max speed
// Get efficiency at partial load
double efficiency = motor.getEfficiency(0.75); // 75% load
System.out.println("Efficiency at 75% load: " + efficiency * 100 + "%");
SteamTurbineDriver
Models steam turbine drivers with Willans line:
import neqsim.process.equipment.compressor.driver.SteamTurbineDriver;
SteamTurbineDriver turbine = new SteamTurbineDriver();
turbine.setRatedPower(8000.0); // 8 MW
turbine.setInletPressure(40.0); // 40 bara steam
turbine.setInletTemperature(673.15); // 400°C superheat
turbine.setExhaustPressure(4.0); // 4 bara exhaust
turbine.setIsentropicEfficiency(0.78); // 78% isentropic efficiency
// Calculate steam consumption
double steamFlow = turbine.getSteamConsumption(6000.0); // At 6 MW
System.out.println("Steam consumption: " + steamFlow + " kg/hr");
Compressor Operating Envelope
Track and validate compressor operation against surge/stonewall limits:
import neqsim.process.equipment.compressor.OperatingEnvelope;
// Create envelope from compressor map data
OperatingEnvelope envelope = new OperatingEnvelope();
// Add surge line points (flow, head)
envelope.addSurgePoint(500.0, 80000.0);
envelope.addSurgePoint(700.0, 100000.0);
envelope.addSurgePoint(900.0, 115000.0);
envelope.addSurgePoint(1100.0, 125000.0);
// Add stonewall line points
envelope.addStonewallPoint(1800.0, 60000.0);
envelope.addStonewallPoint(2200.0, 80000.0);
envelope.addStonewallPoint(2600.0, 95000.0);
envelope.addStonewallPoint(3000.0, 105000.0);
// Set speed limits
envelope.setMinSpeed(7000.0); // RPM
envelope.setMaxSpeed(11000.0); // RPM
// Check operating point
double flow = 1200.0; // Am3/hr
double head = 95000.0; // J/kg
double speed = 9500.0; // RPM
boolean withinEnvelope = envelope.isWithinEnvelope(flow, head, speed);
double surgeMargin = envelope.getSurgeMargin(flow, head);
double stonewallMargin = envelope.getStonewallMargin(flow, head);
System.out.println("Within envelope: " + withinEnvelope);
System.out.println("Surge margin: " + surgeMargin * 100 + "%");
System.out.println("Stonewall margin: " + stonewallMargin * 100 + "%");
// Get limiting constraint
String limitingConstraint = envelope.getLimitingConstraint(flow, head, speed);
System.out.println("Limiting: " + limitingConstraint);
Compressor Constraint Configuration
Configure comprehensive compressor constraints:
import neqsim.process.equipment.compressor.CompressorConstraintConfig;
// Create configuration
CompressorConstraintConfig config = new CompressorConstraintConfig();
// Surge/stonewall margins
config.setMinSurgeMargin(0.10); // 10% minimum surge margin
config.setMinStonewallMargin(0.05); // 5% minimum stonewall margin
// Speed limits
config.setMinSpeed(5000.0); // Minimum RPM
config.setMaxSpeed(11000.0); // Maximum RPM
// Power limits
config.setMaxPower(15000.0); // kW max shaft power
// Temperature limits
config.setMaxDischargeTemperature(200.0); // °C
config.setMaxSuctionTemperature(60.0); // °C
// API 617 compliance
config.setApi617Compliant(true);
// Use factory methods for standard configurations
CompressorConstraintConfig conservative = CompressorConstraintConfig.createConservativeConfig();
CompressorConstraintConfig aggressive = CompressorConstraintConfig.createAggressiveConfig();
CompressorConstraintConfig api617 = CompressorConstraintConfig.createAPI617Config();
Eclipse VFP Export
Generate VFP tables for reservoir simulation. Capacity constraints directly affect the maximum flow rates in VFP tables.
📘 See Also: Capacity Constraint Framework - VFP Section for detailed documentation on constraint management for VFP studies.
How Constraints Affect VFP Tables
When generating VFP tables, the optimizer finds the maximum flow rate at each operating point where:
- Process converges thermodynamically
- All capacity constraints are satisfied (utilization ≤ 100%)
- No HARD limits are exceeded
Operating Point (Pin, Pout, WC, GOR) → Binary Search → Max Flow Rate
↓
Check ALL equipment constraints
↓
Bottleneck determines limit
Constraint Configuration for VFP
import neqsim.process.util.optimizer.EclipseVFPExporter;
// Create process with constrained equipment
ProcessSystem process = createProcess();
// Configure constraints BEFORE generating VFP
Compressor comp = (Compressor) process.getUnit("Export Compressor");
comp.autoSize(1.2); // Sets speed, power, surge constraints
Separator sep = (Separator) process.getUnit("HP Separator");
sep.autoSize(1.2); // Sets gasLoadFactor constraint
// Optionally modify constraints for study
CapacityConstraint speedLimit = comp.getCapacityConstraints().get("speed");
speedLimit.setDesignValue(9000.0); // More conservative speed limit
// Create exporter - will respect all active constraints
EclipseVFPExporter exporter = new EclipseVFPExporter(process);
exporter.setTableNumber(1);
exporter.setConstraintEnforcement(true); // Enable constraint checking (default: true)
VFPPROD Tables (Production Wells)
// Define parameter ranges
double[] thp = {20.0, 30.0, 40.0, 50.0}; // Tubing head pressures (bara)
double[] wfr = {0.0, 0.1, 0.3, 0.5}; // Water fractions
double[] gfr = {100.0, 200.0, 500.0, 1000.0}; // GOR (Sm3/Sm3)
double[] alq = {0.0}; // Artificial lift (none)
double[] flowRates = {1000.0, 5000.0, 10000.0, 20000.0, 50000.0}; // kg/hr
// Generate VFPPROD table - max flow at each point limited by constraints
String vfpTable = exporter.generateVFPPROD(
thp, wfr, gfr, alq, flowRates,
"bara", "kg/hr"
);
// Write to file
Files.writeString(Path.of("VFPPROD_WELL1.INC"), vfpTable);
VFPINJ Tables (Injection Wells)
// Generate VFPINJ table for water injection
double[] injPressures = {100.0, 150.0, 200.0, 250.0}; // BHP
double[] injRates = {5000.0, 10000.0, 20000.0}; // m3/day
String vfpInj = exporter.generateVFPINJ(
thp, injPressures, injRates,
"bara", "m3/day"
);
VFPEXP Tables (Export Pipelines)
// Generate VFPEXP for export pipeline
double[] inletPressures = {50.0, 60.0, 70.0, 80.0};
double[] outletPressures = {40.0, 45.0, 50.0};
double[] temperatures = {20.0, 40.0, 60.0};
String vfpExp = exporter.generateVFPEXP(
inletPressures, outletPressures, temperatures, flowRates,
"bara", "C", "kg/hr"
);
What-If Studies: Modifying Constraints for VFP Scenarios
// Scenario 1: Baseline VFP with current constraints
String baselineVFP = exporter.generateVFPPROD(thp, wfr, gfr, alq, flowRates, "bara", "kg/hr");
Files.writeString(Path.of("VFP_BASELINE.INC"), baselineVFP);
// Scenario 2: Debottleneck compressor (increase speed limit)
CapacityConstraint speedConstraint = comp.getCapacityConstraints().get("speed");
double originalSpeed = speedConstraint.getDesignValue();
speedConstraint.setDesignValue(originalSpeed * 1.1); // 10% higher
String debottleneckedVFP = exporter.generateVFPPROD(thp, wfr, gfr, alq, flowRates, "bara", "kg/hr");
Files.writeString(Path.of("VFP_DEBOTTLENECKED.INC"), debottleneckedVFP);
speedConstraint.setDesignValue(originalSpeed); // Restore
// Scenario 3: No equipment constraints (thermodynamic limits only)
comp.clearCapacityConstraints();
sep.clearCapacityConstraints();
String unconstrainedVFP = exporter.generateVFPPROD(thp, wfr, gfr, alq, flowRates, "bara", "kg/hr");
Files.writeString(Path.of("VFP_UNCONSTRAINED.INC"), unconstrainedVFP);
// Restore constraints
comp.initializeCapacityConstraints();
sep.initializeCapacityConstraints();
VFP Generation with Bottleneck Reporting
// Generate VFP with detailed bottleneck information
VFPGenerationResult result = exporter.generateVFPPRODWithDetails(
thp, wfr, gfr, alq, flowRates, "bara", "kg/hr");
// Access VFP table
String vfpTable = result.getVFPTable();
// Access bottleneck analysis
for (VFPPoint point : result.getPoints()) {
if (point.isConstrained()) {
System.out.printf("At THP=%.0f, WC=%.1f, GOR=%.0f: " +
"Max=%.0f kg/hr, Limited by %s (%s)%n",
point.getTHP(), point.getWaterCut(), point.getGOR(),
point.getMaxFlowRate(),
point.getBottleneckEquipment(),
point.getBottleneckConstraint());
}
}
ProcessOptimizationEngine API Reference
Constructors
// For ProcessSystem
public ProcessOptimizationEngine(ProcessSystem processSystem)
// For ProcessModule (supports nested modules)
public ProcessOptimizationEngine(ProcessModule processModule)
Creates optimization engine for the given process system or module.
ProcessModule Support
The ProcessOptimizationEngine fully supports ProcessModule, which can contain multiple ProcessSystem instances and nested modules. All optimization methods work recursively across the entire module hierarchy.
import neqsim.process.util.optimizer.ProcessOptimizationEngine;
import neqsim.process.processmodel.ProcessModule;
import neqsim.process.processmodel.ProcessSystem;
// Create a module with multiple systems
ProcessModule fieldModule = new ProcessModule("Field Development");
ProcessSystem subseaSystem = new ProcessSystem();
// ... add subsea equipment ...
fieldModule.add(subseaSystem);
ProcessSystem topsideSystem = new ProcessSystem();
// ... add topside equipment ...
fieldModule.add(topsideSystem);
fieldModule.run();
// Create engine with ProcessModule
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(fieldModule);
// Specify which feed stream to vary (searches across ALL systems in module)
engine.setFeedStreamName("WellheadFeed");
// Find maximum throughput - evaluates constraints across entire module
OptimizationResult result = engine.findMaximumThroughput(
50.0, // inlet pressure (bara)
40.0, // outlet pressure (bara)
10000.0, // min flow (kg/hr)
500000.0 // max flow (kg/hr)
);
// Check which stream is being varied
System.out.println("Feed stream: " + engine.getFeedStreamName());
Feed Stream Configuration
By default, the optimization engine varies the first unit operation in the process. For complex processes or modules, you should explicitly specify the feed stream:
| Method | Description |
|---|---|
setFeedStreamName(String name) |
Set the name of the stream to vary during optimization |
getFeedStreamName() |
Get the name of the stream being varied |
// Explicitly set which stream to vary
engine.setFeedStreamName("InletManifold");
// Method chaining is supported
OptimizationResult result = engine
.setFeedStreamName("WellStream")
.findMaximumThroughput(50.0, 40.0, 1000.0, 100000.0);
// Verify which stream is being used
System.out.println("Optimizing flow rate of: " + engine.getFeedStreamName());
Outlet Stream Configuration
By default, the optimization engine monitors the last unit operation for outlet conditions. For complex processes or modules, you can explicitly specify the outlet stream:
| Method | Description |
|---|---|
setOutletStreamName(String name) |
Set the name of the outlet stream to monitor |
getOutletStreamName() |
Get the name of the outlet stream being monitored |
getOutletTemperature() |
Get outlet temperature in Kelvin |
getOutletTemperature(String unit) |
Get outlet temperature in specified unit (“C”, “K”, “F”, “R”) |
getOutletFlowRate(String flowUnit) |
Get outlet flow rate in specified unit (“kg/hr”, “MSm3/day”) |
// Configure both feed and outlet streams
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(fieldModule);
engine.setFeedStreamName("Well Feed"); // Input stream to vary
engine.setOutletStreamName("Export Gas"); // Output stream to monitor
// Run optimization
OptimizationResult result = engine.findMaximumThroughput(50.0, 40.0, 1000.0, 100000.0);
// Get outlet conditions from the specified stream
double outletTemp = engine.getOutletTemperature("C");
double outletFlow = engine.getOutletFlowRate("MSm3/day");
System.out.println("Export temperature: " + outletTemp + " °C");
System.out.println("Export flow rate: " + outletFlow + " MSm3/day");
ProcessModule Example with Feed and Outlet Streams
// Module with multiple process systems
ProcessModule facilityModule = new ProcessModule("Offshore Facility");
// Subsea system
ProcessSystem subseaSystem = new ProcessSystem("Subsea");
subseaSystem.add(new Stream("Wellhead Feed", wellFluid));
subseaSystem.add(new AdiabaticPipe("Flowline", subseaSystem.getUnit("Wellhead Feed")));
facilityModule.add(subseaSystem);
// Topside system
ProcessSystem topsideSystem = new ProcessSystem("Topside");
topsideSystem.add(new Separator("HP Separator", subseaSystem.getUnit("Flowline")));
topsideSystem.add(new Compressor("Export Compressor", topsideSystem.getUnit("HP Separator")));
topsideSystem.add(new Stream("Export Gas", topsideSystem.getUnit("Export Compressor")));
facilityModule.add(topsideSystem);
facilityModule.run();
// Optimize with explicit feed/outlet
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(facilityModule);
engine.setFeedStreamName("Wellhead Feed"); // From subseaSystem
engine.setOutletStreamName("Export Gas"); // From topsideSystem
// Optimization searches across ALL systems in the module
OptimizationResult result = engine.findMaximumThroughput(85.0, 40.0, 5000.0, 200000.0);
Core Methods
| Method | Returns | Description |
|---|---|---|
evaluateAllConstraints() |
ConstraintReport |
Evaluate constraints on all equipment |
findMaximumThroughput(pin, pout, minQ, maxQ) |
OptimizationResult |
Find max flow for pressure constraints |
findRequiredInletPressure(outletP, flowRate) |
OptimizationResult |
Find inlet pressure for target flow |
findBottleneckEquipment() |
String |
Get name of bottleneck equipment |
generateLiftCurve(pins, pouts, temps, wcuts, gors) |
LiftCurveData |
Generate multi-dimensional lift curve |
analyzeSensitivity(flow, inletP, outletP) |
SensitivityResult |
Analyze flow sensitivity and margins |
calculateShadowPrices(flow, inletP, outletP) |
Map<String, Double> |
Calculate constraint shadow prices |
createFlowRateOptimizer() |
FlowRateOptimizer |
Create integrated FlowRateOptimizer |
generateComprehensiveLiftCurve(stream, pressures, outletP) |
FlowRateOptimizer |
Generate lift curves via FlowRateOptimizer |
evaluateConstraintsWithCache() |
ConstraintEvaluationResult |
Evaluate with caching enabled |
calculateFlowSensitivities(flow, unit) |
Map<String, Double> |
Calculate flow sensitivities by equipment |
estimateMaximumFlow(currentFlow, unit) |
double |
Estimate max feasible flow |
getConstraintEvaluator() |
ProcessConstraintEvaluator |
Get underlying constraint evaluator |
OptimizationResult Class
public class OptimizationResult {
double getOptimalFlowRate(); // Optimal flow in kg/hr
boolean isFeasible(); // True if constraints satisfied
String getBottleneckEquipment(); // Name of limiting equipment
double getTotalPower(); // Total power consumption (kW)
List<String> getConstraintViolations(); // List of violations
}
ConstraintReport Class
public class ConstraintReport {
List<EquipmentConstraintStatus> getEquipmentStatuses();
boolean hasViolations();
String getBottleneckEquipment();
double getOverallUtilization();
}
EquipmentConstraintStatus Class
public class EquipmentConstraintStatus {
String getEquipmentName();
String getEquipmentType();
double getUtilization(); // 0.0 to 1.0+
boolean isWithinLimits();
String getBottleneckConstraint(); // Name of limiting constraint
List<CapacityConstraint> getConstraints();
}
Integration Examples
Example 1: Production Optimization with Constraints
// Complete production optimization example
public class ProductionOptimizationExample {
public static void main(String[] args) {
// Create wellstream fluid
SystemInterface wellFluid = new SystemSrkEos(330.0, 80.0);
wellFluid.addComponent("methane", 0.70);
wellFluid.addComponent("ethane", 0.08);
wellFluid.addComponent("propane", 0.05);
wellFluid.addComponent("n-butane", 0.03);
wellFluid.addComponent("n-pentane", 0.02);
wellFluid.addComponent("nC10", 0.07);
wellFluid.addComponent("water", 0.05);
wellFluid.setMixingRule("classic");
wellFluid.setMultiPhaseCheck(true);
// Create process
Stream wellStream = new Stream("wellStream", wellFluid);
wellStream.setFlowRate(50000, "kg/hr");
wellStream.setPressure(80.0, "bara");
wellStream.setTemperature(330.0, "K");
ThreePhaseSeparator hpSeparator = new ThreePhaseSeparator("HP Separator", wellStream);
Heater gasHeater = new Heater("Gas Heater", hpSeparator.getGasOutStream());
gasHeater.setOutTemperature(320.0);
Compressor exportCompressor = new Compressor("Export Compressor", gasHeater.getOutletStream());
exportCompressor.setOutletPressure(150.0);
exportCompressor.setPolytropicEfficiency(0.78);
Cooler aftercooler = new Cooler("Aftercooler", exportCompressor.getOutletStream());
aftercooler.setOutTemperature(313.15);
ProcessSystem process = new ProcessSystem();
process.add(wellStream);
process.add(hpSeparator);
process.add(gasHeater);
process.add(exportCompressor);
process.add(aftercooler);
process.run();
// Create optimization engine
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(process);
// Evaluate current constraints
ProcessOptimizationEngine.ConstraintReport report = engine.evaluateAllConstraints();
System.out.println("=== Current Operating Status ===");
for (ProcessOptimizationEngine.EquipmentConstraintStatus status : report.getEquipmentStatuses()) {
System.out.printf("%s: %.1f%% utilization%n",
status.getEquipmentName(), status.getUtilization() * 100);
for (CapacityConstraint constraint : status.getConstraints()) {
System.out.printf(" - %s: %.2f %s (%.1f%% of design)%n",
constraint.getName(),
constraint.getCurrentValue(),
constraint.getUnit(),
constraint.getUtilizationPercent());
}
}
// Find maximum throughput
System.out.println("\n=== Optimization Results ===");
ProcessOptimizationEngine.OptimizationResult result =
engine.findMaximumThroughput(80.0, 150.0, 10000.0, 200000.0);
System.out.printf("Maximum throughput: %.0f kg/hr%n", result.getOptimalFlowRate());
System.out.printf("Bottleneck: %s%n", result.getBottleneckEquipment());
System.out.printf("Total power: %.1f kW%n", result.getTotalPower());
if (!result.getConstraintViolations().isEmpty()) {
System.out.println("Constraint violations at max rate:");
for (String violation : result.getConstraintViolations()) {
System.out.println(" - " + violation);
}
}
}
}
Example 2: Lift Curve Generation for Eclipse
// Generate lift curves for reservoir simulation
public class LiftCurveExample {
public static void main(String[] args) {
// ... create process as above ...
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(process);
// Define parameter ranges for lift curve
double[] wellheadPressures = {30.0, 40.0, 50.0, 60.0, 70.0, 80.0};
double[] separatorPressures = {20.0, 25.0, 30.0};
double[] temperatures = {20.0, 40.0, 60.0};
double[] waterCuts = {0.0, 0.1, 0.3, 0.5};
double[] gors = {100.0, 200.0, 500.0};
// Generate lift curve data
ProcessOptimizationEngine.LiftCurveData liftCurve =
engine.generateLiftCurve(
wellheadPressures,
separatorPressures,
temperatures,
waterCuts,
gors
);
// Export to Eclipse format
EclipseVFPExporter exporter = new EclipseVFPExporter(process);
exporter.setTableNumber(1);
String vfpTable = exporter.generateVFPPROD(
wellheadPressures,
waterCuts,
gors,
new double[]{0.0}, // No artificial lift
new double[]{10000, 20000, 50000, 100000, 150000},
"bara",
"kg/hr"
);
// Save to file
Files.writeString(Path.of("VFPPROD_TABLE1.INC"), vfpTable);
System.out.println("VFP table written to VFPPROD_TABLE1.INC");
}
}
Example 3: Using Strategy Registry Directly
// Direct strategy usage for custom analysis
public class StrategyUsageExample {
public static void main(String[] args) {
// Get registry singleton
EquipmentCapacityStrategyRegistry registry =
EquipmentCapacityStrategyRegistry.getInstance();
// Find strategy for specific equipment
Compressor compressor = new Compressor("test", feedStream);
compressor.setOutletPressure(100.0);
compressor.run();
EquipmentCapacityStrategy strategy = registry.findStrategy(compressor);
if (strategy != null) {
// Get all constraints
Map<String, CapacityConstraint> constraints = strategy.getConstraints(compressor);
System.out.println("Compressor Constraints:");
for (Map.Entry<String, CapacityConstraint> entry : constraints.entrySet()) {
CapacityConstraint c = entry.getValue();
System.out.printf(" %s: %.2f / %.2f %s (%.1f%%)%n",
c.getName(),
c.getCurrentValue(),
c.getDesignValue(),
c.getUnit(),
c.getUtilizationPercent());
}
// Check for violations
List<CapacityConstraint> violations = strategy.getViolations(compressor);
if (!violations.isEmpty()) {
System.out.println("\nViolations:");
for (CapacityConstraint v : violations) {
System.out.printf(" %s: %.2f exceeds %.2f %s%n",
v.getName(), v.getCurrentValue(), v.getDesignValue(), v.getUnit());
}
}
// Get bottleneck
CapacityConstraint bottleneck = strategy.getBottleneckConstraint(compressor);
if (bottleneck != null) {
System.out.println("\nBottleneck constraint: " + bottleneck.getName());
}
}
}
}
Unified Result Classes
OptimizationResultBase
The OptimizationResultBase class provides a unified structure for all optimization results:
import neqsim.process.util.optimizer.OptimizationResultBase;
// Create result and track optimization
OptimizationResultBase result = new OptimizationResultBase();
result.markStart(); // Start timing
result.setObjective("MaxThroughput");
// During optimization
for (int i = 0; i < maxIterations; i++) {
result.incrementIterations();
result.incrementFunctionEvaluations();
// ... optimization logic ...
}
// Record results
result.setOptimalValue(5500.0);
result.addOptimalValue("FlowRate", 5500.0);
result.setObjectiveValue(5500.0);
result.setBottleneckEquipment("Compressor1");
result.setBottleneckConstraint("MaxPower");
result.setConverged(true);
result.markEnd(); // End timing
// Get summary
System.out.println(result.getSummary());
System.out.println("Elapsed time: " + result.getElapsedTimeSeconds() + " s");
Status Enum
The Status enum tracks optimization state:
| Status | Description |
|---|---|
NOT_STARTED |
Optimization not yet begun |
IN_PROGRESS |
Currently running |
CONVERGED |
Successfully converged |
MAX_ITERATIONS_REACHED |
Hit iteration limit |
INFEASIBLE |
No feasible solution found |
FAILED |
Error during optimization |
CANCELLED |
User cancelled |
ConstraintViolation Class
Track constraint violations with detailed information:
OptimizationResultBase.ConstraintViolation violation =
new OptimizationResultBase.ConstraintViolation(
"Compressor1", // equipment name
"MaxPower", // constraint name
15.0, // current value
12.0, // limit value
"MW", // unit
true // is hard constraint
);
System.out.println("Violation: " + violation.getViolationAmount()); // 3.0 MW over
System.out.println("Percent over: " + violation.getViolationPercent() + "%"); // 25%
ProcessConstraintEvaluator
The ProcessConstraintEvaluator provides composite constraint evaluation with caching and sensitivity analysis.
Basic Usage
import neqsim.process.util.optimizer.ProcessConstraintEvaluator;
// Create evaluator
ProcessConstraintEvaluator evaluator = new ProcessConstraintEvaluator(processSystem);
// Evaluate all constraints
ProcessConstraintEvaluator.ConstraintEvaluationResult result = evaluator.evaluate();
System.out.println("Overall utilization: " + result.getOverallUtilization() * 100 + "%");
System.out.println("Bottleneck: " + result.getBottleneckEquipment());
System.out.println("Feasible: " + result.isFeasible());
System.out.println("Violations: " + result.getTotalViolationCount());
// Get per-equipment summaries
for (Map.Entry<String, ProcessConstraintEvaluator.EquipmentConstraintSummary> entry :
result.getEquipmentSummaries().entrySet()) {
ProcessConstraintEvaluator.EquipmentConstraintSummary summary = entry.getValue();
System.out.printf("%s: %.1f%% utilization, margin to limit: %.1f%%%n",
summary.getEquipmentName(),
summary.getUtilization() * 100,
summary.getMarginToLimit() * 100);
}
Constraint Caching
Enable caching for repeated evaluations:
// Configure cache TTL (default 10 seconds)
evaluator.setCacheTTLMillis(30000); // 30 seconds
// Evaluate with caching
ProcessConstraintEvaluator.ConstraintEvaluationResult result1 = evaluator.evaluate();
// ... process runs again ...
ProcessConstraintEvaluator.ConstraintEvaluationResult result2 = evaluator.evaluate(); // Uses cache if valid
// Clear cache when needed
evaluator.clearCache();
CachedConstraints Class
Manual cache management:
ProcessConstraintEvaluator.CachedConstraints cache =
new ProcessConstraintEvaluator.CachedConstraints();
cache.setFlowRate(5000.0);
cache.setTimestamp(System.currentTimeMillis());
cache.setTtlMillis(10000); // 10 second TTL
cache.setValid(true);
// Check cache status
if (!cache.isExpired() && cache.isValid()) {
// Use cached results
double cachedFlow = cache.getFlowRate();
}
// Invalidate when process changes
cache.invalidate();
Flow Sensitivity Analysis
Calculate how constraint utilization changes with flow:
// Calculate sensitivities at current operating point
Map<String, Double> sensitivities = evaluator.calculateFlowSensitivities(8000.0, "kg/hr");
for (Map.Entry<String, Double> entry : sensitivities.entrySet()) {
System.out.printf("%s: sensitivity = %.3f (utilization change per kg/hr)%n",
entry.getKey(), entry.getValue());
}
// Estimate maximum feasible flow
double maxFlow = evaluator.estimateMaxFlow(8000.0, "kg/hr");
System.out.println("Estimated max flow: " + maxFlow + " kg/hr");
Gradient-Based Optimization
The ProcessOptimizationEngine supports gradient descent optimization for smooth objective functions.
Search Algorithms
| Algorithm | Description | Best For |
|---|---|---|
BINARY_SEARCH |
Binary search for feasibility boundary | Simple monotonic problems |
GOLDEN_SECTION |
Golden section search | Unimodal objectives |
GRADIENT_DESCENT |
Gradient descent with finite differences | Smooth multi-variable problems |
Using Gradient Descent
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(processSystem);
// Select gradient descent algorithm
engine.setSearchAlgorithm(ProcessOptimizationEngine.SearchAlgorithm.GRADIENT_DESCENT);
engine.setTolerance(1e-4);
engine.setMaxIterations(100);
engine.setEnforceConstraints(true);
// Find maximum throughput
ProcessOptimizationEngine.OptimizationResult result =
engine.findMaximumThroughput(50.0, 10.0, 1000.0, 100000.0);
System.out.println("Optimal flow: " + result.getOptimalValue() + " kg/hr");
System.out.println("Converged: " + result.isConverged());
System.out.println("Iterations: " + result.getIterations());
Gradient Descent Features
- Finite-difference gradient estimation with configurable step size
- Adaptive step size with line search backtracking
- Constraint penalties for infeasible solutions
- Bounds enforcement to stay within search range
Sensitivity Analysis
Analyze how the optimal solution responds to parameter changes.
SensitivityResult Class
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(processSystem);
// Analyze sensitivity at current operating point
ProcessOptimizationEngine.SensitivityResult sensitivity =
engine.analyzeSensitivity(5000.0, 50.0, 10.0);
System.out.println("Base flow: " + sensitivity.getBaseFlow() + " kg/hr");
System.out.println("Flow gradient: " + sensitivity.getFlowGradient());
System.out.println("Tightest constraint: " + sensitivity.getTightestConstraint());
System.out.println("Margin to limit: " + sensitivity.getTightestMargin() * 100 + "%");
System.out.println("Flow buffer: " + sensitivity.getFlowBuffer() + " kg/hr");
// Check if near capacity
if (sensitivity.isAtCapacity()) {
System.out.println("WARNING: Operating near capacity!");
System.out.println("Bottleneck: " + sensitivity.getBottleneckEquipment());
}
// Access constraint margins
Map<String, Double> margins = sensitivity.getConstraintMargins();
for (Map.Entry<String, Double> entry : margins.entrySet()) {
System.out.printf(" %s: %.1f%% margin%n", entry.getKey(), entry.getValue() * 100);
}
Shadow Prices
Calculate the economic value of relaxing constraints:
// Calculate shadow prices (value of constraint relaxation)
Map<String, Double> shadowPrices = engine.calculateShadowPrices(5000.0, 50.0, 10.0);
System.out.println("Shadow Prices (flow increase per unit constraint relaxation):");
for (Map.Entry<String, Double> entry : shadowPrices.entrySet()) {
if (entry.getValue() > 0) {
System.out.printf(" %s: %.2f kg/hr per unit%n",
entry.getKey(), entry.getValue());
}
}
// Identify most valuable constraint to relax
String mostValuable = shadowPrices.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("none");
System.out.println("Most valuable to relax: " + mostValuable);
FlowRateOptimizer Integration
The ProcessOptimizationEngine integrates with FlowRateOptimizer for advanced lift curve generation.
Creating FlowRateOptimizer
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(processSystem);
// Create FlowRateOptimizer with auto-detected streams
FlowRateOptimizer optimizer = engine.createFlowRateOptimizer();
// Use optimizer for detailed flow rate calculations
double maxFlow = optimizer.findFlowRate(50.0, 10.0, "bara");
System.out.println("Max flow at P_in=50, P_out=10: " + maxFlow + " kg/hr");
Comprehensive Lift Curve Generation
// Define inlet pressure range
double[] inletPressures = {30.0, 40.0, 50.0, 60.0, 70.0, 80.0};
double outletPressure = 10.0;
// Generate comprehensive lift curves
FlowRateOptimizer liftOptimizer =
engine.generateComprehensiveLiftCurve("feed", inletPressures, outletPressure);
// Use the optimizer for additional calculations
for (double pin : inletPressures) {
double flow = liftOptimizer.findFlowRate(pin, outletPressure, "bara");
System.out.printf("P_in=%.0f bara -> Max flow = %.0f kg/hr%n", pin, flow);
}
Configuration and Tuning
Optimization Tolerances
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(process);
// Set convergence tolerance (default 1e-6)
engine.setTolerance(1e-4);
// Set maximum iterations (default 100)
engine.setMaxIterations(50);
Strategy Priority System
When multiple strategies support the same equipment type, the one with highest priority is used:
| Strategy | Default Priority |
|---|---|
| Custom strategies | User-defined |
| CompressorCapacityStrategy | 10 |
| SeparatorCapacityStrategy | 10 |
| PumpCapacityStrategy | 10 |
| ValveCapacityStrategy | 10 |
| PipeCapacityStrategy | 10 |
| HeatExchangerCapacityStrategy | 10 |
To override, create a custom strategy with higher priority:
public class MySpecialCompressorStrategy extends CompressorCapacityStrategy {
@Override
public int getPriority() {
return 100; // Higher than default 10
}
@Override
public boolean supports(ProcessEquipmentInterface equipment) {
// Only for specific compressor types
return equipment instanceof MySpecialCompressor;
}
}
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| No strategy found | Equipment type not registered | Register custom strategy |
| Constraints return 0 | Equipment not run | Call equipment.run() first |
| Invalid utilization values | Missing design values | Set design values in constraints |
| VFP export fails | Process not converging | Check fluid composition and conditions |
Debug Mode
Enable detailed logging:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
// In log4j2.xml, set level to DEBUG for optimizer package
// <Logger name="neqsim.process.util.optimizer" level="DEBUG"/>
See Also
- Capacity Constraint Framework - Detailed constraint system documentation
- Process Optimization Framework - Parameter estimation and calibration
- Multi-Objective Optimization - Pareto optimization
- Batch Studies - Sensitivity analysis
Version History
| Version | Date | Changes |
|---|---|---|
| 1.0 | 2026-01 | Initial release with plugin architecture |
| 1.1 | 2026-01 | Added driver package and operating envelope |
| 1.2 | 2026-01 | Added Eclipse VFP export support |
| 1.3 | 2026-01 | Added OptimizationResultBase unified result class |
| 1.4 | 2026-01 | Added ProcessConstraintEvaluator with caching and sensitivity |
| 1.5 | 2026-01 | Added gradient descent optimization |
| 1.6 | 2026-01 | Added FlowRateOptimizer integration and shadow prices |