Skip to the content.

Capacity Constraint Framework

Overview

The Capacity Constraint Framework extends NeqSim’s existing bottleneck analysis capability with multi-constraint support. It provides:

Setting limits through mechanical design: To derive capacity constraints directly from an equipment’s design envelope (max design pressure drop, volume flow, power, etc.), see Equipment Utilization via Mechanical Design.

Important: Constraints Disabled by Default

⚠️ Key Behavior: All separator, valve, pipeline, pump, and manifold constraints are disabled by default for backward compatibility. The optimizer checks whether any constraints are enabled before using the CapacityConstrainedEquipment interface.

Why Constraints Are Disabled by Default

To maintain backward compatibility with existing simulations, constraints are created but not enabled when equipment is initialized. This ensures that:

  1. Existing code works unchanged - Simulations that don’t use capacity analysis continue to work
  2. Explicit opt-in for capacity analysis - You must explicitly enable constraints to use them
  3. No unexpected optimization failures - Optimizer falls back to traditional methods if no constraints are enabled

How to Enable Constraints

// Method 1: Use pre-configured constraint sets (Separator example)
Separator separator = new Separator("HP Separator", feed);
separator.useEquinorConstraints();  // Enables K-value, droplet, momentum, retention times
// OR
separator.useAPIConstraints();      // Enables K-value and retention times per API 12J
// OR
separator.useAllConstraints();      // Enables all 5 constraint types

// Method 2: Enable individual constraints
separator.getConstraints().get(StandardConstraintType.SEPARATOR_K_VALUE).setEnabled(true);

// Method 3: Enable all constraints at once
separator.enableConstraints();      // Enables all constraints on this equipment

// Method 4: Disable constraints (return to default)
separator.disableConstraints();     // Disables all constraints

How to Disable Constraints for What-If Analysis

For what-if scenarios or focused analysis, you can disable constraints at multiple levels:

// Method 1: Disable a specific constraint
Map<String, CapacityConstraint> constraints = separator.getCapacityConstraints();
constraints.get("gasLoadFactor").setEnabled(false);  // Disable one constraint
constraints.get("gasLoadFactor").setEnabled(true);   // Re-enable

// Method 2: Disable all constraints on a single equipment
int disabled = separator.disableAllConstraints();  // Returns count of disabled constraints
int enabled = separator.enableAllConstraints();    // Re-enable all

// Method 3: Disable all constraints across entire ProcessSystem
int total = processSystem.disableAllConstraints();  // All equipment, all constraints
processSystem.enableAllConstraints();               // Re-enable all

// Method 4: Disable all constraints across ProcessModule
processModule.disableAllConstraints();
processModule.enableAllConstraints();

// Method 5: FULLY exclude equipment from optimization (not just disable constraints)
separator.setCapacityAnalysisEnabled(false);  // Completely excluded from capacity analysis
separator.setCapacityAnalysisEnabled(true);   // Re-include

Comparison of Disable Methods:

Method Scope Effect on Optimization
constraint.setEnabled(false) One constraint Uses other constraints or fallback rules
equipment.disableAllConstraints() All constraints on equipment Falls back to type-specific capacity rules
processSystem.disableAllConstraints() All equipment in system All use fallback capacity rules
equipment.setCapacityAnalysisEnabled(false) Equipment level Fully excluded from optimization

How the Optimizer Uses Constraints

The ProductionOptimizer uses a multi-level decision process:

// Step 1: In evaluateProcess(), the optimizer FIRST checks if equipment is excluded
if (!constrained.isCapacityAnalysisEnabled()) {
    continue;  // FULLY SKIP this equipment - no capacity checks at all
}

// Step 2: In determineCapacityRule(), for included equipment:
boolean hasEnabledConstraints = constrained.getCapacityConstraints().values().stream()
    .anyMatch(CapacityConstraint::isEnabled);

if (hasEnabledConstraints) {
    // Use multi-constraint capacity analysis (getMaxUtilization())
    return new ConstrainedCapacityRule(equipment);
} else {
    // Fall back to type-specific rules (separator level, valve opening, etc.)
    return new TypeSpecificCapacityRule(equipment);
}

Key behavior:

Summary: Constraint Enablement by Equipment Type

Equipment Type Default State How to Enable
Separator All disabled useEquinorConstraints(), useAPIConstraints(), enableConstraints()
ThreePhaseSeparator All disabled Same as Separator
GasScrubber K-value only enabled useGasScrubberConstraints() (automatic in constructor)
Compressor All enabled (constraints created by autoSize() are enabled by default)
ThrottlingValve All disabled enableConstraints()
Pipeline All disabled enableConstraints()
Pump All disabled enableConstraints()
Manifold All disabled enableConstraints()

Relationship to Existing Bottleneck Analysis

NeqSim already provides bottleneck analysis via ProcessEquipmentInterface:

Existing Method Description
getCapacityDuty() Current operating load (power, flow, etc.)
getCapacityMax() Maximum design capacity
getRestCapacity() Available headroom
ProcessSystem.getBottleneck() Equipment with highest utilization

The new CapacityConstrainedEquipment interface extends this by allowing:

The systems are integrated: ProcessSystem.getBottleneck() automatically uses multi-constraint data when available, falling back to single-capacity metrics for equipment that doesn’t implement the new interface.

Architecture

The framework integrates with existing bottleneck analysis in neqsim.process.equipment.capacity:

┌─────────────────────────────────────────────────────────────────────┐
│                         ProcessModule                                │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │  getConstrainedEquipment() │ findBottleneck() │ ...            │ │
│  │  (recursively searches all nested modules and systems)         │ │
│  └────────────────────────────────────────────────────────────────┘ │
│                              │                                       │
│         ┌────────────────────┴────────────────────┐                  │
│         ▼                                         ▼                  │
│  ┌──────────────────────┐             ┌──────────────────────┐       │
│  │    ProcessModule     │             │    ProcessSystem     │       │
│  │    (nested)          │             │                      │       │
│  └──────────────────────┘             └──────────────────────┘       │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                         ProcessSystem                                │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │  getBottleneck()  │  findBottleneck()  │  getRestCapacity()   │  │
│  │  (unified: checks both single and multi-constraint equipment) │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                              │                                       │
│         ┌────────────────────┴────────────────────┐                  │
│         ▼                                         ▼                  │
│  ┌──────────────────────┐             ┌─────────────────────────────┐│
│  │  Traditional API     │             │  Multi-Constraint API       ││
│  │  getCapacityDuty()   │             │  CapacityConstrainedEquipment│
│  │  getCapacityMax()    │             │  ├─ getCapacityConstraints()││
│  │  getRestCapacity()   │             │  ├─ getBottleneckConstraint()│
│  └──────────────────────┘             │  └─ getMaxUtilization()     ││
│                                       └─────────────────────────────┘│
│                                                   │                  │
│                                                   ▼                  │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │                    CapacityConstraint                            ││
│  │  ┌──────────────────────────────────────────────────────────┐   ││
│  │  │ name │ type │ designValue │ maxValue │ valueSupplier │...│   ││
│  │  └──────────────────────────────────────────────────────────┘   ││
│  └─────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────┘

Core Classes

1. CapacityConstraint

The fundamental building block representing a single capacity limit on equipment.

import neqsim.process.equipment.capacity.CapacityConstraint;
import neqsim.process.equipment.capacity.CapacityConstraint.ConstraintType;

// Create a constraint with fluent builder pattern
CapacityConstraint speedConstraint = new CapacityConstraint("speed", "RPM", ConstraintType.HARD)
    .setDesignValue(10000.0)           // Design operating point (RPM)
    .setMaxValue(11000.0)              // Absolute maximum (trip point)
    .setMinValue(5000.0)               // Minimum stable operation
    .setValueSupplier(() -> compressor.getSpeed());  // Live value getter

Constraint Types

Type Description Example
HARD Absolute limit - equipment trip or damage if exceeded Compressor max speed, surge limit
SOFT Operational limit - reduced efficiency or accelerated wear High discharge temperature
DESIGN Normal operating limit - design basis Separator gas load factor

Key Methods

Method Returns Description
getCurrentValue() double Current value from the valueSupplier
getUtilization() double Current value / design value (1.0 = 100%)
getUtilizationPercent() double Utilization as percentage
isViolated() boolean True if utilization > 1.0
isHardLimitExceeded() boolean True if HARD constraint exceeds max value
isNearLimit() boolean True if above warning threshold (default 90%)
getMargin() double Remaining headroom (1.0 - utilization)

2. CapacityConstrainedEquipment (Interface)

Interface that equipment classes implement to participate in capacity tracking.

public interface CapacityConstrainedEquipment {
    // Get all constraints
    Map<String, CapacityConstraint> getCapacityConstraints();

    // Get the most limiting constraint
    CapacityConstraint getBottleneckConstraint();

    // Check constraint status
    boolean isCapacityExceeded();
    boolean isHardLimitExceeded();
    boolean isNearCapacityLimit();

    // Get utilization metrics
    double getMaxUtilization();
    double getMaxUtilizationPercent();
    double getAvailableMargin();

    // Modify constraints
    void addCapacityConstraint(CapacityConstraint constraint);
    boolean removeCapacityConstraint(String constraintName);
    void clearCapacityConstraints();
}

3. StandardConstraintType (Enum)

Predefined constraint types for common equipment with standardized names and units.

import neqsim.process.equipment.capacity.StandardConstraintType;

