Skip to the content.

This document provides a high-level introduction to the process optimization capabilities in NeqSim, explaining how the different components relate to each other and when to use each one.

Table of Contents


Quick Navigation

I want to… Use this class Documentation
Find maximum throughput for given pressures ProcessOptimizationEngine Optimizer Plugin Architecture
Optimize arbitrary objectives with constraints ProductionOptimizer Production Optimization Guide
Do multi-objective Pareto optimization ProductionOptimizer.optimizePareto() Multi-Objective Optimization
Run batch parameter studies BatchStudy Batch Studies
Generate and rank candidate flowsheets from feed/product targets ProcessResearcher Process Researcher
Calculate flow rates for pressure boundaries FlowRateOptimizer Flow Rate Optimization
Generate Eclipse lift curves (VFP tables) EclipseVFPExporter Optimizer Plugin Architecture
Evaluate equipment constraints ProcessConstraintEvaluator Capacity Constraint Framework
Apply a facility operating restriction without replacing the equipment rating CapacityConstraint.setOperatingLimit(...) Capacity Constraint Framework
Integrate with external optimizers (SciPy, NLopt) ProcessSimulationEvaluator External Optimizer Integration
Optimize full multi-area process models ProcessModelSimulationEvaluator Use area-qualified ProcessAutomation addresses and installed CapacityConstraint limits
Sample fail-closed 1D/2D operating-envelope slices ProcessModelOperatingEnvelopeStudy Reuse ProcessModelOperatingActionSetEvaluator; every point retains complete candidate/restoration evidence
Compare accepted envelopes across ordered lifecycle periods ProcessModelOperatingEnvelopeLifecycleStudy Admit only complete exact-runtime period evidence; retain stable action identities and report sampled bottleneck migration
Ramp producers until a full facility reaches a bottleneck ProcessModelThroughputOptimizer Use producer mappings, installed capacity tables, and exported case traces
Compare one installed capacity alternative with the same search policy ProcessModelDebottleneckStudy Optimization & Constraints Guide
Rank independently documented capacity alternatives on one compatible metric ProcessModelDebottleneckRanking Optimization & Constraints Guide
Solve constrained NLP (equality + inequality) SQPoptimizer SQP Optimizer
Calibrate model parameters to data BatchParameterEstimator Data Reconciliation and Steady-State Detection
Load optimization config from YAML/JSON ProductionOptimizationSpecLoader YAML Spec Format

Getting Started

If you are new to process optimization in NeqSim, begin with:

  1. Getting Started
  2. Optimization & Constraints Guide
  3. Constraint Framework

This sequence covers base-run requirements, optimizer selection, and safe variable access through ProcessAutomation.


All Documentation Files

Document Purpose
This Document High-level overview and when to use which optimizer
Optimization & Constraints Guide COMPREHENSIVE: Complete guide to algorithms, constraint types, bottleneck analysis, practical examples
ProductionOptimizer Tutorial (Jupyter) Interactive notebook: algorithms, single/multi-variable, Pareto, constraints
Python Optimization Tutorial (Jupyter) Using SciPy/Python optimizers with NeqSim: constraints, Pareto, global opt
Optimizer Plugin Architecture Equipment capacity strategies, ProcessOptimizationEngine API, VFP export
Production Optimization Guide Complete examples for ProductionOptimizer with Java/Python
Practical Examples Code samples for common optimization tasks
Process Researcher Candidate flowsheet generation and ranking from feed/product specifications
Multi-Objective Optimization Pareto fronts, weighted-sum, epsilon-constraint methods
Batch Studies Parallel parameter sweeps and sensitivity analysis
Flow Rate Optimization FlowRateOptimizer and lift curve tables
External Optimizer Integration ProcessSimulationEvaluator and ProcessModelSimulationEvaluator for Python/SciPy integration
Getting Started Step-by-step first optimization workflow for process models/systems
Optimizer Guide Detailed API reference for all optimizer classes
SQP Optimizer Sequential Quadratic Programming — constrained NLP with BFGS + active-set QP
Capacity Constraint Framework Equipment constraints and bottleneck detection

Architecture Overview

NeqSim provides three main levels of optimization capability:

┌─────────────────────────────────────────────────────────────────────────────┐
│                    LEVEL 3: Application-Specific                             │
│  ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐             │
│  │ ProductionOpt.   │ │ BatchParameter   │ │ EclipseVFP       │             │
│  │ (max throughput) │ │ (model calibr.)  │ │ (lift curves)    │             │
│  └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘             │
└───────────┼──────────────────────────────────────────┼───────────────────────┘
            │                    │                     │
┌───────────┼──────────────────────────────────────────┼───────────────────────┐
│           ▼        LEVEL 2: Unified Engine           ▼                       │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │                   ProcessOptimizationEngine                              ││
│  │  • findMaximumThroughput()   • evaluateAllConstraints()                  ││
│  │  • analyzeSensitivity()      • generateLiftCurve()                       ││
│  │  • Search algorithms: Binary, Golden-Section, BFGS                       ││
│  └──────────────────────────────────┬──────────────────────────────────────┘│
└─────────────────────────────────────┼────────────────────────────────────────┘
                                      │
┌─────────────────────────────────────┼────────────────────────────────────────┐
│                                     ▼                                        │
│                   LEVEL 1: Equipment Constraint Layer                        │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │            EquipmentCapacityStrategyRegistry (18 Built-in Strategies)    ││
│  │  ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐            ││
│  │  │Compressor  │ │ Separator  │ │   Pump     │ │  Reactor   │            ││
│  │  │ Strategy   │ │  Strategy  │ │ Strategy   │ │  Strategy  │ + custom   ││
│  │  │Pipe, Valve │ │  HX, Tank  │ │ Expander   │ │ PowerGen   │            ││
│  │  │Mixer, Split│ │  Ejector   │ │ Distill.   │ │ Subsea,Well│            ││
│  │  └────────────┘ └────────────┘ └────────────┘ └────────────┘            ││
│  └─────────────────────────────────────────────────────────────────────────┘│
│                                     │                                        │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │         ProcessEquipmentBaseClass (Universal Constraint Storage)         ││
│  │  All 144+ equipment types inherit: addCapacityConstraint(),              ││
│  │  getMaxUtilization(), isCapacityExceeded(), getBottleneckConstraint()    ││
│  └─────────────────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────────────────┘

The Two Main Optimizers

The single-system Java snippets use the complete OptimizationGuideSetup.java from Getting Started. Compile that class first; put snippet imports above your class and execute the remaining statements in a method. Run the base setup below before independent extensions. The multi-area sections explicitly require the named model, installed limits and actions described in their linked tests and guides.

ProcessOptimizationEngine

Purpose: Find maximum throughput for given inlet/outlet pressure conditions while respecting equipment constraints.

Best for:

Key Features:

import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.util.optimizer.ProcessOptimizationEngine;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

ProcessSystem process = OptimizationGuideSetup.createProcess();
Stream feed = (Stream) process.getUnit("feed");
Compressor compressor = (Compressor) process.getUnit("comp");
Logger logger = LogManager.getLogger("OptimizationOverview");
ProcessOptimizationEngine engine = new ProcessOptimizationEngine(process);
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);
if (!result.isConverged()) {
    throw new IllegalStateException(result.getErrorMessage());
}
feed.setFlowRate(result.getOptimalValue(), "kg/hr");
process.run();
logger.info("Max flow: {} kg/hr; bottleneck: {}", result.getOptimalValue(), result.getBottleneck());

ProductionOptimizer

Purpose: General-purpose optimization with arbitrary objective functions, multiple decision variables, and user-defined constraints.

Best for:

Key Features:

import java.util.Arrays;
import java.util.List;
import neqsim.process.util.optimizer.ProductionOptimizer;
import neqsim.process.util.optimizer.ProductionOptimizer.*;

ProductionOptimizer optimizer = new ProductionOptimizer();
OptimizationConfig config = new OptimizationConfig(10000.0, 200000.0)
    .rateUnit("kg/hr").defaultUtilizationLimit(1.0).utilizationMarginFraction(0.05)
    .tolerance(1.0).searchMode(SearchMode.GOLDEN_SECTION_SCORE).maxIterations(60);
List<OptimizationObjective> objectives = Arrays.asList(
    new OptimizationObjective("throughput",
        proc -> ((Stream) proc.getUnit("outlet")).getFlowRate("kg/hr"),
        1.0, ObjectiveType.MAXIMIZE));
ProductionOptimizer.OptimizationResult result = optimizer.optimize(process, feed, config, objectives, null);
if (!result.isFeasible()) {
    throw new IllegalStateException("No feasible operating point");
}
feed.setFlowRate(result.getOptimalRate(), "kg/hr");
process.run();
logger.info("Optimal rate: {} kg/hr", result.getOptimalRate());

When to Use Which Optimizer

