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
- Architecture Overview
- The Two Main Optimizers
- Full ProcessModel Optimization
- When to Use Which Optimizer
- Key Concepts
- Search Algorithms
- Python usage through neqsim-python
- Getting Started
- Complete Examples
- Related Documentation
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:
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:
- Maximum throughput calculations
- Pressure-constrained optimization
- Lift curve generation
- Equipment bottleneck analysis
- Integration with ProcessSystem/ProcessModule
Key Features:
- Works directly with
ProcessSystemorProcessModule - Uses equipment capacity strategy plugins
- Supports multiple search algorithms (Binary, Golden-Section, BFGS)
- Auto-detects feed and outlet streams
- Generates sensitivity analysis
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:
- Custom objective functions (not just throughput)
- Multi-variable optimization
- Multi-objective Pareto optimization
- User-defined constraints
- Scenario evaluation and parallelization
Key Features:
- Arbitrary objective functions via lambdas/interfaces
- Multiple decision variables (
ManipulatedVariable) - Multiple search algorithms (Binary, Golden-Section, Nelder-Mead, PSO)
- Pareto multi-objective optimization
- Parallel scenario evaluation
- Works with any
ProcessSystem
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:
- Build each process area as a separate
ProcessSystemand compose them in aProcessModel. - Add installed capacity constraints to equipment whose sizes are fixed.
- Register producer feed rates or scenario multipliers as throughput controls.
- Register the objective, for example export gas flow, oil export rate, or total sales flow.
- Run
findMaximumThroughput(...)to bracket the first infeasible case and binary-search the maximum feasible multiplier. - Export the case table with
ProcessModelThroughputResult.exportToCSV(...)or serialize it withtoJson(). - 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}}\]0.0= not used1.0= at design limit> 1.0= exceeds limit (constraint violation)
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.
Mixed continuous/discrete operating-point search
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_SCOREalgorithm, 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());
}
Related Documentation
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:
- Max throughput at pressures →
ProcessOptimizationEngine - Custom objectives/multi-variable →
ProductionOptimizer - Full
ProcessModelwith several process areas and producer ramping →ProcessModelThroughputOptimizer - Full
ProcessModelcustom external optimization →ProcessModelSimulationEvaluator - Coupled well-rate candidate with mandatory rollback →
ProcessModelOperatingActionSetEvaluator - Fixed-total continuous allocation across coupled actions →
ProcessModelAllocationOptimizer - Mixed choke/setpoint and discrete line-up search →
ProcessModelOperatingPointOptimizer - Model calibration →
BatchParameterEstimator