// Use predefined constraint types
StandardConstraintType.COMPRESSOR_SPEED          // "speed", "RPM"
StandardConstraintType.COMPRESSOR_POWER          // "power", "kW"
StandardConstraintType.COMPRESSOR_SURGE_MARGIN   // "surgeMargin", "%"
StandardConstraintType.SEPARATOR_GAS_LOAD_FACTOR // "gasLoadFactor", "m/s"
StandardConstraintType.PUMP_NPSH_MARGIN          // "npshMargin", "m"
StandardConstraintType.PIPE_VELOCITY             // "velocity", "m/s"
// ... and more

4. BottleneckResult

Result class returned by ProcessSystem.findBottleneck().

BottleneckResult result = process.findBottleneck();

if (!result.isEmpty()) {
    System.out.println("Bottleneck: " + result.getEquipmentName());
    System.out.println("Constraint: " + result.getConstraint().getName());
    System.out.println("Utilization: " + result.getUtilizationPercent() + "%");
}

Adding & Configuring Custom Constraints

There are three ways to give an equipment new constraint functionality, ordered from the quickest (runtime, no subclassing) to the most reusable (built into an equipment type). All three produce ordinary CapacityConstraint objects, so they surface identically in getMaxUtilization(), getBottleneckConstraint(), findBottleneck(), and the utilization snapshot.

The constraint anatomy (what you configure)

Every constraint is built with the fluent CapacityConstraint API. The two constructors are:

new CapacityConstraint("name", "unit", ConstraintType.HARD);  // explicit unit + type
new CapacityConstraint("name");                                // defaults: unit "", type SOFT

Configurable properties (all are optional fluent setters returning this):

Setter Purpose Default
setDesignValue(double) Limit used for utilization = current / design required
setValueSupplier(DoubleSupplier) Live current value, re-evaluated each query none
setCurrentValue(double) Static current value (use when there is no live source) NaN
setMaxValue(double) Absolute trip point for HARD constraints none
setMinValue(double) Minimum stable operating point none
setWarningThreshold(double) Near-limit early warning (fraction, e.g. 0.9) 0.9
setUnit(String) Unit label (when the 1-arg constructor was used) ””
setDescription(String) Human-readable description ””
setDataSource(String) Provenance tag (e.g. "mechanicalDesign", "user") “not_set”
setEnabled(boolean) Whether the optimizer/analysis counts it true

Live vs. static value: prefer setValueSupplier(...) so the constraint tracks the simulation. A manually built constraint is enabled by default, so it counts as soon as you add it (unlike the auto-generated strategy constraints, which start disabled).

Level 1 — Add a custom constraint to one equipment instance (no subclassing)

The fastest path: build a constraint with a live supplier and register it on the equipment. Use this for one-off or study-specific limits.

// Example: cap a heater on its outlet temperature
Heater heater = new Heater("H-100", feed);
// ... add to process and run ...

CapacityConstraint tempLimit =
    new CapacityConstraint("outletTemperature", "C", ConstraintType.SOFT)
        .setDesignValue(120.0)                                  // design limit
        .setWarningThreshold(0.9)                               // warn at 90%
        .setDescription("Metallurgical limit on tube wall")
        .setValueSupplier(() -> heater.getOutletStream()
            .getTemperature("C"));                              // live value

heater.addCapacityConstraint(tempLimit);                        // active immediately
double util = heater.getMaxUtilization();                       // includes the new limit

Reconfigure or remove it at any time:

heater.getCapacityConstraints().get("outletTemperature").setDesignValue(110.0);
heater.getCapacityConstraints().get("outletTemperature").setEnabled(false); // what-if
heater.removeCapacityConstraint("outletTemperature");                       // remove

Level 2 — Give an equipment type built-in default constraints (subclass)

To make a constraint part of every instance of an equipment type, override the initializeDefaultConstraints() hook on ProcessEquipmentBaseClass. It is called lazily the first time constraints are accessed (and after deserialization), so it is the right place to register type-specific constraints.

public class MyReactor extends ProcessEquipmentBaseClass {

  // ... constructors, run(), etc. ...

  @Override
  protected void initializeDefaultConstraints() {
    addCapacityConstraint(
        new CapacityConstraint("catalystBedDP", "bara", ConstraintType.DESIGN)
            .setDesignValue(1.5)
            .setDataSource("default")
            .setValueSupplier(() -> getInletStreams().get(0).getPressure("bara")
                - getOutletStreams().get(0).getPressure("bara")));
  }
}

Follow the framework convention: if your equipment should preserve backward compatibility, create the constraints disabled (setEnabled(false)) and provide a useXxxConstraints() / enableConstraints() method so users opt in (see how Separator exposes useEquinorConstraints()).

Level 3 — Derive constraints from the mechanical-design envelope

When the limit is a property of the equipment’s design envelope (max design power, flow, pressure drop, velocity, Cv, duty), configure it on the MechanicalDesign and let the bridge build the constraint for you. No CapacityConstraint object is needed. To support a new design-derived metric, override the matching protected getOperating* hook on a MechanicalDesign subclass. This path is documented in full in Equipment Utilization via Mechanical Design:

equipment.getMechanicalDesign().setMaxDesignPower(6000.0);     // kW
equipment.applyMechanicalDesignCapacityConstraints();          // opt-in bridge
double util = equipment.getMaxUtilization();                   // now includes design power

Plant-wide one-call activation

After auto-sizing a whole flowsheet, you do not need to loop over every unit. ProcessSystem and ProcessModel both expose a bulk helper that calls applyMechanicalDesignCapacityConstraints() on every contained piece of equipment and returns the number of units that registered at least one design-derived constraint. The call is idempotent (constraints use stable names) and safe to re-run after re-sizing:

plant.autoSizeEquipment(1.20);                 // size every unit with a 20% margin
int n = plant.applyMechanicalDesignCapacityConstraints();   // activate utilization plant-wide
// n == number of units that now expose design-envelope constraints
String snapshot = plant.getUtilizationSnapshotJson();        // side-effect-free read

For a multi-area ProcessModel the same method walks every area’s ProcessSystem, so a single call activates utilization tracking across the entire plant.

Which level should I use?

Need Use
A one-off limit for a single run/study Level 1 (instance addCapacityConstraint)
A limit that every instance of a new equipment type should have Level 2 (initializeDefaultConstraints)
A limit that is part of the design envelope (power, flow, dP, velocity, Cv, duty) Level 3 (mechanical design + bridge)

Integration with AutoSizing and Mechanical Design

How AutoSizing Creates Constraints

When equipment is auto-sized using the AutoSizeable interface, constraints are automatically created based on the calculated design values:

// Auto-sizing creates constraints automatically
Separator sep = new Separator("HP-Sep", feedStream);
sep.autoSize(1.2);  // 20% safety factor

// This creates the following constraints:
// - gasLoadFactor: based on K-factor sizing calculation
// - liquidResidenceTime: based on L/D ratio and liquid level

// For compressors, autoSize does even more:
Compressor comp = new Compressor("Export", gasStream);
comp.setOutletPressure(100.0);
comp.autoSize(1.2);

// This creates:
// - speed constraint (from mechanical design)
// - power constraint (from driver sizing)
// - surgeMargin constraint (soft limit)
// AND generates compressor curves, sets solveSpeed=true

Mechanical Design as Source of Constraint Values

The MechanicalDesign class provides design values that become constraint limits:

// Mechanical design values feed constraints
Separator sep = new Separator("V-100", feed);
sep.initMechanicalDesign();
SeparatorMechanicalDesign mechDesign = (SeparatorMechanicalDesign) sep.getMechanicalDesign();

// Set design limits that will become constraints
mechDesign.setMaxDesignVolumeFlow(5000.0);     // m³/hr → volumeFlow constraint
mechDesign.setMaxDesignPressureDrop(2.0);      // bara → pressureDrop constraint

// For pipelines
Pipeline pipe = new PipeBeggsAndBrills("L-100", gasStream);
pipe.initMechanicalDesign();
PipelineMechanicalDesign pipeDesign = (PipelineMechanicalDesign) pipe.getMechanicalDesign();

// Design values → constraints
pipeDesign.maxDesignVelocity = 15.0;           // → velocity constraint
pipeDesign.maxDesignPressureDrop = 5.0;        // → pressureDrop constraint
pipeDesign.maxDesignVolumeFlow = 10000.0;      // → volumeFlow constraint

Complete Workflow: Design → Constraints → Optimization

// 1. Create process with auto-sizing
ProcessSystem process = new ProcessSystem();

Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(10000.0, "kg/hr");
process.add(feed);

Separator sep = new Separator("HP-Sep", feed);
sep.autoSize(1.2);  // Creates gasLoadFactor constraint
process.add(sep);

Compressor comp = new Compressor("K-100", sep.getGasOutStream());
comp.setOutletPressure(100.0);
comp.autoSize(1.2);  // Creates speed, power, surge constraints + curves
process.add(comp);

Pipeline pipe = new PipeBeggsAndBrills("Export", comp.getOutletStream());
pipe.setLength(30000.0);
pipe.setDiameter(0.3);
pipe.autoSize(1.2);  // Creates velocity, pressureDrop, FIV constraints
process.add(pipe);

// 2. Run process
process.run();