Scenario Recommended Why
“What’s the max flow at P_in=50, P_out=10?” ProcessOptimizationEngine Designed exactly for this
“Find bottleneck equipment” ProcessOptimizationEngine Has constraint evaluation built-in
“Format supplied well BHP as VFPPROD” EclipseVFPExporter Explicit axes/units; see export contract
“Minimize operating cost” ProductionOptimizer Custom objective function support
“Optimize pressure AND flow rate together” ProductionOptimizer Multi-variable support
“Trade off throughput vs power consumption” ProductionOptimizer.optimizePareto() Pareto multi-objective
“Increase several producers until the full facility reaches a bottleneck” ProcessModelThroughputOptimizer Maps producers, loads installed capacities, and records the active bottleneck per case
“Trace how the feasible region and installed or plant bottleneck changes across one or two declared actions” ProcessModelOperatingEnvelopeStudy Samples the existing transactional action evaluator, keeps infeasible points, and stops on unsafe baseline recovery
“Compare accepted envelopes as reservoir, availability, and operating limits change” ProcessModelOperatingEnvelopeLifecycleStudy Aggregates independently validated periods with stable action identities and no shared mutable model state
“Quantify one documented equipment expansion against the installed case” ProcessModelDebottleneckStudy Pairs identical searches, metrics, evidence, and state recovery
“Rank several independently evaluated equipment alternatives” ProcessModelDebottleneckRanking Requires one exact metric definition and one identical deterministic baseline; rejects unlike evidence
“Evaluate 100 scenarios in parallel” ProductionOptimizer Has parallel evaluation
“Calibrate model to match field data” BatchParameterEstimator Levenberg-Marquardt for data fitting

Full ProcessModel Optimization

For single-process searches, ProductionOptimizer rejects failed compressor-speed solves and invalid fixed-speed chart points as hard infeasible trials. These appear as compressorSpeedSolve and compressorChartOperatingPoint constraints. Failed trials do not evaluate objectives or user constraints against stale downstream results, including when optional simulation validation is disabled. Other calculation exceptions propagate so configuration and programming errors remain visible.

Use ProcessModelThroughputOptimizer for large fixed-equipment studies such as increasing one or more producer feed rates until a separator, compressor, valve, heat exchanger, or export train reaches its installed capacity. It is the ergonomic layer for the common full-facility throughput-to-bottleneck task.

Use ProcessModelSimulationEvaluator directly when you need a lower-level black-box bridge to SciPy, NLopt, SQP, Pyomo, or another external optimizer.

Use ProcessModelOperatingEnvelopeStudy when the engineering question is a sampled one- or two-dimensional operating envelope over already-declared ProcessModelOperatingAction controls. The study reuses ProcessModelOperatingActionSetEvaluator for every grid point, so failed or infeasible points retain the same constraint and restoration evidence and sampling stops immediately if the mutable baseline cannot be restored and reconverged. At feasible points, the leading row is selected from finite normalized installed-equipment and typed plant/shared/coupled evidence; raw kW, velocity, rate, surge margin, and other unlike physical quantities are never compared. Infeasible points use the exact fail-closed rejection source: the first applicable required hydraulic binding, then typed plant/shared/coupled evidence, then the first violated hard model constraint in declaration order. Each adjacent grid edge also reports an OperatingRegionTransition when feasibility, the exact evaluator outcome, or the leading constraint/evidence layer changes. The record preserves both endpoint outcomes, action semantics, interval width, and constraint identities. It always states that the event is sampled and not interpolated; this is especially important for discrete route or line-up actions. A sampled slice is not proof of a continuous feasible boundary or optimizer active set.

For a live field network, add FieldWellNetworkProcessUnit to the normal process area and address its canonical choke and edge controls through ProcessAutomation. This lets the same transactional evaluator run the coupled well/network solve before separator, compressor, common-shaft and shared-power evidence is captured. Splitter route actions preserve the requested outlet fraction exactly and proportionally rescale the remaining outlets, which is required for deterministic candidate read-back and exact reverse-order restoration. Common-shaft casing/allocation consistency uses the explicitly configured power-balance tolerance; stale calculation identities, non-finite evidence and discrepancies outside that tolerance still fail closed.

A fixed separator-arrival boundary can be declared as a continuous action through sink.<node>.pressure. Discovery exposes that input only for a fixed-pressure sink; calculated node pressure remains an output and attempts to address a junction as a sink are rejected. Route-conditioned pressure studies should use a separate exact 2D route × pressure slice. They must not collapse route choice into a continuous variable or promote the combined controls to an interpolated 3D operating surface.

A variable common-shaft speed can likewise be exposed through a fixed-speed CompressorShaftCalculator action. Sample separator arrival pressure × shaft speed as an exact 2D slice: each cell runs the coupled process and retains the existing export-pressure, compressor-map, common-speed, driver/gearbox and shared-power evidence. Reject incomplete, stale or non-finite cells; do not relax those constraints to fill the slice or infer an unsampled continuous surface.

When a declared driver limit is the qualification target, add a separate exact 1D shaft-speed slice and refine only an evaluated feasible/infeasible crossing. Preserve both terminal bracket endpoints, their signed driver-power margins, every refinement recovery gate, and the per-casing surge/stonewall evidence. The refined bracket is diagnostic: do not infer monotonic behavior outside it or promote its midpoint to an operating set point without the normal optimizer acceptance and replay.

A casing-map boundary uses the same exact speed action and the installed surgeMargin or stonewallMargin row. When a reviewed facility procedure requires more clearance than the preserved map default, configure an operating override on that row and keep the map-derived value supplier, physical unit, source reference and signed margin in the receipt. To demonstrate bottleneck migration, restore that override before applying a tighter driver rating and reevaluate the same low/high speed points with the same evaluator. The two receipts must identify the map row and the driver row independently; a driver-limited result must never hide a negative casing-map margin, and neither receipt authorizes changing an OEM map or protection setting.

The source-backed living acceptance also exports this pair as map_driver_migration_evidence. The receipt is bound to the living-task cycle, evaluator, continuous speed action, unit and identical sampled endpoints. It retains both complete slice records, the configured map-limit role and provenance, the independently refined map and driver boundaries, explicit map-override clearance, and complete baseline restoration. Set production.require_map_driver_migration_evidence: true when the receipt is required. The existing #4187 production guard reconstructs sampled transitions and every midpoint recovery gate before the proposal can enter the ledger; promotion then carries the unchanged receipt into results.json and the reviewed baseline snapshot. A changed or tampered constraint identity, limit role, provenance, endpoint grid, cycle ID, refinement, or restoration flag fails closed. This is persistence of advisory qualification evidence, not a second optimizer or permission to change an OEM map, driver rating, or operating set point.

The living report renders every valid ledger receipt in a cycle-ordered map-to-driver timeline. Superseded receipts remain visible with their invalidating cycle, while malformed or cycle-mismatched receipts are withheld. The canonical current-best and immutable final reports independently validate the promoted receipt before rendering the exact map restriction, configured operating-limit role and provenance, driver restriction/rating, and identical common-speed endpoints. When lifecycle and migration evidence are both required, the guard also resolves each diagnostic identity to exactly one AVAILABLE row in the same current period’s complete accepted-replay utilization coverage. It rejects an ambiguous or missing identity, the same row used twice, or configured map provenance that remains after the receipt says the temporary override was cleared. Both report views show the joined accepted-replay current value, applicable limit, utilization, basis, and provenance for the map and driver identities. These report views are advisory consumers of guarded evidence; they do not recalculate compressor physics or turn a refined boundary into an approved operating point. Installed-equipment display identities are qualified by process area (area::equipment/constraint), while stable IDs preserve the coverage model, area, and equipment separately. Declared coverage must include both compressor map margins and the equipment-local shaft-power constraint alongside registered plant driver limits, because either driver representation can lead the diagnostic boundary.

Use ProcessModelDebottleneckStudy after the evaluator and direct installed constraints are configured when one documented capacity replacement or expansion must be compared with the installed baseline. It uses the same search policy for both scenarios, freezes immutable objective/constraint/metric evidence, and restores the installed limit plus pre-study operating point. This is a paired screening study, not an equipment-sizing algorithm or economic approval.

Use ProcessModelDebottleneckRanking after independent paired studies complete when alternatives must be ordered by one comparable production, power, energy, emissions, or screening-economic delta. The policy requires exact units and provenance plus an identical deterministic baseline. It does not combine unlike metrics, infer a shadow price, or provide investment approval.

The evaluator is deliberately a black-box bridge. It keeps the full plant model intact, lets external optimizers pass a vector of decision variables, runs ProcessModel.run(), and returns objective values, constraint margins, feasibility, and the active bottleneck.

Requirement Pattern
Producer controls Use addProducer(...) with area-qualified addresses such as wells::feed.flowRate
Scenario-level multipliers Use addProducerMultiplier(...) for variables not exposed by automation
Objective Use setObjective(...), typically export gas, export oil, total sales rate, or power-normalized production
Installed equipment limits Attach CapacityConstraint objects or load a CSV table with loadInstalledCapacities(...)
Search Use findMaximumThroughput(lower, upper, tolerance) for scalar producer-ramp studies
Bottleneck reporting Read getBestFeasibleCase(), getFirstInfeasibleCase(), and the case table rows

The typical workflow is:

  1. Build each process area as a separate ProcessSystem and compose them in a ProcessModel.
  2. Add installed capacity constraints to equipment whose sizes are fixed.
  3. Register producer feed rates or scenario multipliers as throughput controls.
  4. Register the objective, for example export gas flow, oil export rate, or total sales flow.
  5. Run findMaximumThroughput(...) to bracket the first infeasible case and binary-search the maximum feasible multiplier.
  6. Export the case table with ProcessModelThroughputResult.exportToCSV(...) or serialize it with toJson().
  7. Inspect the best feasible case, first infeasible case, and active bottleneck metadata for the recommended operating point.

The high-level API pattern below is covered by the focused unit test ProcessModelThroughputOptimizerTest.

