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 │ │ Validated BHP │ │
│ │ (Production) │ │ (Injection) │ │ (Export) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Quick Start
Compile the complete OptimizationGuideSetup.java in Getting Started
first. The snippets below are method bodies: place their imports above your class and run
each section after the base setup below. process, feed, compressor, engine, and
logger refer to that setup. Later result/report snippets follow the throughput search.
Each example is checked by OptimizationEntryDocumentationTest against the current source.
Use Java 8 or newer and the NeqSim dependency classpath. Driver and map data are synthetic.
Basic Usage: Evaluate Process Constraints
import java.util.Map;
import neqsim.process.equipment.capacity.CapacityConstraint;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.util.optimizer.ProcessOptimizationEngine;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
// Save and compile OptimizationGuideSetup.java from Getting Started first.
ProcessSystem process = OptimizationGuideSetup.createProcess();
Stream feed = (Stream) process.getUnit("feed");
Compressor compressor = (Compressor) process.getUnit("comp");
Logger logger = LogManager.getLogger("OptimizationGuide");
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(process);
engine.setFeedStreamName("feed");
engine.setOutletStreamName("outlet");
ProcessOptimizationEngine.ConstraintReport report = engine.evaluateAllConstraints();
for (ProcessOptimizationEngine.EquipmentConstraintStatus status : report.getEquipmentStatuses()) {
logger.info("{}: {}% utilization; hard limits satisfied: {}", status.getEquipmentName(),
100.0 * status.getUtilization(), status.isWithinLimits());
}
logger.info("Bottleneck equipment: {}", engine.findBottleneckEquipment());
Find Maximum Throughput
engine.setSearchAlgorithm(ProcessOptimizationEngine.SearchAlgorithm.BINARY_SEARCH);
engine.setTolerance(1.0); // absolute flow interval, kg/hr
ProcessOptimizationEngine.OptimizationResult result =
engine.findMaximumThroughput(50.0, 150.0, 10000.0, 200000.0);
if (!result.isConverged()) {
throw new IllegalStateException(result.getErrorMessage());
}
// Explicitly restore the reported point for compatibility with neqsim 3.20.0.
feed.setFlowRate(result.getOptimalValue(), "kg/hr");
process.run();
logger.info("Optimal flow: {} kg/hr; bottleneck: {}; power: {} kW",
result.getOptimalValue(), result.getBottleneck(), compressor.getPower("kW"));
logger.info("Reported constraint violations: {}", result.getConstraintViolations());
Equipment Capacity Strategies
Strategy Interface
Each equipment type has a dedicated strategy that understands its specific constraints:
import neqsim.process.equipment.capacity.EquipmentCapacityStrategy;
import neqsim.process.equipment.capacity.EquipmentCapacityStrategyRegistry;
EquipmentCapacityStrategy strategy = EquipmentCapacityStrategyRegistry.getInstance().findStrategy(compressor);
boolean supported = strategy.supports(compressor);
double utilization = strategy.evaluateCapacity(compressor);
Map<String, CapacityConstraint> constraints = strategy.getConstraints(compressor);
boolean hardLimitsSatisfied = strategy.isWithinHardLimits(compressor);
boolean designLimitsSatisfied = strategy.isWithinSoftLimits(compressor);
logger.info("{}: utilization {}; hard {}; design {}", strategy.getName(), utilization,
hardLimitsSatisfied, designLimitsSatisfied);
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;
CompressorCapacityStrategy strategy = new CompressorCapacityStrategy(0.10, 0.05, 200.0);
Map<String, CapacityConstraint> constraints = strategy.getConstraints(compressor);
CapacityConstraint surgeConstraint = constraints.get("surgeMargin");
if (surgeConstraint != null && surgeConstraint.isEnabled()) {
logger.info("Surge constraint current: {} {}; lower bound: {}", surgeConstraint.getCurrentValue(),
surgeConstraint.getUnit(), surgeConstraint.getMinValue());
} else {
logger.info("No active surge constraint: supply a validated performance chart for surge analysis");
}
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;
// Independent separator example using the solved inlet fluid; dimensions are illustrative.
Separator separator = new Separator("separator capacity example", feed);
separator.setInternalDiameter(2.0);
separator.setSeparatorLength(6.0);
separator.run();
SeparatorCapacityStrategy strategy = new SeparatorCapacityStrategy(
0.10, // maximum gas load factor, m/s
0.80 // maximum liquid level fraction
);
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.ProcessEquipmentInterface;
import neqsim.process.equipment.capacity.CompressorCapacityStrategy;
import neqsim.process.equipment.capacity.EquipmentCapacityStrategyRegistry;
// Extend a complete strategy; restrict it to the named compressor.
CompressorCapacityStrategy customStrategy = new CompressorCapacityStrategy(0.15, 0.08, 180.0) {
@Override
public String getName() {
return "NamedCompressorDemo";
}
@Override
public boolean supports(ProcessEquipmentInterface equipment) {
return equipment instanceof Compressor && equipment.getName().equals("comp");
}
@Override
public int getPriority() {
return 100;
}
};
EquipmentCapacityStrategyRegistry registry = EquipmentCapacityStrategyRegistry.getInstance();
registry.register(customStrategy);
try {
logger.info("Selected strategy priority: {}", registry.findStrategy(compressor).getPriority());
} finally {
registry.unregister(customStrategy.getName());
}
Driver Package
The driver package provides compressor driver models with performance curves.
DriverCurve Interface
import neqsim.process.equipment.compressor.driver.DriverCurve;
import neqsim.process.equipment.compressor.driver.GasTurbineDriver;
DriverCurve curve = new GasTurbineDriver(15000.0, 10000.0, 0.35);
double availablePowerKW = curve.getAvailablePower(curve.getRatedSpeed());
double efficiency = curve.getEfficiency(curve.getRatedSpeed(), 0.75);
double fuelRate = curve.getFuelConsumption(10000.0, curve.getRatedSpeed());
logger.info("Available: {} kW; efficiency {}; fuel: {} kg/hr", availablePowerKW, efficiency, fuelRate);
GasTurbineDriver
Models gas turbine drivers with ambient derating:
import neqsim.process.equipment.compressor.driver.GasTurbineDriver;
GasTurbineDriver driver = new GasTurbineDriver(15000.0, 10000.0, 0.35);
driver.setAmbientTemperature(30.0); // Celsius, not Kelvin
driver.setAltitude(500.0); // metres
double availablePower = driver.getAvailablePower(driver.getRatedSpeed());
double fuelGas = driver.getFuelConsumption(10000.0, driver.getRatedSpeed());
logger.info("Available: {} kW; fuel at 10 MW: {} kg/hr", availablePower, fuelGas);
ElectricMotorDriver
Models electric motor drivers with VFD support:
import neqsim.process.equipment.compressor.driver.ElectricMotorDriver;
ElectricMotorDriver motor = new ElectricMotorDriver(5000.0, 3000.0, 0.96);
motor.setHasVFD(true);
motor.setMinSpeed(600.0);
motor.setMaxSpeed(3600.0);
double efficiency = motor.getEfficiency(3000.0, 0.75);
logger.info("Motor efficiency at 3000 rpm and 75% load: {}%", efficiency * 100.0);
SteamTurbineDriver
Models steam turbine drivers with Willans line:
import neqsim.process.equipment.compressor.driver.SteamTurbineDriver;
SteamTurbineDriver turbine = new SteamTurbineDriver(8000.0, 10000.0, 0.78);
turbine.setInletPressure(40.0); // bara
turbine.setInletTemperature(400.0); // Celsius
turbine.setExhaustPressure(4.0); // bara
double steamFlow = turbine.getSteamConsumption(6000.0, turbine.getRatedSpeed());
logger.info("Steam consumption at 6 MW: {} kg/hr", steamFlow);
Compressor Operating Envelope
Track and validate compressor operation against surge/stonewall limits:
import neqsim.process.equipment.compressor.OperatingEnvelope;
OperatingEnvelope envelope = new OperatingEnvelope(7000.0, 11000.0);
envelope.setRatedSpeed(10000.0);
// Illustrative map: actual m3/hr, kJ/kg, rpm. Supply vendor curves for design work.
envelope.setSurgeLine(new double[] {500, 700, 900, 1100},
new double[] {80, 100, 115, 125}, new double[] {10000, 10000, 10000, 10000});
envelope.setStonewallLine(new double[] {1800, 2200, 2600, 3000},
new double[] {60, 80, 95, 105}, new double[] {10000, 10000, 10000, 10000});
double flow = 1200.0;
double head = 95.0;
double speed = 9500.0;
boolean withinEnvelope = envelope.isWithinEnvelope(flow, head, speed);
double surgeMargin = envelope.getSurgeMargin(flow, head, speed);
double stonewallMargin = envelope.getStonewallMargin(flow, head, speed);
logger.info("Within envelope: {}; surge {}%; stonewall {}%; limiting {}", withinEnvelope,
100.0 * surgeMargin, 100.0 * stonewallMargin, envelope.getLimitingConstraint(flow, head, speed));
Compressor Constraint Configuration
Configure comprehensive compressor constraints:
import neqsim.process.equipment.compressor.CompressorConstraintConfig;
CompressorConstraintConfig config = new CompressorConstraintConfig();
config.setMinSurgeMargin(0.10);
config.setMinStonewallMargin(0.05);
config.setRatedSpeed(10000.0);
config.setMinSpeedRatio(0.5); // 5000 rpm
config.setMaxSpeedRatio(1.1); // 11000 rpm
config.setMaxPower(15000.0); // kW
config.setMaxDischargeTemperatureCelsius(200.0);
config.setMaxSuctionTemperature(333.15); // Kelvin: 60 C
CompressorConstraintConfig conservative = CompressorConstraintConfig.createConservativeConfig();
CompressorConstraintConfig aggressive = CompressorConstraintConfig.createAggressiveConfig();
CompressorConstraintConfig api617 = CompressorConstraintConfig.createAPI617Config();
Eclipse VFP Export
Format supplied well BHP tables for reservoir simulation. Qualify the well calculation and its feasible operating envelope before export.
📘 See Also: Capacity Constraint Framework - VFP Section for detailed documentation on constraint management for VFP studies.
How Constraints Affect VFP Tables
EclipseVFPExporter is a formatter for supplied pressure tables. It has no process-taking
constructor, constraint-enforcement switch, BHP solver, or pointwise bottleneck result API.
Solve the well/pipeline separately and reject failed or infeasible points before supplying
finite BHP values. Never substitute a throughput maximum for BHP.
The writer serializes every supplied composition and ALQ slice and rejects missing or infeasible
pressures. OPM/Eclipse VFPPROD and VFPINJ are supported with explicit METRIC or FIELD
headers. Input rates default to Sm3/day and pressures to bara; the output units are converted,
including the FIELD Mscf/STB gas-ratio convention. The surrounding deck must use the same unit
system. See the VFP export contract for supported definitions,
index order, conversion rules and migration from the legacy methods. These examples qualify
formatting; a well model must be independently validated.
Constraint Configuration for VFP
import neqsim.process.util.optimizer.EclipseVFPExporter;
// Exporter formats supplied BHP data; it does not execute or constrain a process.
EclipseVFPExporter exporter = new EclipseVFPExporter(1);
exporter.setDatumDepth(1000.0); // metres
exporter.setFlowRateType("GAS");
exporter.setWaterCutType("WGR");
exporter.setGORType("OGR");
exporter.setUnitSystem("METRIC");
exporter.setTableTitle("Synthetic dry-gas format example; not a qualified well model");
VFPPROD Tables (Production Wells)
double[] thp = {20.0, 40.0}; // bara
double[] flowRates = {10000.0, 20000.0, 40000.0}; // gas Sm3/day for METRIC/GAS
// Input order: [flow][THP][water ratio][gas ratio][ALQ].
double[][][][][] bhp = new double[3][2][1][1][1];
bhp[0][0][0][0][0] = 30.0;
bhp[1][0][0][0][0] = 36.0;
bhp[2][0][0][0][0] = 48.0;
bhp[0][1][0][0][0] = 50.0;
bhp[1][1][0][0][0] = 56.0;
bhp[2][1][0][0][0] = 68.0;
exporter.setTHPs(thp);
exporter.setFlowRates(flowRates);
exporter.setWaterCuts(new double[] {0.0});
exporter.setGORs(new double[] {0.0});
exporter.setALQs(new double[] {0.0});
exporter.setBHPTable(bhp);
String vfpTable = exporter.getVFPPRODString();
exporter.exportVFPPROD("VFPPROD_WELL1.INC");
VFPINJ Tables (Injection Wells)
// Reuse the supplied dry-gas BHP table; this example injects gas, not water.
String vfpInj = exporter.getVFPINJString();
exporter.exportVFPINJ("VFPINJ_GAS1.INC");
VFPEXP Method (Legacy Export-System Writer)
// The unsupported VFPEXP dialect fails before opening the output file.
try {
exporter.exportVFPEXP("EXPORT_SYSTEM.INC");
throw new AssertionError("VFPEXP must reject unsupported export-system semantics");
} catch (UnsupportedOperationException expected) {
logger.info("Use process capacity CSV/JSON for facility screening");
}
What-If Studies: Modifying Constraints for VFP Scenarios
// Compare installed power limits in the PROCESS, separately from formatting a BHP table.
double originalPowerKW = compressor.getMechanicalDesign().maxDesignPower;
try {
for (double ratingKW : new double[] {4000.0, 4400.0}) {
compressor.getMechanicalDesign().setMaxDesignPower(ratingKW);
engine.clearCache();
ProcessOptimizationEngine.OptimizationResult candidate =
engine.findMaximumThroughput(50.0, 150.0, 10000.0, 200000.0);
logger.info("Rating {} kW: throughput {} kg/hr; converged {}", ratingKW,
candidate.getOptimalValue(), candidate.isConverged());
}
} finally {
compressor.getMechanicalDesign().setMaxDesignPower(originalPowerKW);
feed.setFlowRate(50000.0, "kg/hr");
process.run();
engine.clearCache();
}
Process Bottleneck Reporting Alongside Export
// Evaluate an actual solved process point; the exporter does not supply bottleneck records.
ProcessOptimizationEngine.ConstraintReport report = engine.evaluateAllConstraints();
for (ProcessOptimizationEngine.EquipmentConstraintStatus point : report.getEquipmentStatuses()) {
logger.info("{}: utilization {}%; limiting {}; hard limits satisfied {}", point.getEquipmentName(),
100.0 * point.getUtilization(), point.getBottleneckConstraint(), point.isWithinLimits());
}
ProcessOptimizationEngine API Reference
Constructors
import neqsim.process.processmodel.ProcessModule;
ProcessOptimizationEngine systemEngine = new ProcessOptimizationEngine(process);
ProcessModule fieldModule = new ProcessModule("Field Development");
fieldModule.add(process);
ProcessOptimizationEngine moduleEngine = new ProcessOptimizationEngine(fieldModule);
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.processmodel.ProcessModule;
ProcessModule fieldModule = new ProcessModule("Field Development");
fieldModule.add(OptimizationGuideSetup.createProcess());
fieldModule.run();
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(fieldModule);
engine.setFeedStreamName("feed");
engine.setOutletStreamName("outlet");
engine.setSearchAlgorithm(ProcessOptimizationEngine.SearchAlgorithm.BINARY_SEARCH);
engine.setTolerance(1.0);
ProcessOptimizationEngine.OptimizationResult result =
engine.findMaximumThroughput(50.0, 150.0, 10000.0, 200000.0);
logger.info("Feed stream: {}; optimum: {} kg/hr", engine.getFeedStreamName(), result.getOptimalValue());
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 |
engine.setFeedStreamName("feed");
ProcessOptimizationEngine.OptimizationResult result = engine
.setFeedStreamName("feed")
.findMaximumThroughput(50.0, 150.0, 10000.0, 200000.0);
logger.info("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”) |
engine.setFeedStreamName("feed");
engine.setOutletStreamName("outlet");
ProcessOptimizationEngine.OptimizationResult result =
engine.findMaximumThroughput(50.0, 150.0, 10000.0, 200000.0);
feed.setFlowRate(result.getOptimalValue(), "kg/hr");
process.run();
logger.info("Outlet: {} C; {} MSm3/day", engine.getOutletTemperature("C"),
engine.getOutletFlowRate("MSm3/day"));
ProcessModule Example with Feed and Outlet Streams
import neqsim.process.processmodel.ProcessModule;
import neqsim.process.equipment.heatexchanger.Cooler;
ProcessModule facilityModule = new ProcessModule("Offshore Facility");
ProcessSystem compression = OptimizationGuideSetup.createProcess();
facilityModule.add(compression);
Stream compressionOutlet = (Stream) compression.getUnit("outlet");
Cooler exportCooler = new Cooler("export cooler", compressionOutlet);
exportCooler.setOutTemperature(313.15);
Stream exportGas = new Stream("Export Gas", exportCooler.getOutletStream());
ProcessSystem export = new ProcessSystem("Export");
export.add(exportCooler);
export.add(exportGas);
facilityModule.add(export);
facilityModule.run();
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(facilityModule);
engine.setFeedStreamName("feed");
engine.setOutletStreamName("Export Gas");
engine.setSearchAlgorithm(ProcessOptimizationEngine.SearchAlgorithm.BINARY_SEARCH);
engine.setTolerance(1.0);
ProcessOptimizationEngine.OptimizationResult result =
engine.findMaximumThroughput(50.0, 150.0, 10000.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 |
generateCapacityScreening(pressures, temperaturesK) |
LiftCurveData |
Fixed-composition mass-throughput screening; no BHP or recombination |
analyzeSensitivity(flow, inletP, outletP) |
SensitivityResult |
Analyze flow sensitivity and margins |
calculateShadowPrices(flow, inletP, outletP) |
Map<String, Double> |
Calculate heuristic constraint-relief indicators |
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
// Engine result: flow for findMaximumThroughput, pressure for findRequiredInletPressure.
double optimalValue = result.getOptimalValue();
boolean converged = result.isConverged();
String bottleneck = result.getBottleneck();
java.util.List<String> violations = result.getConstraintViolations();
// Power is read from the solved equipment with an explicit unit.
double powerKW = compressor.getPower("kW");
ConstraintReport Class
ProcessOptimizationEngine.ConstraintReport report = engine.evaluateAllConstraints();
ProcessOptimizationEngine.EquipmentConstraintStatus bottleneck = report.getBottleneck();
double highestUtilization = bottleneck == null ? 0.0 : bottleneck.getUtilization();
logger.info("Highest reported utilization: {}%", 100.0 * highestUtilization);
EquipmentConstraintStatus Class
for (ProcessOptimizationEngine.EquipmentConstraintStatus status :
engine.evaluateAllConstraints().getEquipmentStatuses()) {
logger.info("{} ({}): {}%; hard limits satisfied {}", status.getEquipmentName(),
status.getEquipmentType(), 100.0 * status.getUtilization(), status.isWithinLimits());
for (CapacityConstraint constraint : status.getConstraints()) {
if (constraint.isEnabled()) {
logger.info("{}: {} {}", constraint.getName(), constraint.getCurrentValue(), constraint.getUnit());
}
}
}
Integration Examples
Example 1: Production Optimization with Constraints
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.util.optimizer.ProcessOptimizationEngine;
public class ProductionOptimizationExample {
private static final Logger logger = LogManager.getLogger(ProductionOptimizationExample.class);
public static void main(String[] args) {
ProcessSystem process = OptimizationGuideSetup.createProcess();
ProcessOptimizationEngine.OptimizationResult result = OptimizationGuideSetup.optimize(process);
Compressor compressor = (Compressor) process.getUnit("comp");
logger.info("Maximum throughput: {} kg/hr; power: {} kW; bottleneck: {}",
result.getOptimalValue(), compressor.getPower("kW"), result.getBottleneck());
}
}
Example 2: Fixed-Composition Throughput Sweep for Eclipse
import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.util.optimizer.ProcessOptimizationEngine;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LiftCurveExample {
private static final Logger logger = LogManager.getLogger(LiftCurveExample.class);
public static void main(String[] args) {
ProcessSystem process = OptimizationGuideSetup.createProcess();
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(process);
engine.setFeedStreamName("feed");
engine.setOutletStreamName("outlet");
engine.setSearchAlgorithm(ProcessOptimizationEngine.SearchAlgorithm.BINARY_SEARCH);
engine.setTolerance(1.0);
// Fixed composition and export pressure; each point is an independent throughput search.
for (double inletBara : new double[] {40.0, 50.0, 60.0}) {
ProcessOptimizationEngine.OptimizationResult result =
engine.findMaximumThroughput(inletBara, 150.0, 10000.0, 200000.0);
if (!result.isConverged()) {
throw new IllegalStateException(result.getErrorMessage());
}
logger.info("Inlet {} bara: maximum throughput {} kg/hr", inletBara, result.getOptimalValue());
}
}
}
Example 3: Using Strategy Registry Directly
import java.util.Map;
import neqsim.process.equipment.capacity.CapacityConstraint;
import neqsim.process.equipment.capacity.EquipmentCapacityStrategy;
import neqsim.process.equipment.capacity.EquipmentCapacityStrategyRegistry;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.processmodel.ProcessSystem;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class StrategyUsageExample {
private static final Logger logger = LogManager.getLogger(StrategyUsageExample.class);
public static void main(String[] args) {
ProcessSystem process = OptimizationGuideSetup.createProcess();
Compressor compressor = (Compressor) process.getUnit("comp");
EquipmentCapacityStrategy strategy = EquipmentCapacityStrategyRegistry.getInstance().findStrategy(compressor);
for (CapacityConstraint constraint : strategy.getConstraints(compressor).values()) {
if (constraint.isEnabled()) {
logger.info("{}: {} {} ({}%)", constraint.getName(), constraint.getCurrentValue(),
constraint.getUnit(), constraint.getUtilizationPercent());
}
}
logger.info("Violations: {}", strategy.getViolations(compressor));
}
}
Unified Result Classes
OptimizationResultBase is a separate result container. The engine and ProductionOptimizer
retain their own nested result types; do not interchange their getters or cast one to another.
OptimizationResultBase
The OptimizationResultBase class is a standalone result container:
import neqsim.process.util.optimizer.OptimizationResultBase;
// Demonstrates result bookkeeping; these illustrative values are not a new optimization run.
OptimizationResultBase result = new OptimizationResultBase();
result.markStart();
result.setObjective("MaxThroughput");
result.incrementIterations();
result.incrementFunctionEvaluations();
result.setOptimalValue(5500.0);
result.addOptimalValue("FlowRate", 5500.0);
result.setObjectiveValue(5500.0);
result.setBottleneckEquipment("comp");
result.setBottleneckConstraint("power");
result.setConverged(true);
result.markEnd();
logger.info("{}; elapsed {} s", result.getSummary(), result.getElapsedTimeSeconds());
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
);
logger.info("Violation: " + violation.getViolationAmount()); // 3.0 MW over
logger.info("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(process);
// Evaluate all constraints
ProcessConstraintEvaluator.ConstraintEvaluationResult result = evaluator.evaluate();
logger.info("Overall utilization: " + result.getOverallUtilization() * 100 + "%");
logger.info("Bottleneck: " + result.getBottleneckEquipment());
logger.info("Feasible: " + result.isFeasible());
logger.info("Violations: " + result.getTotalViolationCount());
// Get per-equipment summaries
for (Map.Entry<String, ProcessConstraintEvaluator.EquipmentConstraintSummary> entry :
result.getEquipmentSummaries().entrySet()) {
ProcessConstraintEvaluator.EquipmentConstraintSummary summary = entry.getValue();
logger.info(String.format("%s: %.1f%% utilization, margin to limit: %.1f%%%n",
summary.getEquipmentName(),
summary.getUtilization() * 100,
summary.getMarginToLimit() * 100));
}
Constraint Caching
The cache belongs to the evaluator, not the process. Its key does not represent arbitrary temperature, composition, pressure or rating edits. Clear it after changing inputs or limits, and before reading constraints from a new solve:
// Configure cache TTL (default 10 seconds)
evaluator.setCacheTTLMillis(30000); // 30 seconds
// Evaluate with caching
ProcessConstraintEvaluator.ConstraintEvaluationResult result1 = evaluator.evaluate();
process.run();
evaluator.clearCache();
ProcessConstraintEvaluator.ConstraintEvaluationResult result2 = evaluator.evaluate(); // Fresh evaluation after the new solve
// 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()) {
logger.info(String.format("%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");
logger.info("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 single-flow searches |
Using Gradient Descent
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(process);
// 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, 150.0, 10000.0, 200000.0);
logger.info("Optimal flow: " + result.getOptimalValue() + " kg/hr");
logger.info("Converged: " + result.isConverged());
logger.info("Configured iteration budget: {}", engine.getMaxIterations());
Gradient Descent Features
- Finite-difference gradient estimation for the feed flow variable
- Adaptive step size with line search backtracking
- Constraint penalties for infeasible solutions
- Bounds enforcement to stay within search range
Disabled Compressor Constraints
CompressorCapacityStrategy excludes disabled constraints from violation lists,
bottleneck selection, and hard/soft limit checks. This matters for fixed-pressure
compressors without performance charts, whose surge and speed constraints are
disabled. Disabled map constraints must not reject an otherwise feasible power
limited operating point. Active limits remain enforced.
Sensitivity Analysis
Analyze how the optimal solution responds to parameter changes. Sensitivity probes restore and solve the supplied base flow and inlet pressure before returning, so equipment outputs remain consistent with the reported throughput. The flow buffer is the last feasible sampled increase from up to 50 successive 1% probes. It is zero when the first probe violates a hard limit; it is not the size of that infeasible step or a guaranteed maximum margin. See the executable Practical Examples for the explicit operating-point re-run needed with the 3.20.0 release.
SensitivityResult Class
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(process);
// Analyze sensitivity at current operating point
ProcessOptimizationEngine.SensitivityResult sensitivity =
engine.analyzeSensitivity(50000.0, 50.0, 150.0);
logger.info("Base flow: " + sensitivity.getBaseFlow() + " kg/hr");
logger.info("Flow gradient: " + sensitivity.getFlowGradient());
logger.info("Tightest constraint: " + sensitivity.getTightestConstraint());
logger.info("Margin to limit: " + sensitivity.getTightestMargin() * 100 + "%");
logger.info("Flow buffer: " + sensitivity.getFlowBuffer() + " kg/hr");
// Check if near capacity
if (sensitivity.isAtCapacity()) {
logger.info("WARNING: Operating near capacity!");
logger.info("Bottleneck: " + sensitivity.getBottleneckEquipment());
}
// Access constraint margins
Map<String, Double> margins = sensitivity.getConstraintMargins();
for (Map.Entry<String, Double> entry : margins.entrySet()) {
logger.info(String.format(" %s: %.1f%% margin%n", entry.getKey(), entry.getValue() * 100));
}
Shadow Prices
The engine’s calculateShadowPrices currently returns a numerical constraint-relief
dimensionless indicator derived from a 1% flow perturbation and capacity utilization. It is not an economic shadow price
or a reoptimized throughput gain. Use controlled, independently reoptimized alternatives
to quantify a debottleneck benefit.
// Calculate numerical constraint-relief indicators
Map<String, Double> shadowPrices = engine.calculateShadowPrices(50000.0, 50.0, 150.0);
logger.info("Constraint-relief indicators (heuristic):");
for (Map.Entry<String, Double> entry : shadowPrices.entrySet()) {
if (entry.getValue() > 0) {
logger.info(String.format(" %s: %.2f (heuristic)%n",
entry.getKey(), entry.getValue()));
}
}
// Rank the heuristic indicators; confirm benefits with separate optimization runs
String mostValuable = shadowPrices.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("none");
logger.info("Most valuable to relax: " + mostValuable);
FlowRateOptimizer Integration
The ProcessOptimizationEngine integrates with FlowRateOptimizer for advanced lift curve generation.
Creating FlowRateOptimizer
import neqsim.process.util.optimizer.FlowRateOptimizer;
// FlowRateOptimizer matches a receiving-pressure target using an actual pressure-loss model.
// For the fixed-discharge compressor fixture, use throughput constraints in the engine instead.
FlowRateOptimizer optimizer = engine.createFlowRateOptimizer();
optimizer.setMinFlowRate(10000.0);
optimizer.setMaxFlowRate(200000.0);
logger.info("Flow optimizer configured; attach a pressure-loss model before a hydraulic search");
Comprehensive Lift Curve Generation
import neqsim.process.util.optimizer.FlowRateOptimizer;
// Use the fully constructed hydraulic example in the Flow Rate Optimization guide.
// Here a compressor pressure sweep is performed with the engine's explicit flow bounds.
for (double inletBara : new double[] {40.0, 50.0, 60.0}) {
ProcessOptimizationEngine.OptimizationResult point =
engine.findMaximumThroughput(inletBara, 150.0, 10000.0, 200000.0);
logger.info("{} bara: {} kg/hr; converged {}", inletBara, point.getOptimalValue(), point.isConverged());
}
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:
import neqsim.process.equipment.ProcessEquipmentInterface;
import neqsim.process.equipment.capacity.CompressorCapacityStrategy;
CompressorCapacityStrategy namedCompressorStrategy = new CompressorCapacityStrategy() {
@Override
public int getPriority() {
return 100;
}
@Override
public boolean supports(ProcessEquipmentInterface equipment) {
return equipment instanceof Compressor && equipment.getName().equals("comp");
}
};
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 | Missing or invalid supplied BHP data | Supply finite values with the documented dimensions |
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 |