// 3. Constraints are now active and can be queried
System.out.println("Equipment constraints after auto-sizing:");
for (CapacityConstrainedEquipment equip : process.getConstrainedEquipment()) {
    System.out.println(((ProcessEquipmentInterface) equip).getName() + ":");
    for (CapacityConstraint c : equip.getCapacityConstraints().values()) {
        System.out.printf("  %s: %.2f / %.2f %s%n",
            c.getName(), c.getCurrentValue(), c.getDesignValue(), c.getUnit());
    }
}

// 4. Use in optimization - optimizer checks ALL constraints
ProductionOptimizer.OptimizationConfig config =
    new ProductionOptimizer.OptimizationConfig(1000.0, 50000.0);
ProductionOptimizer optimizer = new ProductionOptimizer();
ProductionOptimizer.OptimizationResult result =
    optimizer.optimize(process, feed, config, null, null);

// 5. The bottleneck could be separator, compressor, or pipeline
System.out.println("Bottleneck: " + result.getBottleneck().getName());

Equipment Currently Supporting CapacityConstrainedEquipment

Equipment Constraints Set By
Separator gasLoadFactor, liquidResidenceTime autoSize(), setDesignGasLoadFactor()
Compressor speed, power, ratedPower, surgeMargin, stonewallMargin autoSize(), setMaximumSpeed(), getMechanicalDesign().setMaxDesignPower()
Pump npshMargin, power, flowRate getMechanicalDesign().setMaxDesignPower(), mechanical design
ThrottlingValve valveOpening, cvUtilization, AIV autoSize(), setCv(), setMaxDesignAIV()
Pipeline velocity, pressureDrop, volumeFlow, FIV_LOF, FIV_FRMS autoSize(), getMechanicalDesign().setMaxDesignVelocity()
PipeBeggsAndBrills velocity, LOF, FRMS, AIV autoSize(), setMaxDesignVelocity(), setMaxDesignLOF(), setMaxDesignAIV()
AdiabaticPipe velocity, LOF, FRMS, AIV, pressureDrop autoSize(), setMaxDesignVelocity(), setMaxDesignLOF(), setMaxDesignAIV()
Manifold headerVelocity, branchVelocity, headerLOF, headerFRMS, branchLOF, branchFRMS autoSize(), setMaxDesignVelocity()
Heater/Cooler duty, outletTemperature autoSize(), setMaxDesignDuty()

How to Override autoSize Constraints

After autoSize() creates constraints, you can override them:

// 1. Override BEFORE autoSize (parameter will be used in sizing)
separator.setDesignGasLoadFactor(0.15);  // Your K-factor
separator.autoSize(1.2);                  // Uses your K-factor

// 2. Override AFTER autoSize (keeps sizing, changes constraint limit)
compressor.autoSize(1.2);
compressor.getMechanicalDesign().setMaxDesignPower(6000.0);  // Override constraint limit (kW)
compressor.setMaximumSpeed(12000.0);      // Override speed limit (RPM)

// 3. Manually set constraint on existing equipment
CapacityConstraint customPower = new CapacityConstraint("powerLimit", "kW", ConstraintType.HARD)
    .setDesignValue(5000.0)
    .setValueSupplier(() -> compressor.getPower("kW"));
compressor.addCapacityConstraint(customPower);

// 4. Remove auto-generated constraint and add custom one
compressor.removeCapacityConstraint("power");  // Remove default
compressor.addCapacityConstraint(customPower); // Add custom

Constraint Priority After Override

When you override a constraint parameter, the priority is:

  1. User-specified value (highest) - via setter methods
  2. autoSize calculated value - based on flow conditions
  3. Mechanical design default - from design standards
  4. Hard-coded default (lowest) - in equipment class

Usage Examples

Basic Usage: Process-Wide Bottleneck Detection

// Create and run process
ProcessSystem process = new ProcessSystem();
// ... add equipment ...
process.run();

// Find the process bottleneck
BottleneckResult bottleneck = process.findBottleneck();
System.out.println("Process bottleneck: " + bottleneck.getEquipmentName());
System.out.println("Limiting constraint: " + bottleneck.getConstraint().getName());
System.out.println("Utilization: " + bottleneck.getUtilizationPercent() + "%");

// Check if any equipment is overloaded
if (process.isAnyEquipmentOverloaded()) {
    System.out.println("WARNING: Equipment operating above design capacity!");
}

// Check if any hard limits are exceeded (critical)
if (process.isAnyHardLimitExceeded()) {
    System.out.println("CRITICAL: Hard equipment limits exceeded!");
}

// Get utilization summary for all equipment
Map<String, Double> utilization = process.getCapacityUtilizationSummary();
for (Map.Entry<String, Double> entry : utilization.entrySet()) {
    System.out.printf("%s: %.1f%%\n", entry.getKey(), entry.getValue());
}

// Get equipment near capacity limit (early warning)
List<String> nearLimit = process.getEquipmentNearCapacityLimit();
if (!nearLimit.isEmpty()) {
    System.out.println("Equipment near capacity: " + nearLimit);
}

ProcessModule Support

The capacity constraint framework also works with ProcessModule, which can contain multiple ProcessSystem instances and nested modules. All constraint methods work recursively across the entire module hierarchy.

import neqsim.process.processmodel.ProcessModule;

// Create a complex module with multiple systems
ProcessModule productionModule = new ProcessModule("Production Platform");

// Add process systems
ProcessSystem separationSystem = new ProcessSystem();
separationSystem.add(inletManifold);
separationSystem.add(hpSeparator);
separationSystem.add(lpSeparator);

ProcessSystem compressionSystem = new ProcessSystem();
compressionSystem.add(lpCompressor);
compressionSystem.add(hpCompressor);
compressionSystem.add(exportPipeline);

productionModule.add(separationSystem);
productionModule.add(compressionSystem);
productionModule.run();

// Find bottleneck across ALL systems in the module
BottleneckResult bottleneck = productionModule.findBottleneck();
if (bottleneck.hasBottleneck()) {
    System.out.println("Module bottleneck: " + bottleneck.getEquipmentName());
    System.out.println("Constraint: " + bottleneck.getConstraint().getName());
    System.out.println("Utilization: " + bottleneck.getUtilizationPercent() + "%");
}

// Check for overloaded equipment across all systems
if (productionModule.isAnyEquipmentOverloaded()) {
    System.out.println("WARNING: Equipment overloaded in module!");
}

// Get all constrained equipment from the module
List<CapacityConstrainedEquipment> allConstrained =
    productionModule.getConstrainedEquipment();
System.out.println("Found " + allConstrained.size() + " constrained equipment items");

// Get utilization summary across entire module
Map<String, Double> utilization = productionModule.getCapacityUtilizationSummary();
for (Map.Entry<String, Double> entry : utilization.entrySet()) {
    System.out.printf("%s: %.1f%%\n", entry.getKey(), entry.getValue());
}

Nested Module Support

ProcessModule supports nesting, and constraint methods work recursively:

// Create nested modules
ProcessModule topside = new ProcessModule("Topside");
ProcessModule subsea = new ProcessModule("Subsea");

subsea.add(subseaManifold);
subsea.add(flowlines);
subsea.add(risers);

topside.add(separationSystem);
topside.add(compressionSystem);

// Create master module containing both
ProcessModule field = new ProcessModule("Field Development");
field.add(subsea);
field.add(topside);
field.run();

// findBottleneck() searches recursively through ALL nested modules
BottleneckResult fieldBottleneck = field.findBottleneck();

// All constraint methods work recursively
List<CapacityConstrainedEquipment> allEquipment = field.getConstrainedEquipment();
Map<String, Double> fieldUtilization = field.getCapacityUtilizationSummary();
boolean anyOverloaded = field.isAnyEquipmentOverloaded();
boolean hardLimitExceeded = field.isAnyHardLimitExceeded();

ProcessModule Constraint Methods

Method Description
getConstrainedEquipment() Returns all equipment implementing CapacityConstrainedEquipment from all systems and nested modules
findBottleneck() Finds the equipment with highest utilization across entire module hierarchy
isAnyEquipmentOverloaded() Checks if any equipment exceeds design capacity (utilization > 100%)
isAnyHardLimitExceeded() Checks if any HARD constraint limits are exceeded
getCapacityUtilizationSummary() Returns Map<String, Double> of equipment name to utilization percentage
getEquipmentNearCapacityLimit() Returns list of equipment names near warning threshold

Individual Equipment Inspection

// Get a specific compressor
Compressor compressor = (Compressor) process.getUnit("27-KA-01");

// Check overall capacity status
System.out.println("Max utilization: " + compressor.getMaxUtilizationPercent() + "%");
System.out.println("Available margin: " + compressor.getAvailableMarginPercent() + "%");

// Inspect individual constraints
Map<String, CapacityConstraint> constraints = compressor.getCapacityConstraints();
for (CapacityConstraint c : constraints.values()) {
    System.out.printf("  %s: %.1f / %.1f %s (%.1f%% utilized)\n",
        c.getName(), c.getCurrentValue(), c.getDesignValue(),
        c.getUnit(), c.getUtilizationPercent());
}

// Get the bottleneck constraint for this equipment
CapacityConstraint limiting = compressor.getBottleneckConstraint();
System.out.println("Limiting factor: " + limiting.getName());

Adding Custom Constraints at Runtime

// Add a custom constraint to a separator
Separator separator = (Separator) process.getUnit("20-VA-01");