// Requires connected "wells" and "separation" areas with a 10000 kg/hr baseline feed.
// Save the installed-capacity CSV shown below before running this fragment.
ProcessModelThroughputOptimizer optimizer = new ProcessModelThroughputOptimizer(model);
optimizer.addProducer("feed", "wells::feed.flowRate", 1.0, 2.0, "kg/hr");
optimizer.setObjective("exportGas", new ToDoubleFunction<ProcessModel>() {
    @Override
    public double applyAsDouble(ProcessModel processModel) {
        return processModel.getVariableValue("separation::separator.gasOutStream.flowRate", "kg/hr");
    }
}, "kg/hr");
optimizer.loadInstalledCapacities("installed_capacity.csv");

ProcessModelThroughputResult result = optimizer.findMaximumThroughput(1.0, 2.0, 0.01);
ThroughputCaseRow best = result.getBestFeasibleCase();
ThroughputCaseRow firstLimit = result.getFirstInfeasibleCase();
result.exportToCSV("throughput_trace.csv");

Active bottleneck rows preserve limit provenance, evidence-quality confidence, the inclusive scalar validity range, and whether the snapshotted current load is inside that range. Check hasConfidence() and hasValidityRange() before reading numeric metadata. JSON uses null for unset values; CSV retains explicit presence flags and blank unset-value cells. These fields are diagnostics only and do not change feasibility or throughput search.

The installed-capacity table uses one row per equipment limit:

area,equipment,constraint,currentValueAddress,designValue,maxValue,unit,severity,enabled
separation,separator,installedGasCapacity,wells::feed.flowRate,15000,16500,kg/hr,HARD,true

For custom optimizers, use the underlying ProcessModelSimulationEvaluator. The API pattern below is covered by the focused unit test ProcessModelSimulationEvaluatorTest.

// Uses the same connected two-area model and its installed separator limit.
ProcessModelSimulationEvaluator evaluator = new ProcessModelSimulationEvaluator(model);
evaluator.addParameter("wells::feed.flowRate", 5000.0, 20000.0, "kg/hr");
evaluator.addObjective("exportGas", new ToDoubleFunction<ProcessModel>() {
    @Override
    public double applyAsDouble(ProcessModel processModel) {
        return processModel.getVariableValue("separation::separator.gasOutStream.flowRate", "kg/hr");
    }
}, ProcessModelSimulationEvaluator.ObjectiveDefinition.Direction.MAXIMIZE);
evaluator.addConstraintUpperBound("feedLimit", new ToDoubleFunction<ProcessModel>() {
    @Override
    public double applyAsDouble(ProcessModel processModel) {
        return processModel.getVariableValue("wells::feed.flowRate", "kg/hr");
    }
}, 15000.0);
evaluator.addEquipmentCapacityConstraints();

ProcessModelSimulationEvaluator.EvaluationResult result = evaluator.evaluate(new double[] {12000.0});
ProcessModelSimulationEvaluator.BottleneckStatus bottleneck = result.getActiveBottleneck();
List<ProcessModelSimulationEvaluator.BottleneckStatus> ranked =
    result.getRankedCapacityConstraints();
List<InstalledEquipmentCapacityEvidence> installed =
    result.getInstalledEquipmentCapacityEvidence();

Each successful evaluate(...) call samples every registered installed-capacity supplier exactly once and reuses that value for normalized feasibility, legacy bottleneck ranking, and immutable InstalledEquipmentCapacityEvidence. The evidence rows are ordered by descending utilization and remain unchanged after later evaluations or equipment mutation, so a case history can reveal an emerging compressor, separator, pipeline, utility, or export bottleneck before it becomes the leading constraint.

Do not interpret a normalized value using the equipment unit. getNormalizedUtilization() and getNormalizedMargin() use unit "1"; getCurrentValue(), getApplicableLimit(), getPhysicalMargin(), and getRequiredRelief() use getPhysicalUnit(). Required relief is a signed-residual conversion at the sampled point, not a capacity-sizing recommendation, production-loss estimate, shadow price, economic value, or operating approval. Stable area::equipment/constraint identity, equipment class/reference designation, direct-versus-strategy provenance, type, severity, warning threshold, installed design/minimum/maximum values, source, confidence, validity range, and numerical status are retained for Java serialization and JPype. ThroughputCaseRow preserves the same list for every case generated by ProcessModelThroughputOptimizer; its JSON representation includes rankedCapacityConstraints with engineering values and evidence metadata.

Use getEvidenceApplicability() to distinguish WITHIN_VALIDITY_RANGE, OUTSIDE_VALIDITY_RANGE, and NOT_ASSESSED results. The engineering order remains utilization-only: confidence is retained as evidence quality and is never converted to a safety probability, feasibility adjustment, or ranking weight. Equal-utilization limits retain model registration order, including the declared order of built-in strategy-generated limits, and each dynamic limit supplier is sampled once per ranking call. Enabled limits with undefined (NaN) utilization remain visible at the end for diagnosis. Call rankCapacityConstraints(model) directly only when a ranking is needed outside an evaluator run; it returns the same snapshot shape without adding a process simulation.

Plant-wide budgets and coupled-equipment restrictions should be registered with addPlantConstraint(...) or addPlantConstraintGroup(...), using immutable PlantConstraintDefinition and exact-calculation PlantConstraintSample evidence. A group callback is sampled once per completed model evaluation, so a PlantCommonShaftEvidence result can contribute its common-speed, casing-map, shaft/driver-power, torque and gearbox rows without rebuilding the train for each constraint. Prefer addCommonShaftConstraintGroup(...) for this adapter: it freezes the canonical definition contract and verifies the returned candidate identity before exposing samples. Use the callback’s evaluator-owned calculationId for the evidence and sourceCalculationId(compressor.getCalculationIdentifier().toString()) for casing freshness when those identities differ. PlantSharedResourceEvidence.toPlantConstraintSample() provides the corresponding single-row path for a shared shaft-power or electrical-demand budget. Missing, stale, non-finite, out-of-validity, mismatched or incomplete evidence fails closed through PlantUtilizationSnapshot. Optimizer arrays retain dimensionless utilization and margin, while EvaluationResult.getPlantConstraintEvidence() preserves physical units, provenance, coverage and operating status in registration order. Runtime callbacks are transient. A deserialized evaluator therefore retains its registration metadata but fails closed; construct a fresh evaluator and re-register the callbacks before resuming optimization.

ProcessModelSimulationEvaluator complements rather than replaces the other optimizers. Use ProcessOptimizationEngine for compact throughput cases on one process, ProductionOptimizer for existing single-system objective workflows, and ProcessModelSimulationEvaluator when the optimization boundary is the full plant model.


Relationship Diagram

┌──────────────────────────────────────────────────────────────────────────────┐
│                           USER CODE                                          │
└─────────┬─────────────────────┬────────────────────────┬─────────────────────┘
          │                     │                        │
          ▼                     ▼                        ▼
┌─────────────────────┐ ┌────────────────────┐ ┌─────────────────────────────┐
│ProcessOptimization  │ │ ProductionOptimizer│ │ BatchParameterEstimator     │
│Engine               │ │                    │ │ (model calibration)         │
│                     │ │• Custom objectives │ │                             │
│• findMaxThroughput()│ │• Multi-variable    │ │• Levenberg-Marquardt        │
│• evaluateConstraint │ │• Pareto multi-obj  │ │• Parameter fitting          │
│• generateLiftCurve()│ │• Parallel eval     │ │• Uncertainty quantification │
└──────────┬──────────┘ └─────────┬──────────┘ └──────────────────────────────┘
           │                      │
           │    ┌─────────────────┘
           │    │
           ▼    ▼
    ┌──────────────────────────────────────────┐
    │            ProcessSystem                  │
    │  (contains equipment, streams, recycles) │
    │                                          │
    │  process.run() → converged state         │
    │  process.getUnit("name") → equipment     │
    └────────────────┬─────────────────────────┘
                     │
                     ▼
    ┌──────────────────────────────────────────┐
    │   Equipment Capacity Strategy Registry   │
    │                                          │
    │  CompressorCapacityStrategy              │
    │  SeparatorCapacityStrategy               │
    │  PumpCapacityStrategy                    │
    │  ... (extensible plugin system)          │
    └────────────────┬─────────────────────────┘
                     │
                     ▼
    ┌──────────────────────────────────────────┐
    │         CapacityConstraint               │
    │                                          │
    │  • name, unit, type                      │
    │  • designValue, maxValue                 │
    │  • getUtilization() → 0.0 to 1.0+        │
    │  • severity (HARD/SOFT)                  │
    └──────────────────────────────────────────┘

Key Concepts

Equipment Capacity Constraints

Equipment constraints define operating limits. Each equipment type has a strategy that extracts constraints:

Equipment Typical Constraints
Compressor Surge margin, max power, operating envelope, speed limits
Separator Liquid level, residence time, gas/liquid capacity
Pump NPSH margin, max power, flow limits
Pipe Erosional velocity, pressure drop
Valve Cv capacity, choke conditions

Constraint availability and default enablement depend on the equipment. Inspect getCapacityConstraints() and each constraint’s isEnabled() flag; explicitly configure installed ratings and enable the required constraints before optimization. Chart-dependent compressor limits remain disabled without a chart. A missing or disabled limit is not proof that the physical equipment has unlimited capacity. See the capacity setup guide.

Utilization Ratio

The utilization ratio is the key metric:

\[\text{utilization} = \frac{\text{actual value}}{\text{design limit}}\]

Bottleneck Detection

The bottleneck is the equipment with the highest utilization ratio:

String bottleneck = engine.findBottleneckEquipment();
// Returns equipment name with highest utilization

Search Algorithms