// Add liquid residence time constraint
CapacityConstraint residenceTime = new CapacityConstraint(
    StandardConstraintType.SEPARATOR_LIQUID_RESIDENCE_TIME,
    CapacityConstraint.ConstraintType.DESIGN)
    .setDesignValue(180.0)  // 3 minutes minimum
    .setMinValue(60.0)      // Absolute minimum 1 minute
    .setValueSupplier(() -> separator.getLiquidResidenceTime("sec"));

separator.addCapacityConstraint(residenceTime);

// Remove a constraint
separator.removeCapacityConstraint("gasLoadFactor");

// Clear all constraints
separator.clearCapacityConstraints();

Activating and Deactivating Constraints

Adding Constraints to Equipment WITH Existing Constraints

Equipment that already implements CapacityConstrainedEquipment (Separator, Compressor, Pipeline, Manifold, etc.) can have additional constraints added:

// Equipment already has default constraints from autoSize() or initialization
Compressor compressor = new Compressor("Export Compressor", feed);
compressor.setOutletPressure(150.0);
compressor.autoSize(1.2);  // Creates speed, power, surgeMargin constraints
compressor.run();

// View existing constraints
System.out.println("Current constraints:");
for (CapacityConstraint c : compressor.getCapacityConstraints().values()) {
    System.out.println("  " + c.getName() + ": " + c.getDesignValue() + " " + c.getUnit());
}

// ADD a new custom constraint (discharge temperature)
CapacityConstraint tempLimit = new CapacityConstraint("dischargeTemp", "°C", ConstraintType.SOFT)
    .setDesignValue(150.0)  // °C design limit
    .setMaxValue(180.0)     // °C absolute max
    .setUnit("°C")
    .setWarningThreshold(0.9)
    .setValueSupplier(() -> compressor.getOutletStream().getTemperature("C"));

compressor.addCapacityConstraint(tempLimit);

// MODIFY an existing constraint's design value
CapacityConstraint speedConstraint = compressor.getCapacityConstraints().get("speed");
if (speedConstraint != null) {
    speedConstraint.setDesignValue(9500.0);  // Lower speed limit
    speedConstraint.setMaxValue(10000.0);
}

Adding Constraints to Equipment WITHOUT Existing Constraints

For equipment that does not implement CapacityConstrainedEquipment, you need to either:

  1. Use the Strategy Registry (recommended for temporary/external constraints):
// Equipment without built-in constraints
Heater heater = new Heater("Process Heater", feed);
heater.setOutTemperature(350.0);

// Use strategy registry to add constraint evaluation
EquipmentCapacityStrategyRegistry registry = EquipmentCapacityStrategyRegistry.getInstance();

// Create custom strategy for heater
EquipmentCapacityStrategy heaterStrategy = new EquipmentCapacityStrategy() {
    @Override
    public boolean supports(ProcessEquipmentInterface equipment) {
        return equipment instanceof Heater && equipment.getName().equals("Process Heater");
    }

    @Override
    public Map<String, CapacityConstraint> getConstraints(ProcessEquipmentInterface equipment) {
        Heater h = (Heater) equipment;
        Map<String, CapacityConstraint> constraints = new LinkedHashMap<>();

        constraints.put("duty", new CapacityConstraint("duty", "kW", ConstraintType.DESIGN)
            .setDesignValue(5000.0)  // kW
            .setMaxValue(6000.0)
            .setUnit("kW")
            .setValueSupplier(() -> Math.abs(h.getDuty()) / 1000.0));

        return constraints;
    }
    // ... implement other interface methods
};

registry.registerStrategy(heaterStrategy);
  1. Extend the equipment class (for permanent constraints):
// Create subclass with constraint support
public class ConstrainedHeater extends Heater implements CapacityConstrainedEquipment {
    private Map<String, CapacityConstraint> capacityConstraints = new LinkedHashMap<>();

    public ConstrainedHeater(String name, StreamInterface inletStream) {
        super(name, inletStream);
        initializeCapacityConstraints();
    }

    protected void initializeCapacityConstraints() {
        addCapacityConstraint(new CapacityConstraint("duty", "kW", ConstraintType.DESIGN)
            .setDesignValue(5000.0)
            .setUnit("kW")
            .setValueSupplier(() -> Math.abs(getDuty()) / 1000.0));
    }

    // Implement CapacityConstrainedEquipment interface methods...
}

Deactivating (Removing) Constraints

// Remove a specific constraint by name
compressor.removeCapacityConstraint("surgeMargin");

// Remove multiple constraints
compressor.removeCapacityConstraint("stonewallMargin");
compressor.removeCapacityConstraint("dischargeTemp");

// Remove ALL constraints (equipment will no longer be capacity-limited)
compressor.clearCapacityConstraints();

// Re-initialize default constraints after clearing
compressor.initializeCapacityConstraints();  // If method is public/protected

Temporarily Disabling Constraints

For scenarios where you want to keep constraints defined but temporarily ignore them:

// Option 1: Set design value to very high (effectively disabling)
CapacityConstraint speedConstraint = compressor.getCapacityConstraints().get("speed");
double originalDesign = speedConstraint.getDesignValue();
speedConstraint.setDesignValue(Double.MAX_VALUE);  // Disable

// ... run optimization without speed constraint ...

speedConstraint.setDesignValue(originalDesign);  // Re-enable

// Option 2: Store and remove, then re-add
CapacityConstraint removedConstraint = compressor.getCapacityConstraints().get("power");
compressor.removeCapacityConstraint("power");

// ... run optimization without power constraint ...

compressor.addCapacityConstraint(removedConstraint);  // Re-add

Constraints in Eclipse VFP Table Generation

When generating VFP (Vertical Flow Performance) tables for Eclipse reservoir simulation, capacity constraints determine the maximum feasible flow rates at each operating point.

How Constraints Affect VFP Tables

┌─────────────────────────────────────────────────────────────────────────┐
│                       VFP Table Generation Process                       │
├─────────────────────────────────────────────────────────────────────────┤
│  For each (inlet_pressure, outlet_pressure, temperature, WC, GOR):      │
│                                                                          │
│  1. Set process boundary conditions                                      │
│  2. Binary search for maximum flow rate where:                           │
│     - Process converges (thermodynamically feasible)                     │
│     - ALL capacity constraints satisfied (utilization ≤ 1.0)             │
│     - No HARD limit exceeded                                             │
│                                                                          │
│  3. Record: flow_rate, BHP (or THP), bottleneck_equipment                │
└─────────────────────────────────────────────────────────────────────────┘

VFP Generation with Constraint Checking

import neqsim.process.util.optimizer.EclipseVFPExporter;
import neqsim.process.util.optimizer.ProcessOptimizationEngine;

// Create process and optimization engine
ProcessSystem process = createProductionProcess();
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(process);

// Define VFP table parameters
double[] inletPressures = {60.0, 70.0, 80.0, 90.0, 100.0};  // Wellhead (bara)
double[] outletPressures = {40.0, 50.0, 60.0};              // Separator (bara)
double[] waterCuts = {0.0, 0.2, 0.4, 0.6};
double[] gors = {100.0, 300.0, 500.0};

// Generate VFP with constraint-limited flow rates
EclipseVFPExporter exporter = new EclipseVFPExporter(process);
exporter.setTableNumber(1);
exporter.setConstraintEnforcement(true);  // Enable constraint checking

// Each cell in the VFP table will contain the MAXIMUM flow rate
// that satisfies ALL equipment constraints
String vfpTable = exporter.generateVFPPROD(
    inletPressures,
    waterCuts,
    gors,
    new double[]{0.0},  // No artificial lift
    new double[]{5000, 10000, 20000, 50000, 100000, 150000},  // Test flow rates
    "bara",
    "kg/hr"
);

// The VFP table will show:
// - Flow rate = 0 if no feasible operation at that point
// - Flow rate = max achievable if constrained by equipment
// - Flow rate = tested max if all constraints satisfied

Understanding Constraint Impact on VFP

// Example: Analyze how each constraint affects maximum flow at one operating point
double inletP = 80.0;   // bara
double outletP = 50.0;  // bara

// Find maximum flow WITH all constraints
OptimizationResult withConstraints = engine.findMaximumThroughput(
    inletP, outletP, 1000.0, 200000.0);
System.out.println("Max flow (all constraints): " + withConstraints.getOptimalFlowRate());
System.out.println("Bottleneck: " + withConstraints.getBottleneckEquipment());

// Temporarily remove compressor speed constraint
Compressor comp = (Compressor) process.getUnit("Export Compressor");
CapacityConstraint speedLimit = comp.getCapacityConstraints().get("speed");
comp.removeCapacityConstraint("speed");

OptimizationResult noSpeedLimit = engine.findMaximumThroughput(
    inletP, outletP, 1000.0, 200000.0);
System.out.println("Max flow (no speed limit): " + noSpeedLimit.getOptimalFlowRate());

// Restore constraint
comp.addCapacityConstraint(speedLimit);

// Calculate flow increase if speed limit removed
double flowIncrease = noSpeedLimit.getOptimalFlowRate() - withConstraints.getOptimalFlowRate();
System.out.println("Flow increase if speed debottlenecked: " + flowIncrease + " kg/hr");

Modifying Constraints for What-If VFP Studies

// Study: How does upgrading separator affect production capacity?

// Baseline VFP
String baselineVFP = exporter.generateVFPPROD(...);
Files.writeString(Path.of("VFP_BASELINE.INC"), baselineVFP);

// Upgraded separator (higher gas load factor)
Separator sep = (Separator) process.getUnit("HP Separator");
CapacityConstraint gasLoad = sep.getCapacityConstraints().get("gasLoadFactor");
double originalDesign = gasLoad.getDesignValue();
gasLoad.setDesignValue(originalDesign * 1.5);  // 50% larger separator

String upgradedVFP = exporter.generateVFPPROD(...);
Files.writeString(Path.of("VFP_UPGRADED_SEPARATOR.INC"), upgradedVFP);

// Restore original
gasLoad.setDesignValue(originalDesign);

Constraint-Aware Lift Curve Generation

// Generate lift curves showing which constraint limits each point
ProcessOptimizationEngine.LiftCurveData liftCurve = engine.generateLiftCurve(
    inletPressures, outletPressures, temperatures, waterCuts, gors);

// Access detailed results
for (LiftCurvePoint point : liftCurve.getPoints()) {
    System.out.printf("Pin=%.0f, Pout=%.0f, WC=%.1f, GOR=%.0f: " +
                      "MaxFlow=%.0f kg/hr, Limited by: %s%n",
        point.getInletPressure(),
        point.getOutletPressure(),
        point.getWaterCut(),
        point.getGOR(),
        point.getMaxFlowRate(),
        point.getBottleneckConstraint()  // e.g., "Compressor:speed"
    );
}

Summary: Constraint Management for VFP Tables

Action Method Effect on VFP
Add constraint equipment.addCapacityConstraint(c) Lower max flow rates
Remove constraint equipment.removeCapacityConstraint(name) Higher max flow rates
Tighten constraint constraint.setDesignValue(lower) Lower max flow rates
Relax constraint constraint.setDesignValue(higher) Higher max flow rates
Disable all equipment.clearCapacityConstraints() Unconstrained (thermodynamic only)
What-if study Modify → generate VFP → restore Compare scenarios

Integration with Optimization

/**
 * Find maximum throughput without exceeding equipment capacity.
 */
public double findMaxThroughput(ProcessSystem process, double initialRate) {
    double rate = initialRate;
    double maxRate = initialRate;

    while (!process.isAnyEquipmentOverloaded()) {
        maxRate = rate;
        rate *= 1.05;  // Increase by 5%

        // Update feed rate
        Stream feed = (Stream) process.getUnit("well stream");
        feed.setFlowRate(rate, "kmol/hr");
        process.run();
    }

    // Report bottleneck at max rate
    BottleneckResult bottleneck = process.findBottleneck();
    System.out.printf("Maximum rate: %.0f kmol/hr\n", maxRate);
    System.out.printf("Limited by: %s (%s at %.1f%%)\n",
        bottleneck.getEquipmentName(),
        bottleneck.getConstraint().getName(),
        bottleneck.getUtilizationPercent());

    return maxRate;
}

Extending to Other Equipment

Step-by-Step Guide

To add capacity constraint support to a new equipment type:

Step 1: Implement the Interface

import neqsim.process.equipment.capacity.CapacityConstrainedEquipment;
import neqsim.process.equipment.capacity.CapacityConstraint;
import neqsim.process.equipment.capacity.StandardConstraintType;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