Both optimizers support multiple search algorithms:

Algorithm Best For Convergence Notes
Binary Search Monotonic problems Fast Assumes feasibility is monotonic
Golden Section Single variable, non-monotonic Moderate Robust, doesn’t require derivatives
Nelder-Mead Multi-variable (2-10 vars) Moderate No gradients needed
PSO (Particle Swarm) Global search, many local optima Slow Good for non-convex problems
Gradient Descent Smooth multi-variable (5-20+) Fast New (Jan 2026) - Finite-difference gradients
BFGS Smooth functions Fast Requires gradient approximation

For full multi-area ProcessModel studies, ProcessModelSimulationEvaluator keeps forward differences as the low-cost default and also exposes FiniteDifferenceMethod.CENTRAL. Both methods strictly honor parameter bounds and divide by the actual applied perturbation. Central differences use a symmetric stencil at interior points and a one-sided fallback at an active bound. Verify step-size stability before interpreting a local derivative as debottlenecking sensitivity or shadow-value evidence. estimateSensitivitiesWithQuality(...) automates one step-halving check, returns the fine-step objective gradient and constraint-margin Jacobian, and records the actual stencil, applied steps, convergence, hard-constraint feasibility, and evaluation errors for every perturbation. Callers select the acceptable relative-disagreement tolerance and must still check nearby points and active equipment/control regimes. Its immutable parameter, selected-objective, and constraint snapshots bind every derivative column and row to names, addresses, units, directions or types, bounds, hard/soft semantics, capacity origin, and the sampled base values and margins. This avoids joining archived matrices back to mutable evaluator definitions. The snapshots preserve raw units; normalize only with declared engineering scales before comparing unlike constraints.

Before using those derivatives in local bottleneck or operating-action logic, construct a SensitivityQualificationPolicy and call assessConstraintSensitivities(policy). Each immutable constraint/parameter assessment retains raw and minimizer objective derivatives, the margin derivative, declared derivative units, stencil, pair-specific coarse/fine disagreements, complete evidence flags, rejection reasons, and actionable diagnostics. Convergence failures, errors, non-finite derivatives, unstable refinement, and fixed parameters always reject a pair; policy controls whether infeasible samples or one-sided stencils are permitted. Permitted cautions remain flagged. getAcceptedConstraintSensitivities(policy) filters accepted pairs without rerunning the model. This is local numerical qualification, not constraint scaling, ranking, active-set inference, a KKT multiplier, or a shadow price.

After qualifying the raw local derivatives, use ConstraintActivityAnalyzer only when every constraint has an explicit positive reference value in its declared unit and recorded provenance. ConstraintScale.fromSnapshot(...) binds that reference to the exact constraint identity, including type, bounds, hard/soft semantics, penalty and capacity origin. An ActivityPolicy retains both the normalized-margin tolerance and the sensitivity qualification policy. assess(...) reports dimensionless base margins, scaled margin derivatives, violated constraints, and conservative CANDIDATE_ACTIVE / INACTIVE diagnostics without rerunning the model. Missing, duplicate, unitless or stale scales fail closed. Candidate active means only that a feasible local margin is within the declared tolerance; it is not an optimizer active-set proof, ranking, KKT multiplier, economic shadow price or engineering approval.

Reversible operating actions

ProcessModelOperatingAction defines one simulator-bound action with an immutable stable ID, display name, area-qualified automation address, unit, provenance, and either inclusive continuous bounds or exact enumerated discrete values. inspectCapability(model) reports whether the target is readable. capture(model), apply(model, value), and restore(model, state) provide identity-checked candidate mutation with automation read-back verification and explicit rollback diagnostics, without running the model.

Use registerWith(ProcessModelSimulationEvaluator) for external optimization. Registration fails closed when the current model value is outside the candidate domain, while capture/restore remains available for that brownfield baseline. Continuous actions map directly to bounded evaluator parameters. Discrete actions expose their ordered allowed values through the returned binding; callers must enumerate them because intermediate vector values fail closed rather than being silently rounded or interpolated. Re-register actions after evaluator deserialization because all evaluator callbacks are transient.

This API preserves candidate semantics and restoration only. It does not alter routing topology, solve a mixed-integer problem, run the process, establish process feasibility, rank actions, or approve an operating change. After each candidate, explicitly run NeqSim and verify convergence, constraint residuals, conservation, product quality, equipment limits, and nearby behavior.

Transactional hydraulic action evaluation

ProcessModelOperatingActionEvaluator closes the steady-state validation loop for one action and explicitly selected reservoir, well, gathering, or pipeline capacity constraints. It establishes a converged baseline, captures the action state, applies and runs one candidate through ProcessModelSimulationEvaluator, snapshots every exact required hydraulic constraint, then restores and reruns the baseline before returning. CandidateEvaluationResult is immutable and serializable and retains action identity/provenance, raw and sign-adjusted objectives, registered constraint values and margins, hydraulic utilization and margin, equipment/design provenance, confidence, validity applicability, restoration evidence, and diagnostics.

The wrapped simulation evaluator must have no parameters because the transactional wrapper owns the one candidate write. Objectives and non-equipment constraints may still be registered before construction. The wrapper adds enabled equipment capacity constraints automatically. Require at least one exact area, equipment, and constraint binding; a misspelled or disabled constraint, non-finite value, limit violation, or sample outside an explicitly declared validity range fails closed with a distinct Outcome. An absent validity range is reported as NOT_ASSESSED and is not silently invented.

// Requires a solved producer/well model with an enabled "well drawdown" limit.
// export is a downstream stream; the current producer rate is inside the action bounds.
ProcessModelSimulationEvaluator simulation = new ProcessModelSimulationEvaluator(model);
simulation.addObjective("export gas", processModel -> export.getFlowRate("kg/hr"),
    ProcessModelSimulationEvaluator.ObjectiveDefinition.Direction.MAXIMIZE);

ProcessModelOperatingAction rate = ProcessModelOperatingAction
    .continuous("producer-rate", "Producer gas rate", "Subsurface::producer.flowRate",
        0.5, 1.5, "MSm3/day", "approved well operating envelope revision A")
    .withReadBackTolerance(1.0e-5, 0.0,
        "producer flow-control tag resolution in MSm3/day");

ProcessModelOperatingActionEvaluator hydraulic =
    new ProcessModelOperatingActionEvaluator(simulation, rate)
        .requireHydraulicConstraint(
            ProcessModelOperatingActionEvaluator.HydraulicLimitRole.WELL_INFLOW_OUTFLOW,
            "Subsurface", "well", "well drawdown", "installed maximum drawdown basis");

ProcessModelOperatingActionEvaluator.CandidateEvaluationResult candidate =
    hydraulic.evaluate(1.2);
if (!candidate.isBaselineRestored() || !candidate.isBaselineSimulationConverged()) {
  throw new IllegalStateException("The model baseline was not recovered");
}

The default action tolerance remains a strict scale-aware floating-point comparison. When a specific automation conversion or control tag has coarser resolution, declare its absolute and/or relative tolerance with withReadBackTolerance and retain the evidence source. Application diagnostics report the requested and read-back values, absolute residual, allowed tolerance, and tolerance provenance. A tolerated write does not prove process feasibility.

This API consumes existing hydraulic and equipment calculations; it does not add a correlation, change topology, prove conservation beyond the configured model, or approve an operating change. Use independent ProcessModel instances for parallel candidates. Add product, mechanical, safety, environmental, and market constraints to the wrapped evaluator when those limits are in scope.

Coupled well-allocation action transactions

Use ProcessModelOperatingActionSetEvaluator when one candidate changes two or more independent automation addresses, such as producer-rate allocation against per-well drawdown and a shared manifold capacity. The constructor retains a stable set ID, name, provenance, and declaration-ordered actions. Duplicate action IDs or addresses fail before mutation.

The evaluator establishes one converged baseline and captures every action before the first write. It applies the complete vector in order, refuses to simulate a partial vector after any rejected write, snapshots exact required hydraulic constraints, restores all actions in reverse order, and reruns the baseline. The serializable result exposes immutable per-action requested/read-back values, residuals, tolerances and provenance together with objective, constraint, hydraulic and restoration evidence for Java and JPype/Python.

// simulation wraps a connected two-well model with an explicit objective and installed limits.
// Both independent well actions and candidate rates use kg/hr for the allocation search below.
ProcessModelOperatingActionSetEvaluator allocation =
    new ProcessModelOperatingActionSetEvaluator(
        "field-allocation", "Field production allocation",
        "approved well operating envelopes and gathering basis revision A", simulation,
        Arrays.asList(wellAAction, wellBAction))
            .requireHydraulicConstraint(
                ProcessModelOperatingActionEvaluator.HydraulicLimitRole.WELL_INFLOW_OUTFLOW,
                "Subsurface", "well A", "well drawdown", "well A installed drawdown basis")
            .requireHydraulicConstraint(
                ProcessModelOperatingActionEvaluator.HydraulicLimitRole.WELL_INFLOW_OUTFLOW,
                "Subsurface", "well B", "well drawdown", "well B installed drawdown basis")
            .requireHydraulicConstraint(
                ProcessModelOperatingActionEvaluator.HydraulicLimitRole.GATHERING_HYDRAULICS,
                "Gathering", "inlet separator", "installed gathering rate",
                "shared installed gathering capacity");

ProcessModelOperatingActionSetEvaluator.CandidateSetEvaluationResult result =
    allocation.evaluate(new double[] {wellARate, wellBRate});
if (!result.isBaselineRestored() || !result.isBaselineSimulationConverged()) {
  throw new IllegalStateException(result.getDiagnostics().toString());
}

A feasible result proves only the configured steady-state simulation and exact constraints at that candidate. It does not choose an allocation, mutate routing, qualify dynamics, create new hydraulic physics, or approve an operating change. Check mass and energy balance, product specifications, mechanical/safety/environmental/market limits, nearby points and evidence validity. Use independent ProcessModel instances for parallel candidates.

Fixed-total allocation search with complete candidate evidence

ProcessModelAllocationOptimizer turns the atomic action-set evaluator into a bounded continuous allocation search. Every candidate preserves one declared total and moves rate only by pairwise transfer, so the sum remains constant while per-action bounds and all configured process and hydraulic constraints are enforced by NeqSim. The optimizer stops immediately if rollback or restored-baseline convergence fails.

// Continue with allocation above; initial rates sum to totalRate and lie inside their bounds.
ProcessModelAllocationOptimizer optimizer = new ProcessModelAllocationOptimizer(
    "field-rate-allocation", "Field rate allocation",
    "approved well envelopes and host capacity basis revision A", allocation,
    totalRate, "kg/hr")
        .setInitialAllocation(new double[] {initialWellARate, initialWellBRate})
        .setObjectiveIndex(0)
        .setInitialStepFraction(0.10)
        .setRelativeStepTolerance(1.0e-3)
        .setObjectiveImprovementTolerance(
            1.0e-6, "validated export-rate calculation resolution")
        .setMaximumEvaluations(100);

ProcessModelAllocationOptimizer.AllocationSearchResult search = optimizer.optimize();
if (!search.isModelRecovered()) {
  throw new IllegalStateException(search.getDiagnostics().toString());
}
ProcessModelAllocationOptimizer.CandidateRecord best = search.getBestFeasibleCandidate();
if (best != null) {
  double[] allocationRates = best.getCandidateValues();
  List<ProcessModelOperatingActionEvaluator.HydraulicConstraintSnapshot> limiting =
      search.getRankedHydraulicConstraintsAtBestFeasible();
}

The immutable serializable result freezes optimizer/action-set/objective identity, bounds, seed, budget, transfer and objective tolerances with provenance, every atomic candidate result, the best feasible candidate, and utilization-ranked hydraulic evidence. It also reports the gap to the best objective among sampled points. That sampled gap is a search diagnostic, not global production loss, economic value, or a shadow price.

This first allocation optimizer requires continuous actions with exactly matching units and a feasible fixed total. Convergence means that no improving feasible pair transfer was found above the declared step tolerance; it does not prove global optimality. Validate nearby allocations, conservation, product specifications, rotating-equipment maps, utilities, safety and market limits, and use independent model/evaluator/optimizer instances for parallel searches.

Use ProcessModelOperatingPointOptimizer when the same atomic action set contains independent continuous controls and exact discrete choices—for example well-choke openings together with HP/LP separator routing. The optimizer reuses ProcessModelOperatingActionSetEvaluator for every trial. It never interpolates a route selection, bypasses a hard constraint, or continues after incomplete baseline recovery.

The deterministic coordinate search explores both directions of each continuous action using a shrinking fraction of its declared range and every alternative declared value of each discrete action. It accepts the best feasible finite-objective move from each complete neighborhood. The hard evaluation budget includes a reserved final evaluation: the selected point must replay from the restored baseline with the same objective before it is exposed as accepted.

For a qualified continuous control that crosses a sharp map, driver, capacity, or other hard constraint, boundary refinement can be enabled explicitly. The optimizer bisects only between an eligible feasible point and a fully restored, converged rejection on that same continuous coordinate. Every midpoint consumes the normal evaluation budget and passes through the complete simulator, constraint snapshot, restoration, and replay contract; discrete routes are never interpolated. This is a sampled feasibility-boundary refinement, not permission to weaken a limit or accept an unconverged point.

ProcessModelOperatingPointOptimizer optimizer = new ProcessModelOperatingPointOptimizer(
    "field-operating-point", "Field operating point",
    "approved choke envelopes and qualified separator line-ups", actionSet)
        .setInitialCandidate(initialChokesAndRoutes)
        .setObjectiveIndex(0)
        .setInitialStepFraction(0.25)
        .setRelativeStepTolerance(1.0e-3)
        .setMaximumBoundaryRefinementEvaluations(8)
        .setMaximumEvaluations(100);

ProcessModelOperatingPointOptimizer.OperatingPointSearchResult search = optimizer.optimize();
if (!search.isAcceptedPointReplayed()) {
  throw new IllegalStateException(search.getDiagnostics().toString());
}
Map<String, Object> optimizerEvidence = search.getOptimizerEvidence(currentCycleId);
double[] acceptedChokesAndRoutes = search.getAcceptedCandidateValues();

getOptimizerEvidence(cycleId) emits the exact fail-closed fields used by the living-task production guard: current cycle identity, exact optimizer termination reason, candidate convergence/feasibility/finiteness, complete constraint and action evidence, complete state restoration, accepted-point replay, evaluation count, and measured runtime. The termination reason distinguishes tolerance convergence from an evaluation-budget stop; both still require the same feasible accepted-point replay, and budget exhaustion is never relabelled as convergence. When boundary refinement is enabled, it also records the configured per-direction maximum and the number of midpoint evaluations actually performed. A true evidence block qualifies only the configured steady-state model and limits. It is not a global-optimality claim, a dynamic/safety study, or permission to write to a control system.

Pass the completed search to the overloads on ProcessModelOperatingEnvelopeStudy to use the terminal replay as the exact one- or two-dimensional slice anchor. The study verifies the complete replay, exact runtime evaluator identity, action/objective/constraint/hydraulic definition identity, unchanged installed-limit metadata, current model convergence, and unchanged baseline action values before running another point. getAcceptedPointBottleneck(search) returns the already-recorded unit-safe leading constraint without rerunning the model. Definition or baseline drift fails before mutation; callers must rerun the search instead of treating an old candidate as current. The reported row and the envelope remain sampled evidence, not causal attribution, a shadow price, a continuous feasible boundary, or operating approval.

For a persistent engineering cycle, call getAcceptedPointEnvelopeEvidence(cycleId, search, slices...) after all requested slices complete. The compact immutable map combines the existing optimizer guard block, the accepted-point bottleneck, every sampled point, bottleneck transition, explicit operating-region transition, optional fail-closed continuous-boundary receipt, and per-slice plus aggregate evaluation counts and wall-clock runtimes. Accepted-envelope schema v2 also includes cross_slice_constraint_summary. Summary version 2 traverses every exact required-hydraulic, installed-equipment, and typed plant constraint already recorded at sampled points and final refined endpoints, so a secondary line, separator, compressor-map, common-shaft, or shared-power limit can remain visible even when it is not the single leading bottleneck. It groups rows only when source, qualified constraint name, applicable limit, unit, limit role, and limit provenance all match. Rows with any rejection rank first by occurrence; otherwise feasible rows enter only when their finite dimensionless normalized margin is at or below the configured threshold (default 0.10), whose provenance is exported. Physical margins remain visible evidence but are never ranked across unlike units. The summary reuses evaluator evidence and performs no additional process simulations; summarizeConstraints(...) exposes the same immutable read-only result. Summary version 1 remains a supported persisted-input contract for leading-only historical evidence. This is a deterministic screening report, not an optimizer active set, shadow price, causal attribution, or approval. Every slice must come from the exact runtime study, share the accepted candidate anchor, and have complete restoration/reconvergence. A foreign, deserialized, duplicate-axis, partial-recovery, or stale slice is rejected before export. Non-finite optional numerical fields are emitted as JSON null; they are never promoted to accepted evidence. Runtime is provenance for #2939 coordination, not a generic performance claim.

getOperatingRegionTransitions() is the diagnostic handoff for map, injectivity, driver, capacity, and routing event studies. BECAME_FEASIBLE and BECAME_INFEASIBLE identify sampled feasibility changes; EVALUATION_OUTCOME_CHANGED preserves changes between exact fail-closed rejection classes; and LEADING_CONSTRAINT_CHANGED records a changed evidence identity or layer without comparing unlike physical units. Opt in with setMaximumBoundaryRefinementEvaluations(...) and record a positive range-relative tolerance plus provenance to bisect only BECAME_FEASIBLE or BECAME_INFEASIBLE edges on continuous actions. Every midpoint uses the unchanged complete transactional evaluator; only finite, converged, fully restored feasible or explicit hard/validity-rejected points may tighten the bracket. The receipt retains every midpoint and both final independently evaluated sides. It is not interpolation, an accepted set point, or operating approval. Each final side also retains its exact unit-safe leading or rejecting constraint, including the signed physical margin and engineering unit. This makes the reported headroom traceable without recomputing or substituting a different constraint after the refinement. A discrete edge is two evaluated line-ups only and is never refined into a route fraction or topology.