public class MyEquipment extends ProcessEquipmentBaseClass
    implements CapacityConstrainedEquipment {

    // Storage for constraints
    private Map<String, CapacityConstraint> capacityConstraints = new LinkedHashMap<>();

Step 2: Initialize Default Constraints in Constructor

    public MyEquipment(String name, StreamInterface inletStream) {
        super(name, inletStream);
        initializeCapacityConstraints();
    }

    /**
     * Initializes default capacity constraints for this equipment.
     */
    private void initializeCapacityConstraints() {
        // Add constraints relevant to this equipment type
        CapacityConstraint flowConstraint = new CapacityConstraint(
            StandardConstraintType.PUMP_FLOW_RATE,
            CapacityConstraint.ConstraintType.DESIGN)
            .setDesignValue(designFlowRate)
            .setMaxValue(maxFlowRate)
            .setValueSupplier(() -> this.getFlowRate());

        capacityConstraints.put(flowConstraint.getName(), flowConstraint);
    }

Step 3: Implement Required Interface Methods

    /** {@inheritDoc} */
    @Override
    public Map<String, CapacityConstraint> getCapacityConstraints() {
        return Collections.unmodifiableMap(capacityConstraints);
    }

    /** {@inheritDoc} */
    @Override
    public CapacityConstraint getBottleneckConstraint() {
        CapacityConstraint bottleneck = null;
        double maxUtil = 0.0;
        for (CapacityConstraint c : capacityConstraints.values()) {
            double util = c.getUtilization();
            if (!Double.isNaN(util) && util > maxUtil) {
                maxUtil = util;
                bottleneck = c;
            }
        }
        return bottleneck;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isCapacityExceeded() {
        for (CapacityConstraint c : capacityConstraints.values()) {
            if (c.isViolated()) {
                return true;
            }
        }
        return false;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isHardLimitExceeded() {
        for (CapacityConstraint c : capacityConstraints.values()) {
            if (c.isHardLimitExceeded()) {
                return true;
            }
        }
        return false;
    }

    /** {@inheritDoc} */
    @Override
    public double getMaxUtilization() {
        double maxUtil = 0.0;
        for (CapacityConstraint c : capacityConstraints.values()) {
            double util = c.getUtilization();
            if (!Double.isNaN(util) && util > maxUtil) {
                maxUtil = util;
            }
        }
        return maxUtil;
    }

    /** {@inheritDoc} */
    @Override
    public void addCapacityConstraint(CapacityConstraint constraint) {
        if (constraint != null && constraint.getName() != null) {
            capacityConstraints.put(constraint.getName(), constraint);
        }
    }

    /** {@inheritDoc} */
    @Override
    public boolean removeCapacityConstraint(String constraintName) {
        return capacityConstraints.remove(constraintName) != null;
    }

    /** {@inheritDoc} */
    @Override
    public void clearCapacityConstraints() {
        capacityConstraints.clear();
    }

Step 4: Update Constraints When Design Values Change

    /**
     * Sets the design flow rate.
     *
     * @param flowRate design flow rate in m³/hr
     */
    public void setDesignFlowRate(double flowRate) {
        this.designFlowRate = flowRate;
        updateFlowConstraint();
    }

    private void updateFlowConstraint() {
        CapacityConstraint existing = capacityConstraints.get("flowRate");
        if (existing != null) {
            existing.setDesignValue(designFlowRate);
            existing.setMaxValue(maxFlowRate);
        }
    }

Example: Implementing for Pump

public class Pump extends ProcessEquipmentBaseClass
    implements CapacityConstrainedEquipment {

    private Map<String, CapacityConstraint> capacityConstraints = new LinkedHashMap<>();
    private double designFlowRate = 100.0;  // m³/hr
    private double designHead = 50.0;       // m
    private double designNPSH = 3.0;        // m (required)

    private void initializeCapacityConstraints() {
        // Flow rate constraint
        CapacityConstraint flow = new CapacityConstraint(
            StandardConstraintType.PUMP_FLOW_RATE,
            CapacityConstraint.ConstraintType.DESIGN)
            .setDesignValue(designFlowRate)
            .setMaxValue(designFlowRate * 1.2)
            .setValueSupplier(() -> getInletStream().getFlowRate("m3/hr"));
        capacityConstraints.put(flow.getName(), flow);

        // Power constraint
        CapacityConstraint power = new CapacityConstraint(
            StandardConstraintType.PUMP_POWER,
            CapacityConstraint.ConstraintType.HARD)
            .setDesignValue(motorRatedPower)
            .setMaxValue(motorRatedPower * 1.1)  // 110% service factor
            .setValueSupplier(() -> getPower());
        capacityConstraints.put(power.getName(), power);

        // NPSH margin constraint (inverted - higher available is better)
        CapacityConstraint npsh = new CapacityConstraint(
            StandardConstraintType.PUMP_NPSH_MARGIN,
            CapacityConstraint.ConstraintType.HARD)
            .setDesignValue(designNPSH * 1.3)  // 30% margin required
            .setMinValue(designNPSH)            // Absolute minimum
            .setValueSupplier(() -> getNPSHavailable());
        capacityConstraints.put(npsh.getName(), npsh);
    }

    // ... implement all interface methods as shown above
}

Example: Implementing for Heat Exchanger

public class HeatExchanger extends ProcessEquipmentBaseClass
    implements CapacityConstrainedEquipment {

    private Map<String, CapacityConstraint> capacityConstraints = new LinkedHashMap<>();
    private double designDuty = 1000.0;        // kW
    private double designApproachTemp = 5.0;   // °C

    private void initializeCapacityConstraints() {
        // Duty constraint
        CapacityConstraint duty = new CapacityConstraint(
            StandardConstraintType.HEAT_EXCHANGER_DUTY,
            CapacityConstraint.ConstraintType.DESIGN)
            .setDesignValue(designDuty)
            .setMaxValue(designDuty * 1.1)  // 10% overdesign typical
            .setValueSupplier(() -> Math.abs(getDuty()) / 1000.0);  // Convert W to kW
        capacityConstraints.put(duty.getName(), duty);

        // Approach temperature constraint (inverted - want to stay above minimum)
        CapacityConstraint approach = new CapacityConstraint(
            StandardConstraintType.HEAT_EXCHANGER_APPROACH_TEMP,
            CapacityConstraint.ConstraintType.SOFT)
            .setDesignValue(designApproachTemp)
            .setMinValue(2.0)  // Minimum practical approach
            .setValueSupplier(() -> getApproachTemperature());
        capacityConstraints.put(approach.getName(), approach);

        // Pressure drop constraint
        CapacityConstraint pressureDrop = new CapacityConstraint(
            StandardConstraintType.HEAT_EXCHANGER_PRESSURE_DROP,
            CapacityConstraint.ConstraintType.SOFT)
            .setDesignValue(maxPressureDrop)
            .setMaxValue(maxPressureDrop * 1.5)
            .setValueSupplier(() -> getPressureDrop("bar"));
        capacityConstraints.put(pressureDrop.getName(), pressureDrop);
    }
}

Example: Implementing for Pipe/Pipeline

public class Pipe extends ProcessEquipmentBaseClass
    implements CapacityConstrainedEquipment {

    private Map<String, CapacityConstraint> capacityConstraints = new LinkedHashMap<>();
    private double erosionalVelocityRatio = 0.8;  // Design at 80% of erosional

    private void initializeCapacityConstraints() {
        // Velocity constraint
        CapacityConstraint velocity = new CapacityConstraint(
            StandardConstraintType.PIPE_VELOCITY,
            CapacityConstraint.ConstraintType.DESIGN)
            .setDesignValue(calculateErosionalVelocity() * erosionalVelocityRatio)
            .setMaxValue(calculateErosionalVelocity())
            .setValueSupplier(() -> getFluidVelocity());
        capacityConstraints.put(velocity.getName(), velocity);

        // Erosional velocity ratio constraint
        CapacityConstraint erosional = new CapacityConstraint(
            StandardConstraintType.PIPE_EROSIONAL_VELOCITY,
            CapacityConstraint.ConstraintType.HARD)
            .setDesignValue(erosionalVelocityRatio)
            .setMaxValue(1.0)  // Never exceed erosional velocity
            .setValueSupplier(() -> getFluidVelocity() / calculateErosionalVelocity());
        capacityConstraints.put(erosional.getName(), erosional);

        // Pressure drop constraint
        CapacityConstraint dp = new CapacityConstraint(
            StandardConstraintType.PIPE_PRESSURE_DROP,
            CapacityConstraint.ConstraintType.SOFT)
            .setDesignValue(allowablePressureDrop)
            .setMaxValue(allowablePressureDrop * 1.2)
            .setValueSupplier(() -> getPressureDrop("bar"));
        capacityConstraints.put(dp.getName(), dp);
    }
}

StandardConstraintType Reference

Category Type Name Unit Description
Separator SEPARATOR_GAS_LOAD_FACTOR gasLoadFactor m/s Souders-Brown K-factor
  SEPARATOR_LIQUID_RESIDENCE_TIME liquidResidenceTime s Liquid hold-up time
  SEPARATOR_LIQUID_LEVEL liquidLevel % Level as % of capacity
Compressor COMPRESSOR_SPEED speed RPM Maximum rotational speed
  COMPRESSOR_MIN_SPEED minSpeed RPM Minimum stable speed (from curve)
  COMPRESSOR_POWER power % Power utilization vs speed-dependent driver limit
  (custom) ratedPower % Power utilization vs driver rated power
  COMPRESSOR_SURGE_MARGIN surgeMargin % Distance to surge
  COMPRESSOR_STONEWALL_MARGIN stonewallMargin % Distance to stonewall
  COMPRESSOR_DISCHARGE_TEMP dischargeTemperature °C Discharge temperature
  COMPRESSOR_PRESSURE_RATIO pressureRatio - Compression ratio
Pump PUMP_FLOW_RATE flowRate m³/hr Volumetric flow
  PUMP_HEAD head m Developed head
  PUMP_POWER power kW Shaft power
  PUMP_NPSH_MARGIN npshMargin m NPSH available margin
Heat Exchanger HEAT_EXCHANGER_DUTY duty kW Heat transfer rate
  HEAT_EXCHANGER_APPROACH_TEMP approachTemperature °C Minimum ΔT
  HEAT_EXCHANGER_PRESSURE_DROP pressureDrop bar Pressure loss
Valve VALVE_CV_UTILIZATION cvUtilization % Cv used / Cv available
  VALVE_PRESSURE_DROP pressureDrop bar Pressure loss
  VALVE_AIV AIV kW Acoustic-induced vibration power
Pipe PIPE_VELOCITY velocity m/s Fluid velocity
  PIPE_EROSIONAL_VELOCITY erosionalVelocityRatio - v/v_erosional ratio
  PIPE_PRESSURE_DROP pressureDrop bar/km Pressure gradient
  PIPE_AIV AIV kW Acoustic-induced vibration power

Notes:

Flow-Induced Vibration (FIV) Analysis

Pipeline equipment (Pipeline, PipeBeggsAndBrills, AdiabaticPipe, Manifold) includes built-in FIV analysis capabilities with constraints based on industry standards.

FIV Metrics

Metric Description Risk Threshold
LOF (Likelihood of Failure) Dimensionless indicator based on density, velocity, GVF, and support stiffness > 0.6 = High risk
FRMS RMS force per meter (N/m) - dynamic loading indicator > 500 N/m = High risk
Erosional Velocity Maximum velocity per API RP 14E: Ve = C/√ρ > 100% = Erosion risk

Support Arrangement Coefficients

The LOF calculation uses support arrangement coefficients per industry practice:

Support Type Coefficient Description
Stiff 1.0 Rigid supports, short spans
Medium stiff 1.5 Standard pipe racks
Medium 2.0 Longer spans, typical offshore
Flexible 3.0 Flexible supports, risers

Using FIV Analysis

// Pipeline with FIV constraints
PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("Export Line", feed);
pipe.setLength(5000.0);
pipe.setDiameter(0.2032);  // 8 inch
pipe.setThickness(0.008);   // 8mm wall
pipe.setSupportArrangement("Medium stiff");
pipe.run();

// Get FIV metrics
double lof = pipe.calculateLOF();
double frms = pipe.calculateFRMS();
double erosionalVel = pipe.getErosionalVelocity();
double actualVel = pipe.getMixtureVelocity();

System.out.printf("LOF: %.3f (Risk: %s)%n", lof, lof > 0.6 ? "HIGH" : "Low");
System.out.printf("FRMS: %.1f N/m%n", frms);
System.out.printf("Velocity: %.2f / %.2f m/s (%.1f%% of erosional)%n",
    actualVel, erosionalVel, 100 * actualVel / erosionalVel);

// Get full FIV analysis as Map
Map<String, Object> fivAnalysis = pipe.getFIVAnalysis();

// Get FIV analysis as JSON
String fivJson = pipe.getFIVAnalysisJson();

FIV Analysis Output (JSON)

{
  "LOF": 0.234,
  "LOF_risk": "Low",
  "FRMS_N_per_m": 125.6,
  "FRMS_risk": "Low",
  "mixtureDensity_kg_m3": 85.2,
  "mixtureVelocity_m_s": 12.4,
  "erosionalVelocity_m_s": 18.5,
  "velocityRatio": 0.67,
  "gasVolumeFraction": 0.92,
  "supportArrangement": "Medium stiff",
  "supportCoefficient": 1.5,
  "innerDiameter_m": 0.1872
}

Manifold FIV Analysis

Manifolds provide separate FIV analysis for header and branch lines:

Manifold manifold = new Manifold("Production Manifold", inlet1, inlet2);
manifold.setSplitNumber(3);
manifold.setMaxDesignVelocity(15.0);
manifold.setInnerHeaderDiameter(0.3);
manifold.setInnerBranchDiameter(0.15);
manifold.run();

// Header FIV
double headerLOF = manifold.calculateHeaderLOF();
double headerFRMS = manifold.calculateHeaderFRMS();

// Branch FIV (uses average branch flow)
double branchLOF = manifold.calculateBranchLOF();

// All constraints
Map<String, CapacityConstraint> constraints = manifold.getCapacityConstraints();
// Contains: headerVelocity, branchVelocity, headerLOF, headerFRMS, branchLOF

FIV Design Limits

Set design limits for FIV constraints:

// Set maximum allowable values
pipe.setMaxDesignVelocity(15.0);  // m/s
pipe.setMaxDesignLOF(0.5);        // dimensionless
pipe.setMaxDesignFRMS(400.0);     // N/m

// These become constraint design values
CapacityConstraint lofConstraint = pipe.getCapacityConstraints().get("LOF");
// lofConstraint.getDesignValue() returns 0.5

Note: The setter methods (setMaxDesignVelocity, setMaxDesignLOF, setMaxDesignFRMS, setMaxDesignAIV) automatically invalidate cached constraints, so the new values take effect immediately when getCapacityConstraints() is called. If you need to explicitly reinitialize constraints after other changes, call pipe.reinitializeCapacityConstraints().

Acoustic-Induced Vibration (AIV) Analysis

AIV is caused by high acoustic energy generated by pressure-reducing devices (valves, orifices) and is particularly relevant for high-pressure gas systems. Unlike FIV (which relates to liquid slugging), AIV is critical for dry gas systems.

AIV Formula (Energy Institute Guidelines)

The acoustic power is calculated using the Energy Institute Guidelines formula:

\[W_{acoustic} = 3.2 \times 10^{-9} \cdot \dot{m} \cdot P_1 \cdot \left(\frac{\Delta P}{P_1}\right)^{3.6} \cdot \left(\frac{T}{273.15}\right)^{0.8}\]

Where:

AIV Risk Levels

Acoustic Power (kW) Risk Level Action Required
< 1 LOW No action required
1 - 10 MEDIUM Review piping layout
10 - 25 HIGH Detailed analysis required
> 25 VERY HIGH Mitigation required

Using AIV Analysis in Pipes

// Pipeline with AIV constraint
PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("HP Gas Line", feed);
pipe.setLength(100.0);
pipe.setDiameter(0.2032);  // 8 inch
pipe.setThickness(0.008);   // 8mm wall
pipe.run();

// Get AIV metrics
double aivPower = pipe.calculateAIV();  // kW
double aivLOF = pipe.calculateAIVLikelihoodOfFailure();

System.out.printf("AIV Power: %.2f kW%n", aivPower);
System.out.printf("AIV LOF: %.2f%n", aivLOF);

// Set AIV design limit (default is 25 kW)
pipe.setMaxDesignAIV(10.0);  // kW

// Get FIV analysis (now includes AIV)
Map<String, Object> analysis = pipe.getFIVAnalysis();
// Contains: AIV_power_kW, AIV_risk, AIV_LOF

Using AIV Analysis in Valves

Throttling valves are primary sources of AIV due to large pressure drops:

// Control valve with significant pressure drop
ThrottlingValve valve = new ThrottlingValve("PCV-100", feed);
valve.setOutletPressure(30.0, "bara");  // Large ΔP
valve.run();

// Get AIV metrics
double aivPower = valve.calculateAIV();  // kW

// Calculate AIV LOF (requires downstream pipe geometry)
double downstreamDiameter = 0.2032;  // 8 inch
double downstreamThickness = 0.008;  // 8mm
double aivLOF = valve.calculateAIVLikelihoodOfFailure(
    downstreamDiameter, downstreamThickness);

System.out.printf("Valve AIV Power: %.2f kW%n", aivPower);
System.out.printf("Valve AIV LOF: %.3f%n", aivLOF);

// Set AIV design limit (default is 10 kW for valves)
valve.setMaxDesignAIV(5.0);  // kW - stricter limit

// Access AIV constraint
CapacityConstraint aivConstraint = valve.getCapacityConstraints().get("AIV");
double utilization = aivConstraint.getUtilization();

AIV Analysis Output (JSON)

The getFIVAnalysis() method now includes AIV data:

{
  "LOF": 0.05,
  "LOF_risk": "Low",
  "FRMS_N_per_m": 12.3,
  "FRMS_risk": "Low",
  "AIV_power_kW": 8.45,
  "AIV_risk": "MEDIUM",
  "AIV_LOF": 0.35,
  "mixtureDensity_kg_m3": 45.2,
  "mixtureVelocity_m_s": 18.4,
  "erosionalVelocity_m_s": 25.5,
  "velocityRatio": 0.72,
  "gasVolumeFraction": 0.98,
  "supportArrangement": "Medium stiff",
  "supportCoefficient": 1.5,
  "innerDiameter_m": 0.1872
}

FIV vs AIV: When to Use Each

Metric Applicable Systems Primary Concern
LOF/FRMS (FIV) Two-phase flow with liquid slugging Liquid impacts causing pipe vibration
AIV High-pressure gas with pressure drops Acoustic energy from turbulent flow

For dry gas systems, AIV is typically more relevant than FIV (LOF/FRMS will be near zero)


Best Practices

1. Choose Appropriate Constraint Types

2. Set Meaningful Design Values

Design values should represent the intended operating point, not the maximum:

// Good: Design at normal operation, max at limit
constraint.setDesignValue(10000.0);  // Normal speed
constraint.setMaxValue(11000.0);     // Trip speed

// Bad: Design equals maximum
constraint.setDesignValue(11000.0);  // No warning margin

3. Use Warning Thresholds

The default warning threshold is 90%. Adjust if needed:

constraint.setWarningThreshold(0.85);  // Warn at 85% utilization

4. Handle Missing Data Gracefully

The valueSupplier should return Double.NaN for unavailable data:

.setValueSupplier(() -> {
    if (compressorMap == null) return Double.NaN;
    return compressor.getSpeed();
});

5. Update Constraints When Design Changes

Ensure constraints stay synchronized with design parameters:

public void setDesignSpeed(double speed) {
    this.designSpeed = speed;
    CapacityConstraint c = capacityConstraints.get("speed");
    if (c != null) {
        c.setDesignValue(speed);
    }
}

Equipment Capacity Strategy Registry

The Strategy Registry provides a plugin-based architecture for evaluating equipment capacity constraints without modifying equipment classes. The registry ships with 18 built-in strategies covering all major equipment categories:

Built-in Strategies (18 total)

Strategy Equipment Types Typical Constraints
CompressorCapacityStrategy Compressor speed, power, surgeMargin, stonewallMargin
SeparatorCapacityStrategy Separator, ThreePhaseSeparator gasLoadFactor, liquidResidenceTime, dropletCutSize
PipeCapacityStrategy Pipeline, AdiabaticPipe velocity, pressureDrop, FIV_LOF, FIV_FRMS
ValveCapacityStrategy ThrottlingValve valveOpening, cvUtilization
HeatExchangerCapacityStrategy Heater, Cooler duty, outletTemperature
PumpCapacityStrategy Pump npshMargin, power, flowRate
ExpanderCapacityStrategy Expander speed, power
EjectorCapacityStrategy Ejector compressionRatio, motiveFlow
MixerCapacityStrategy Mixer flowRate, pressureDiff
SplitterCapacityStrategy Splitter flowRate
TankCapacityStrategy Tank fillLevel, fillRate
DistillationColumnCapacityStrategy DistillationColumn floodingFactor, reboilerDuty
ReactorCapacityStrategy GibbsReactor, PlugFlowReactor, StirredTankReactor throughput, residenceTime, conversionRate
PowerGenerationCapacityStrategy GasTurbine, SteamTurbine, HRSG, CombinedCycleSystem power, fuelFlow, exhaustTemp
SubseaEquipmentCapacityStrategy SubseaWell, SubseaTree flowRate, wellheadPressure, chokeOpening
FilterAdsorberCapacityStrategy Filter, SulfurFilter, CharCoalFilter, SimpleAdsorber pressureDrop, throughput
ElectrolyzerCapacityStrategy Electrolyzer, CO2Electrolyzer power, currentDensity, efficiency
WellFlowCapacityStrategy WellFlow flowRate, wellheadPressure, drawdown

Expander note (equipment-level override). The ExpanderCapacityStrategy above belongs to the older CapacityAnalysisEngine path. On the getMaxUtilization() / getUtilizationSnapshotJson() path, Expander overrides the inherited Compressor logic: it removes the inherited consumed-power constraints (power, ratedPower) — which are meaningless for a machine that produces shaft power — and, when a rating is set via expander.setRatedRecoveredPower(kW), adds a recoveredPower HARD constraint sourced from |getPower|. It also overrides isSimulationValid() so an expander’s normal state (negative shaft power, outlet colder than inlet, pressure ratio < 1) is treated as valid. This removes the previously spurious ~150 % expander utilization. See Expanders (Turbo-Expanders) below.

Strategy Architecture

┌─────────────────────────────────────────────────────────────────────┐
│            EquipmentCapacityStrategyRegistry (Singleton)            │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │  findStrategy(equipment)  │  getAllStrategies()                 ││
│  │  register(strategy)       │  getConstraints(equipment)         ││
│  └─────────────────────────────────────────────────────────────────┘│
│                              │                                       │
│         ┌────────────────────┴────────────────────┐                  │
│         ▼                                         ▼                  │
│  ┌──────────────────────┐             ┌─────────────────────────────┐│
│  │ Built-in (18)        │             │  Custom Strategies          ││
│  │ Compressor, Separator│             │  MyEquipmentStrategy        ││
│  │ Pump, Valve, Pipe    │             │  VendorSpecificStrategy     ││
│  │ Reactor, PowerGen    │             │  ...                        ││
│  │ Subsea, Filter       │             │                             ││
│  │ Electrolyzer, Well   │             │                             ││
│  └──────────────────────┘             └─────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────┘

Universal Constraint Support: Since all equipment inherits constraint methods from ProcessEquipmentBaseClass, you can add constraints to ANY equipment — even types without a dedicated strategy. The strategy registry provides pre-configured constraints; manual addCapacityConstraint() works on all equipment.

Using the Strategy Registry

import neqsim.process.equipment.capacity.EquipmentCapacityStrategyRegistry;
import neqsim.process.equipment.capacity.EquipmentCapacityStrategy;

// Get the singleton registry
EquipmentCapacityStrategyRegistry registry =
    EquipmentCapacityStrategyRegistry.getInstance();

// Find strategy for a specific equipment
Compressor compressor = (Compressor) process.getUnit("ExportCompressor");
EquipmentCapacityStrategy strategy = registry.findStrategy(compressor);

if (strategy != null) {
    // Evaluate capacity
    double utilization = strategy.evaluateCapacity(compressor);
    System.out.printf("Compressor utilization: %.1f%%\n", utilization * 100);

    // Get all constraints
    Map<String, CapacityConstraint> constraints = strategy.getConstraints(compressor);
    for (CapacityConstraint c : constraints.values()) {
        System.out.printf("  %s: %.2f %s (%.1f%% of design)\n",
            c.getName(), c.getCurrentValue(), c.getUnit(),
            c.getUtilizationPercent());
    }

    // Check for violations
    List<CapacityConstraint> violations = strategy.getViolations(compressor);
    if (!violations.isEmpty()) {
        System.out.println("Constraint violations:");
        for (CapacityConstraint v : violations) {
            System.out.printf("  - %s: %.2f exceeds %.2f\n",
                v.getName(), v.getCurrentValue(), v.getDesignValue());
        }
    }

    // Get bottleneck constraint
    CapacityConstraint bottleneck = strategy.getBottleneckConstraint(compressor);
    System.out.println("Bottleneck: " + bottleneck.getName());
}

Creating Custom Strategies

Implement EquipmentCapacityStrategy for equipment-specific logic:

import neqsim.process.equipment.capacity.EquipmentCapacityStrategy;

public class MyCustomStrategy implements EquipmentCapacityStrategy {

    @Override
    public boolean supports(ProcessEquipmentInterface equipment) {
        // Return true if this strategy handles this equipment type
        return equipment instanceof MyCustomEquipment;
    }

    @Override
    public int getPriority() {
        // Higher priority = more specific strategy
        return 100;  // Built-in strategies use priority 10
    }

    @Override
    public double evaluateCapacity(ProcessEquipmentInterface equipment) {
        MyCustomEquipment eq = (MyCustomEquipment) equipment;
        // Return utilization as 0.0 to 1.0+
        return eq.getCurrentLoad() / eq.getMaxLoad();
    }

    @Override
    public Map<String, CapacityConstraint> getConstraints(
            ProcessEquipmentInterface equipment) {
        Map<String, CapacityConstraint> constraints = new LinkedHashMap<>();
        MyCustomEquipment eq = (MyCustomEquipment) equipment;

        constraints.put("customLoad",
            new CapacityConstraint("customLoad", "kW", ConstraintType.DESIGN)
                .setDesignValue(eq.getDesignLoad())
                .setMaxValue(eq.getMaxLoad())
                .setUnit("kW")
                .setValueSupplier(() -> eq.getCurrentLoad()));

        return constraints;
    }

    @Override
    public List<CapacityConstraint> getViolations(
            ProcessEquipmentInterface equipment) {
        List<CapacityConstraint> violations = new ArrayList<>();
        for (CapacityConstraint c : getConstraints(equipment).values()) {
            if (c.isViolated()) {
                violations.add(c);
            }
        }
        return violations;
    }

    @Override
    public CapacityConstraint getBottleneckConstraint(
            ProcessEquipmentInterface equipment) {
        CapacityConstraint bottleneck = null;
        double maxUtilization = 0.0;
        for (CapacityConstraint c : getConstraints(equipment).values()) {
            if (c.getUtilization() > maxUtilization) {
                maxUtilization = c.getUtilization();
                bottleneck = c;
            }
        }
        return bottleneck;
    }

    @Override
    public boolean isWithinHardLimits(ProcessEquipmentInterface equipment) {
        for (CapacityConstraint c : getConstraints(equipment).values()) {
            if (c.getType() == ConstraintType.HARD && c.isHardLimitExceeded()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean isWithinSoftLimits(ProcessEquipmentInterface equipment) {
        for (CapacityConstraint c : getConstraints(equipment).values()) {
            if (c.getType() == ConstraintType.SOFT && c.isViolated()) {
                return false;
            }
        }
        return true;
    }
}

// Register the custom strategy
registry.registerStrategy(new MyCustomStrategy());

Built-in Strategies

Strategy Equipment Type Constraints Evaluated
CompressorCapacityStrategy Compressor speed, power, surgeMargin, stonewallMargin, dischargeTemperature
SeparatorCapacityStrategy Separator liquidLevel, gasLoadFactor
PumpCapacityStrategy Pump power, npshMargin, flowRate
ValveCapacityStrategy Valve valveOpening, pressureDropRatio
PipeCapacityStrategy Pipeline velocity, pressureDrop
HeatExchangerCapacityStrategy HeatExchanger duty, outletTemperature

For detailed usage and integration with the ProcessOptimizationEngine, see Optimizer Plugin Architecture.

Integration with OilGasProcessSimulationOptimization

The example simulation class demonstrates integration:

// After running the process
ProcessOutputResults results = simulation.getOutput();

// Check separator capacity
if (results.isAnySeparatorOverloaded()) {
    System.out.println("Separator capacity exceeded!");
    for (Map.Entry<String, Double> e : results.getSeparatorCapacityUtilization().entrySet()) {
        if (e.getValue() > 100.0) {
            System.out.printf("  %s at %.1f%%\n", e.getKey(), e.getValue());
        }
    }
}

// Check compressor speed limits
if (results.isAnyCompressorOverspeed()) {
    System.out.println("Compressor speed limit exceeded!");
}

// Use ProcessSystem methods for deeper analysis
ProcessSystem process = simulation.getProcess();
BottleneckResult bottleneck = process.findBottleneck();

Capacity Utilization Snapshot (ML / RL Observation Vector)

Both ProcessSystem and ProcessModel expose getUtilizationSnapshotJson(), and the ProcessAutomation facade exposes getUtilizationSnapshot() which delegates to whichever it wraps. The snapshot is a side-effect-free JSON view of every unit’s capacity utilization — it never calls run(), it only reads the utilization already computed by each unit’s constraints. This makes it the canonical observation vector for closed-loop and reinforcement-learning optimization, paired with ProcessAutomation.evaluate(...) (the action + reward step).

ProcessAutomation auto = plant.getAutomation();
plant.run(); // or auto.evaluate(...) — the snapshot reflects the most recent solve
String json = auto.getUtilizationSnapshot();

The returned JSON (schema "1.0") has the shape:

{
  "schemaVersion": "1.0",
  "units": [
    {
      "area": "Export compression",
      "name": "30-KA-01",
      "type": "Compressor",
      "capacityAnalysisEnabled": true,
      "maxUtilization": 0.83,
      "maxUtilizationPercent": 83.0,
      "limitingConstraint": "power",
      "feasible": true,
      "hardLimitExceeded": false,
      "power_kW": 1240.5,
      "constraints": [
        {"name": "power", "utilization": 0.83, "utilizationPercent": 83.0,
         "current": 83.0, "design": 100.0, "unit": "%", "enabled": true, "violated": false,
         "dataSource": "design"}
      ]
    }
  ],
  "bottleneck": {"name": "30-KA-01", "utilization": 0.83,
                 "utilizationPercent": 83.0, "limitingConstraint": "power"},
  "anyOverloaded": false,
  "anyHardLimitExceeded": false
}
Per-unit snapshot fields
FieldMeaning
areaProcess-area name (only present in a ProcessModel snapshot)
name / typeUnit name and simple class name
maxUtilizationHighest enabled-constraint utilization (0–1; NaN reported as 0)
limitingConstraintName of the constraint driving maxUtilization (or null)
feasibletrue when no enabled constraint is exceeded
power_kWShaft power, present for compressors and pumps only
constraints[]Per-constraint breakdown (utilization, current, design, unit, enabled, violated, and dataSource when set)
dataSourceProvenance tag on each constraint (e.g. "equipment", "design") — lets an agent distinguish a rated/measured limit from an estimate. Emitted only when set.

The plant-wide bottleneck, anyOverloaded, and anyHardLimitExceeded fields summarise the whole flowsheet. Because the snapshot reads constraints rather than re-solving, it is cheap to call on every optimization step.

Closed-loop RL pattern:

Compressors Without a Performance Chart

A compressor’s surge, stonewall, and speed constraints are only physically meaningful when a performance chart is active. Without a chart the compressor runs on a fixed outlet-pressure/polytropic-efficiency model where distance-to-surge is undefined (getDistanceToSurge() returns positive infinity), which would otherwise pin the surge utilization at a degenerate flat 100% that never responds to feed rate.

NeqSim therefore creates those chart-dependent constraints present but disabled for chartless compressors. The constraint objects still exist (so existence checks pass), but they are excluded from getMaxUtilization() and getBottleneckConstraint(). A chartless compressor consequently reports smooth, power-driven utilization. Give the power constraint a basis with:

compressor.getMechanicalDesign().setMaxDesignPower(installedKw); // kW

When a performance chart is later attached, call reinitializeCapacityConstraints() to re-enable the chart-dependent metrics.

Expanders (Turbo-Expanders)

Expander extends Compressor but operates in reverse — it produces shaft power and cools the gas. The inherited compressor capacity logic does not fit this: the consumed-power constraints (power, ratedPower) have no meaning for a power-producing machine, and Compressor.isSimulationValid() treats an expander’s normal operating state (negative shaft power, outlet colder than inlet, pressure ratio < 1) as invalid. Because Compressor.getMaxUtilization() returns a sentinel 1.5 whenever the simulation is flagged invalid with zero computed utilization, a healthy expander used to report a spurious ~150 % utilization.

Expander now fixes this natively by overriding two methods:

Set the rated recovered shaft power to get a meaningful utilization; otherwise the expander simply reports no spurious limit:

Expander expander = new Expander("X-100", feed);
// ... add to process and run ...
expander.setRatedRecoveredPower(5000.0);   // kW — rebuilds the recoveredPower constraint
double util = expander.getMaxUtilization(); // |getPower| / 5000 kW, no spurious 150%

See Also