Use ProcessModelOperatingEnvelopeLifecycleStudy when the accepted envelope must be compared across ordered field periods. Build each period from its own converged model/evaluator definition, search, and accepted-point slices, then add it with a non-negative strictly increasing elapsed time and explicit configuration provenance. The lifecycle study invokes the same exact-runtime envelope export gate before accepting a period, requires identical ordered action identifiers across periods, aggregates evaluation/runtime provenance, and reports changes in the accepted leading constraint between adjacent periods. Schema version 8 also exports period_change_diagnostics: accepted-action deltas, comparable/added/removed slice axes, exact sampled-point overlap, feasible-point changes, and leading-constraint changes for every adjacent period pair. For matching axes it compares the complete sampled operating_region_transitions rows and reports exact retained, removed, and added events. Points and events are compared only when their physical evidence is exactly equal; changed grids are reported explicitly and are never interpolated. Schema v8 also compares complete operating_region_boundary_refinements receipts exactly, including tolerance provenance, every midpoint, terminal bracket, endpoint constraint/headroom evidence, and status. It additionally reports exact retained, removed, and added ranked constraint identities across adjacent periods. Identity includes REJECTING or NEAR_ACTIVE classification, source, qualified name, applicable limit, unit, limit role, and provenance. Rank, counts, headroom, and representative rows are deliberately excluded from identity; a classification or engineering-basis change is reported as one removed and one added identity rather than being hidden as a rank change.

When a period is driven by an independently accepted reservoir/network state, use the addPeriod overload that accepts ordered AcceptedStateMetric values. Each metric carries a stable ID, name, finite value, explicit unit, and source provenance. Metric IDs, order, and units must remain identical across periods. Schema versions 4 through 8 retain the exact accepted state under each period when supplied and adds exact accepted_state_changes to the adjacent period diagnostics. This is the handoff for accepted reservoir pressure, production/injection rates, cumulative throughput, common-shaft power, or other already-qualified state; it does not make an independently calculated or forecast value accepted. State metrics remain optional in schema v8/v9 and remain readable in schemas v4 through v7. It does not copy reservoir, separator, compressor-map, shaft, driver, or power-limit physics; those remain in each period’s canonical model and constraint evidence.

ProcessModelOperatingEnvelopeLifecycleStudy lifecycle =
    new ProcessModelOperatingEnvelopeLifecycleStudy(
        "field-life", "Field lifecycle envelope", "approved forecast cases", "year");
lifecycle.addPeriod(
    "year-0", "Early life", 0.0, "forecast case E0",
    earlyStudy, earlySearch, earlyChokeSlice, earlyRoutingSlice, earlyPowerSlice);
lifecycle.addPeriod(
    currentCycleId, "Late life", 5.0, "forecast case L5",
    lateStudy, lateSearch, lateChokeSlice, lateRoutingSlice, latePowerSlice);
String lifecycleEvidenceJson = lifecycle.toEvidenceJson(currentCycleId);
from neqsim_continuous.production import proposal_from_lifecycle_context


def run(ctx):
    lifecycle_json = build_lifecycle_study(ctx).toEvidenceJson(ctx.cycle_id)
    proposal = proposal_from_lifecycle_context(
        lifecycle_json,
        ctx=ctx,
        title="Increase accepted field export",
        predicted=predicted_kpis,
    )
    return {"proposals": [proposal]}

Periods are independent by design: never mutate a completed period’s model and reuse its accepted search as evidence for a later period. A missing, stale, unconverged, non-finite, partially restored, foreign, duplicate-axis, or action-incompatible period is rejected instead of producing a partial lifecycle report. Bottleneck transitions are sampled identities, not a continuous forecast, causal attribution, shadow price, equipment qualification, or operating approval.

Lifecycle schema versions 1 through 10 remain readable by the persistent production guard for stored historical evidence. A lifecycle with installed-equipment coverage exports schema version 9. When the declared coverage also includes registry-qualified plant constraints it exports schema version 11; without coverage it remains schema version 8 for compatibility. Schema v10 remains readable, while schema v11 adds a deterministic constraint follow-up opportunity screen. The screen classifies exact current rejecting constraints and next-bottleneck candidates from the complete sampled/refined constraint ladder, preserving physical headroom, applicable limit, unit, role, provenance, axes, and evidence counts. It is not a causal attribution, shadow price, predicted benefit, equipment qualification, permission, or approval; a paired relief study is required before making a debottleneck-benefit claim. Schema v12 is emitted only when at least one installed-equipment screen row carries a qualified ProcessModelDebottleneckStudy receipt. Transactional studies use OperatingActionCandidateListSearch with the exact envelope action evaluator. The receipt binds the paired baseline action vector and selected-objective identity to the accepted replay, reruns the same ordered search under the proposed direct capacity limit, and is withheld unless both scenarios are converged, feasible and finite and both the action state and complete installed-limit state are restored. The objective binding retains the accepted replay value, paired sample, residual, allowed replay/search tolerance and tolerance provenance; it never hides numerical repeatability within a declared tolerance.

Schema v13 is emitted when one period carries a complete ranked portfolio of at least two such exact receipts. Create every direct installed-capacity study against the same transactional evaluator, accepted action vector, ordered candidate search, and selected objective; then call ProcessModelOperatingEnvelopeStudy.qualifyReliefPortfolio(...). The method qualifies every receipt first and uses ProcessModelDebottleneckRanking.rankObjectives(...) to compare the immutable transactional objective evidence. Partial portfolios fail closed: changed objective metadata, baseline actions, baseline objectives or constraints, failed restoration, non-finite evidence, or any unrankable candidate prevents the portfolio receipt. Competition ranks are deterministic, retain input order inside the declared tie tolerance, and remain advisory sampled deltas. They do not imply additive benefits, interaction effects, causality, a shadow price, global optimality, investment approval, permission to alter an installed limit, or operating approval.

Schema v14 is emitted when a schema-v13 period also carries one qualified joint evaluation of two or more portfolio members. Construct one list-based ProcessModelDebottleneckStudy with those exact alternatives and the same transactional evaluator, accepted action vector, ordered candidate search and objective policy, then call ProcessModelOperatingEnvelopeStudy.qualifyReliefCombination(...). The study applies all direct limits in declared order, evaluates the combined process once, restores every limit in reverse order and reconverges the original state. The receipt records the actual joint objective delta, the arithmetic sum of the already-qualified independent deltas, their signed interaction difference, and exact baseline-to-joint installed-constraint bottleneck migration. The guard and report recompute all arithmetic and membership bindings. They never infer the joint response from additive assumptions, and reject non-finite, stale, foreign, partially restored or incomplete evidence.

Schema v15 extends the same exact joint-combination binding to paired operating-envelope slices. First evaluate one or more complete baseline slices from the accepted replay. Then call ProcessModelDebottleneckStudy.evaluateCombinedEnvelope(...) with those exact runtime slices; the study applies every combination member together, repeats the identical axes, grids, anchors and refinement policy, stops on any unsafe point recovery, restores every installed limit in reverse order and reconverges the restored process. Finally call ProcessModelOperatingEnvelopeStudy.qualifyReliefEnvelope(...) with the same accepted search, portfolio, combination study and accepted-point result. Qualification rejects foreign, deserialized, partial, non-finite, stale or structurally mismatched slices. The receipt retains both complete slice blocks, exact sampled feasibility/outcome and leading-constraint identity changes, and signed shifts of terminal feasible/infeasible refinement-bracket endpoints only when exactly one baseline and one relieved continuous boundary are comparable on an axis. A bracket endpoint is not promoted to an exact boundary, discrete routes are never interpolated, and no independent relief delta is treated as additive.

Schema v16 adds exact sampled-cell topology when an accepted envelope contains a two-dimensional slice. Each cell is identified by its lower grid indexes and four clockwise corner sequence indexes, classified only from independently evaluated corner feasibility, and linked to the exact sampled perimeter edges where feasibility changes. The paired combined-relief receipt compares those complete baseline and relieved cell records. It does not infer feasible area, an interior contour, or a boundary between discrete pressure-level routes; any continuous refinement remains a separate fail-closed bracket.

Schema v11 retains the schema-v10 requirement for non-empty, independently declared plant-constraint IDs in addition to the schema-v9 equipment scope, complete finite rated evidence, explicit unit/basis/provenance/direction/category, and exact calculation binding to the accepted-point replay. Supply both getInstalledEquipmentCapacityEvidence() and getPlantConstraintEvidence() through the frozen-evidence builder path; this never resamples the mutable process after baseline restoration. Current schema-v11/v12/v13/v14/v15/v16/v17 exports retain optional accepted-state metrics and require schema-v2 cross-slice summaries in every period. The guard reconstructs each summary from persisted sampled and refined endpoint rows before trusting it, independently reconstructs the constraint opportunity screen, validates any v12/v13 receipt against the exact installed identity, limit, unit, action set, accepted action vector, objective identity, accepted value, paired residual/tolerance, finite delta and restoration flags, reconstructs each schema-v13 portfolio identity, candidate count, input indices, alternative identities, common objective policy and competition rank from the persisted deltas, reconstructs each schema-v14 joint membership, objective arithmetic, restoration state and bottleneck migration without assuming additivity, reconstructs every schema-v15 paired slice grid, point identity change, refinement count and bracket-endpoint shift from the persisted baseline and jointly relieved slices, reconstructs every schema-v16 sampled cell and perimeter boundary family from the persisted 2D grid, and then requires every schema-v17 period to retain an accepted optimizer termination reason from the same replayed search, and then recomputes every period-change diagnostic from the persisted periods before accepting it, including exact operating-region event migration, refined-boundary endpoint constraints and physical margins, exact ranked constraint-identity migration, and, when present, state-metric identity, units, finite values, and exact deltas. A missing or modified count, event, delta, axis set, feasibility change, state metric, coverage row, or ranked identity therefore fails closed instead of becoming a trusted advisory result.

Long-lived Task Solver studies can persist this map as lifecycle_envelope_evidence beside the current accepted_point_envelope_evidence. Enable both production.require_envelope_evidence and production.require_lifecycle_envelope_evidence; assign the current lifecycle period the task cycle ID and reuse that period’s exact accepted-point envelope as the current proposal evidence. The production guard revalidates all periods and aggregate/transition closure from persisted JSON, then invalidates and supersedes the lifecycle advice when mapped reservoir state, availability, routing, operating limits, or other evidence changes. This reuses the existing #4187 ledger and revalidation path rather than introducing another lifecycle state store.

Promotion automatically copies one exact schema-v8/v9 block from the reviewed cycle’s current guard-approved ledger proposal into both the current-best results.json and its immutable baseline snapshot. It independently replays the production validation and refuses stale, superseded, rejected, modified, unguarded, or ambiguous evidence before changing the baseline. When lifecycle evidence is required, a cycle without that receipt cannot be promoted.

The supported Java-to-living-task boundary is the compact JSON returned by ProcessModelOperatingEnvelopeLifecycleStudy.toEvidenceJson(currentCycleId). It fails before serialization unless one independently validated lifecycle period has that exact cycle ID. A task-local scripts.optimize stage passes the JSON and cycle context to neqsim_continuous.production.proposal_from_lifecycle_context(...). The adapter rejects stale, duplicate, non-finite, incomplete, or edited evidence, derives exact setpoints and the accepted envelope, and reuses the same production validators that guard and promotion use. It also binds the Java search’s selected-objective name, direction, unit, initial value, and accepted value to the task-local goal.yaml objective and reviewed baseline KPI. The task must declare objective.metric, objective.source_name, objective.direction, and objective.unit. Historical evidence without replay metadata still requires exact baseline equality. Current Java evidence may bind the initial objective within its explicit replay_relative_tolerance and replay_tolerance_provenance; the production guard rejects missing provenance, non-finite/negative values, or a relative tolerance above $10^{-6}$. This uses the same simulator-replay qualification as terminal optimizer acceptance and does not permit a physical-constraint or operating-limit tolerance. The adapter derives the directional gain and objective prediction instead of trusting task-script constants. Set production.require_objective_evidence: true with the existing envelope requirements so the guard repeats this binding before publication. The task may supply additional finite constraint predictions but must not rebuild Java evidence dictionaries. The separate report.prepare hook refreshes report inputs after accepted results exist and is not a proposal-ingestion path. The lower-level proposal_from_lifecycle_json(...) remains available for compatibility when callers already own separately qualified proposal metadata.

The engineer-runnable source example is devtools/field_to_facility_living_example.py. It calls the exact ten-well Java fixture from target/test-classes, assigns generated living-task cycle IDs to the current lifecycle periods, and then exercises the adapter, production guard, append-only ledger, reviewed promotion, selective limit revalidation, living report, and canonical HTML report. Run it after compiling tests and generating the Maven dependency classpath:

./mvnw -B -ntp -DskipTests test-compile dependency:build-classpath \
  -Dmdep.outputFile=target/optimization-classpath.txt
NEQSIM_TEST_CLASSPATH="target/classes:target/test-classes:$(cat target/optimization-classpath.txt)" \
  python devtools/field_to_facility_living_example.py \
  --output-parent target/field-to-facility-example

The command refuses an existing task directory, runs the initial 1.01 shared-power-limit case, promotes only its guard-approved exact replay, changes the declared multiplier to 1.02, selectively recomputes the affected evidence, promotes the replacement cycle, and prints a compact JSON summary. The retained task folder contains the append-only ledger, both cycle receipts, continuous/LIVING_REPORT.md, the canonical HTML report, and field_to_facility_acceptance_summary.json. Use --initial-shared-power-multiplier and --revised-shared-power-multiplier only for finite values greater than 1.0. The complete regression remains devtools.test_field_to_facility_living_workflow.FieldToFacilityLivingWorkflowTest; it exercises the same reusable Python orchestration helpers and adds tamper-rejection assertions.

The workflow asserts finite evaluation counts and runtime, full convergence/feasibility/restoration receipts, exact selected-objective identity and reviewed-baseline binding, schema-v11 persistence and constraint-opportunity reconstruction, complete declared installed and plant-wide constraint coverage with operating-limit provenance, common-shaft/compressor-map/driver/shared-power evidence, the separator/scrubber-to-shared-power bottleneck migration, and report rendering, including the cycle-by-cycle map-to-driver ledger timeline and the promoted canonical migration section. It is a synthetic software acceptance test, not a reservoir forecast, equipment qualification, operating approval, or an instruction to relax any physical constraint.

The same acceptance now performs a second living-task cycle after changing the task-local shared-power operating limit in continuous/user_input.yaml. An explicit evidence-impact rule selects the Java optimization, KPI, constraint, guard, ledger, and report-digest stages while retaining an unrelated completed stage and its KPI. Before the new optimization is published, the evidence stage marks the previous optimizer receipt stale and superseded. The replacement proposal must carry the new cycle ID, the unchanged reviewed objective baseline, fresh finite Java evidence, and a revalidation_of ledger link to the invalidated proposal. Unmapped input changes still fail closed to a full cycle. Selective execution is therefore an execution/provenance claim only: it does not reuse an accepted candidate, constraint snapshot, or partially restored model state.

The canonical task report consumes accepted top-level schema-v8/v9 lifecycle_envelope_evidence in both ordinary current-best and immutable final-report generation. It renders the exact per-period REJECTING and NEAR_ACTIVE rows, representative values, signed physical headroom with engineering units, and adjacent-period identity migration. Schema v9 additionally renders every declared installed-capacity row with current value, applicable limit, utilization, unit, basis, and provenance. Before rendering, it requires complete ordered period evidence, finite counts/runtime, complete slices, matching representative identities, exact reconstructed identity deltas, and—when a promoted living-task baseline exists—the matching source cycle. A mismatch produces an explicit evidence-withheld section rather than a partial bottleneck claim. This report view is downstream of, and does not replace, the production guard. Rankings remain period-local; unlike-unit margins are not compared, and the output is not causal attribution, a shadow price, equipment qualification, operating approval, or authority to weaken a physical constraint.

Trace-qualified bottleneck-relief evidence

ProcessModelAllocationBottleneckAnalyzer reads a completed ProcessModelAllocationOptimizer.AllocationSearchResult; it never runs the simulator or writes an operating action. It keeps only fully recovered candidates whose selected raw objective improves on the best feasible sample by more than the declared objective tolerance and which contain at least one exact finite hard-constraint violation.

// Continue with the completed allocation search above.
ProcessModelAllocationBottleneckAnalyzer analyzer =
    new ProcessModelAllocationBottleneckAnalyzer(
        "field-allocation-relief", "Field allocation bottleneck relief",
        "approved allocation study revision A");
ProcessModelAllocationBottleneckAnalyzer.BottleneckAnalysisResult relief =
    analyzer.analyze(search);

for (ProcessModelAllocationBottleneckAnalyzer.BottleneckReliefOpportunity opportunity :
    relief.getOpportunities()) {
  for (ProcessModelAllocationBottleneckAnalyzer.ConstraintReliefEvidence constraint :
      opportunity.getConstraintRelief()) {
    double requiredRelief = constraint.getRequiredMarginRelief();
    String reliefUnit = constraint.getUnit();
  }
}

Candidate results freeze registered objective and constraint definitions beside their sampled values and margins and retain the complete getInstalledEquipmentCapacityEvidence() list, so serialization or later evaluator/equipment mutation cannot relabel the trace. Installed-capacity relief comes from the same immutable current/applicable-limit row that supplied feasibility; it no longer depends on joining to the smaller required-hydraulic-binding list. General constraints use the magnitude of their negative margin. Missing, invalid, or out-of-validity evidence is reported as evidence-limited, soft constraints are excluded, and unlike relief units are never aggregated or ranked.

The analyzer reports a sampled association, action deltas from the best feasible allocation, and an isolated/coupled/evidence-limited classification. It does not establish causality, capacity-sizing sufficiency, global optimality, a shadow price, production loss, economic value, or operating approval.

ProcessOptimizationEngine Algorithms

engine.setSearchAlgorithm(ProcessOptimizationEngine.SearchAlgorithm.GOLDEN_SECTION);
engine.setSearchAlgorithm(ProcessOptimizationEngine.SearchAlgorithm.BFGS);
engine.setSearchAlgorithm(ProcessOptimizationEngine.SearchAlgorithm.GRADIENT_DESCENT);

ProductionOptimizer Algorithms

config.searchMode(SearchMode.BINARY_FEASIBILITY);
config.searchMode(SearchMode.GOLDEN_SECTION_SCORE);
config.searchMode(SearchMode.NELDER_MEAD_SCORE);
config.searchMode(SearchMode.PARTICLE_SWARM_SCORE);
config.searchMode(SearchMode.GRADIENT_DESCENT_SCORE);  // New (Jan 2026)

January 2026 Update: ProductionOptimizer now includes GRADIENT_DESCENT_SCORE algorithm, configuration validation, stagnation detection, warm start, bounded LRU cache, and infeasibility diagnostics. See Production Optimization Guide for details.


Python usage through neqsim-python

Install neqsim==3.20.0 in a fresh Python runtime. The second block continues from the first:

ProcessOptimizationEngine from Python

from neqsim import jneqsim

SystemSrkEos = jneqsim.thermo.system.SystemSrkEos
Stream = jneqsim.process.equipment.stream.Stream
Compressor = jneqsim.process.equipment.compressor.Compressor
ProcessSystem = jneqsim.process.processmodel.ProcessSystem
ProcessOptimizationEngine = jneqsim.process.util.optimizer.ProcessOptimizationEngine

gas = SystemSrkEos(303.15, 50.0)
gas.addComponent("methane", 0.90)
gas.addComponent("ethane", 0.10)
gas.setMixingRule("classic")
feed = Stream("feed", gas)
feed.setFlowRate(50000.0, "kg/hr")
compressor = Compressor("comp", feed)
compressor.setOutletPressure(150.0, "bara")
compressor.setUsePolytropicCalc(True)
compressor.setPolytropicEfficiency(0.78)
compressor.getMechanicalDesign().setMaxDesignPower(4000.0)
power_limit = compressor.getCapacityConstraints().get("power")
power_limit.setMaxValue(100.0)
compressor.clearCapacityConstraints()
compressor.addCapacityConstraint(power_limit)
outlet = Stream("outlet", compressor.getOutletStream())
process = ProcessSystem()
for equipment in [feed, compressor, outlet]:
    process.add(equipment)
process.run()

engine = ProcessOptimizationEngine(process)
engine.setFeedStreamName("feed")
engine.setOutletStreamName("outlet")
engine.setSearchAlgorithm(ProcessOptimizationEngine.SearchAlgorithm.BINARY_SEARCH)
engine.setTolerance(1.0)
result = engine.findMaximumThroughput(50.0, 150.0, 10000.0, 200000.0)
assert result.isConverged(), str(result.getErrorMessage())
feed.setFlowRate(result.getOptimalValue(), "kg/hr")
process.run()
assert compressor.getPower("kW") <= 4000.0
assert abs(feed.getFlowRate("kg/hr") - outlet.getFlowRate("kg/hr")) < 1e-6
print(f"Maximum flow: {result.getOptimalValue():.1f} kg/hr")
print(f"Power: {compressor.getPower('kW'):.2f} kW; bottleneck: {result.getBottleneck()}")

ProductionOptimizer from Python

# Continue from the process constructed in the preceding block.
from jpype import JClass, JImplements, JOverride

ProductionOptimizer = jneqsim.process.util.optimizer.ProductionOptimizer
OptimizationConfig = ProductionOptimizer.OptimizationConfig
OptimizationObjective = ProductionOptimizer.OptimizationObjective
SearchMode = ProductionOptimizer.SearchMode
ObjectiveType = ProductionOptimizer.ObjectiveType

@JImplements("java.util.function.ToDoubleFunction")
class ThroughputObjective:
    @JOverride
    def applyAsDouble(self, proc):
        return proc.getUnit("outlet").getFlowRate("kg/hr")

config = (OptimizationConfig(10000.0, 200000.0)
          .rateUnit("kg/hr")
          .tolerance(1.0)
          .maxIterations(60)
          .defaultUtilizationLimit(1.0)
          .utilizationMarginFraction(0.05)
          .searchMode(SearchMode.GOLDEN_SECTION_SCORE))
objectives = JClass("java.util.ArrayList")()
objectives.add(OptimizationObjective("throughput", ThroughputObjective(), 1.0, ObjectiveType.MAXIMIZE))
result = ProductionOptimizer().optimize(process, feed, config, objectives, None)
assert result.isFeasible()
feed.setFlowRate(result.getOptimalRate(), "kg/hr")
process.run()
assert 0.0 < compressor.getPower("kW") <= 0.95 * 4000.0
assert abs(feed.getFlowRate("kg/hr") - outlet.getFlowRate("kg/hr")) < 1e-6
print(f"Optimal rate: {result.getOptimalRate():.1f} kg/hr")

Complete Examples

Example 1: Find Maximum Compressor Throughput

import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.util.optimizer.ProcessOptimizationEngine;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class CompressorThroughputOverview {
    private static final Logger logger = LogManager.getLogger(CompressorThroughputOverview.class);

    public static void main(String[] args) {
        ProcessSystem process = OptimizationGuideSetup.createProcess();
        ProcessOptimizationEngine.OptimizationResult result = OptimizationGuideSetup.optimize(process);
        logger.info("Maximum throughput: {} kg/hr; limited by {}; power: {} kW", result.getOptimalValue(),
            result.getBottleneck(), ((Compressor) process.getUnit("comp")).getPower("kW"));
    }
}

Example 2: Multi-Objective Pareto Optimization

import java.util.Arrays;
import java.util.List;
import neqsim.process.util.optimizer.ProductionOptimizer;
import neqsim.process.util.optimizer.ProductionOptimizer.*;

ProductionOptimizer optimizer = new ProductionOptimizer();
List<OptimizationObjective> objectives = Arrays.asList(
    new OptimizationObjective("throughput",
        proc -> ((Stream) proc.getUnit("outlet")).getFlowRate("kg/hr"),
        1.0, ObjectiveType.MAXIMIZE),
    new OptimizationObjective("power",
        proc -> ((Compressor) proc.getUnit("comp")).getPower("kW"),
        1.0, ObjectiveType.MINIMIZE));
OptimizationConfig config = new OptimizationConfig(10000.0, 200000.0)
    .paretoGridSize(20).tolerance(1.0).maxIterations(60);
ParetoResult pareto = optimizer.optimizePareto(process, feed, config, objectives, null);
logger.info("Pareto front has {} solutions", pareto.getParetoFront().size());
for (ParetoPoint point : pareto.getParetoFront()) {
    logger.info("Flow {} kg/hr; power {} kW", point.getObjectiveValues().get("throughput"),
        point.getObjectiveValues().get("power"));
}

YAML Specification Files

The ProductionOptimizationSpecLoader class allows loading optimization scenarios from YAML or JSON files, enabling configuration-driven optimization workflows.

YAML Format

scenarios:
  - name: "MaxThroughput"
    process: "myProcess"           # Key in processes map
    feedStream: "wellFeed"         # Key in feeds map
    lowerBound: 50000.0
    upperBound: 200000.0
    rateUnit: "kg/hr"
    tolerance: 100.0
    maxIterations: 30
    searchMode: "GOLDEN_SECTION_SCORE"
    utilizationMarginFraction: 0.05

    objectives:
      - name: "throughput"
        weight: 1.0
        type: "MAXIMIZE"
        metric: "throughputMetric"   # Key in metrics map

    constraints:
      - name: "maxPower"
        metric: "powerMetric"
        limit: 5000.0
        direction: "LESS_THAN"
        severity: "HARD"
        description: "Compressor power limit"

Loading YAML Specs in Java

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.function.ToDoubleFunction;
import java.nio.file.Paths;
import neqsim.process.equipment.stream.StreamInterface;
import neqsim.process.util.optimizer.ProductionOptimizationSpecLoader;
import neqsim.process.util.optimizer.ProductionOptimizer;
import neqsim.process.util.optimizer.ProductionOptimizer.*;

Map<String, ProcessSystem> processes = new HashMap<>();
processes.put("myProcess", process);
Map<String, StreamInterface> feeds = new HashMap<>();
feeds.put("wellFeed", feed);
Map<String, ToDoubleFunction<ProcessSystem>> metrics = new HashMap<>();
metrics.put("throughputMetric", p -> ((Stream) p.getUnit("outlet")).getFlowRate("kg/hr"));
metrics.put("powerMetric", p -> ((Compressor) p.getUnit("comp")).getPower("kW"));
// Save the YAML block above to optimization.yaml before running this section.
List<ScenarioRequest> scenarios = ProductionOptimizationSpecLoader.load(
    Paths.get("optimization.yaml"), processes, feeds, metrics);
ProductionOptimizer optimizer = new ProductionOptimizer();
for (ScenarioResult scenario : optimizer.optimizeScenarios(scenarios)) {
    logger.info("{}: {} kg/hr; feasible {}", scenario.getName(), scenario.getResult().getOptimalRate(),
        scenario.getResult().isFeasible());
}

Class Summary

Class Purpose Key Method Documentation
ProcessOptimizationEngine Throughput-focused optimization findMaximumThroughput() Plugin Architecture
ProductionOptimizer General-purpose optimization optimize(), optimizePareto() Production Guide
FlowRateOptimizer Flow rate for pressure boundaries findMaxFlowRate() Flow Rate Optimization
MultiObjectiveOptimizer Pareto front generation optimize() Multi-Objective
BatchStudy Parallel parameter sweeps run() Batch Studies
ProcessConstraintEvaluator Constraint evaluation evaluate() Capacity Framework
ProcessSimulationEvaluator External optimizer interface evaluate() External Integration
ProcessModelSimulationEvaluator External optimizer interface for multi-area ProcessModel studies evaluate() External Integration
ProcessModelOperatingActionSetEvaluator Atomic coupled-action candidate evaluation evaluate(double[]) External Integration
ProcessModelAllocationOptimizer Fixed-total continuous allocation search with complete candidate evidence optimize() External Integration
ProcessModelOperatingPointOptimizer Bounded mixed continuous/discrete search with accepted-point replay optimize() External Integration
ProcessModelThroughputOptimizer Full-model throughput-to-bottleneck study helper findMaximumThroughput() External Integration
InstalledCapacityTableLoader Attach fixed equipment limits from CSV load() Capacity Framework
EclipseVFPExporter Eclipse VFP tables exportVFPPROD() Plugin Architecture
LiftCurveGenerator Process pressure requirements; well qualification is separate generateTable() Flow Rate Optimization
BatchParameterEstimator Model calibration solve() Data Reconciliation and Steady-State Detection
ProductionOptimizationSpecLoader YAML/JSON config loading load() YAML Format

Decision Guide

Choose based on your use case: