ProcessSystem Class
Documentation for the ProcessSystem class in NeqSim.
Table of Contents
- Overview
- Creating a Process
- Adding Equipment
- Stream Introspection
- Explicit Connections
- Named Controllers
- Unified Element Model
- Running Simulations
- Results and Reporting
- Advanced Features
- Validation
- Examples
Overview
Location: neqsim.process.processmodel.ProcessSystem
The ProcessSystem class is the main container for building and running process flowsheets. It:
- Manages equipment registration
- Enforces unique naming
- Handles execution order
- Coordinates recycles and adjusters
- Provides reporting capabilities
Creating a Process
Basic Constructor
import neqsim.process.processmodel.ProcessSystem;
// Create empty process system
ProcessSystem process = new ProcessSystem();
// Create with name
ProcessSystem process = new ProcessSystem("Gas Processing Plant");
Adding Equipment
Basic Addition
// Add equipment in sequence
process.add(feedStream);
process.add(heater);
process.add(separator);
process.add(compressor);
Equipment Order
Equipment is typically added in flow order, but the ProcessSystem handles dependencies automatically:
// ProcessSystem resolves dependencies
process.add(stream); // First
process.add(heater); // Uses stream as input
process.add(separator); // Uses heater output
process.add(compressor); // Uses separator gas output
Unique Names
All equipment must have unique names:
Stream stream1 = new Stream("Feed", fluid1);
Stream stream2 = new Stream("Feed", fluid2); // ERROR: Duplicate name!
// Use unique names
Stream stream1 = new Stream("Feed-1", fluid1);
Stream stream2 = new Stream("Feed-2", fluid2);
Stream Introspection
Every equipment class exposes its inlet and outlet streams through a uniform API. This allows tools, graph builders, and DEXPI exporters to discover the process topology without casting to specific equipment types.
Querying Streams
// Works on any ProcessEquipmentInterface — no casting needed
List<StreamInterface> inlets = equipment.getInletStreams();
List<StreamInterface> outlets = equipment.getOutletStreams();
System.out.println(equipment.getName() + " has "
+ inlets.size() + " inlet(s) and "
+ outlets.size() + " outlet(s)");
Per-Equipment Behavior
| Equipment | Inlets | Outlets |
|---|---|---|
Stream (feed) |
0 | 0 (feed streams are boundary conditions) |
TwoPortEquipment (Heater, Compressor, Valve, Pipe, …) |
1 | 1 |
Separator |
N (via internal mixer) | 2 (gas, liquid) |
ThreePhaseSeparator |
N (via internal mixer) | 3 (gas, oil, water) |
Mixer |
N | 1 |
Splitter |
1 | N |
Example: Walk the Flowsheet
ProcessSystem process = new ProcessSystem();
// ... add equipment ...
process.run();
for (ProcessEquipmentInterface unit : process.getUnitOperations()) {
List<StreamInterface> ins = unit.getInletStreams();
List<StreamInterface> outs = unit.getOutletStreams();
System.out.printf("%-20s in=%d out=%d%n",
unit.getName(), ins.size(), outs.size());
}
The returned lists are unmodifiable — they are read-only views of the equipment’s current connections. To change connections, use the equipment’s own setters (e.g., addStream(), setInletStream()).
Explicit Connections
ProcessSystem can record explicit connection metadata between equipment. This is used by DEXPI import/export, diagram generation, and topology analysis.
Declaring Connections
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(compressor);
// Record that feed connects to separator, via a specific stream
process.connect(feed, separator, feed.getOutletStream(),
ProcessConnection.ConnectionType.MATERIAL, "Feed to HP Sep");
// Simple connection (defaults to MATERIAL type)
process.connect(separator, compressor);
Connection Types
| Type | Description |
|---|---|
MATERIAL |
Process stream carrying fluid (default) |
ENERGY |
Energy stream (heat duty, shaft power) |
SIGNAL |
Instrument signal (controller, transmitter) |
Querying Connections
List<ProcessConnection> connections = process.getConnections();
for (ProcessConnection conn : connections) {
System.out.printf("%s -> %s [%s] %s%n",
conn.getSource().getName(),
conn.getTarget().getName(),
conn.getType(),
conn.getLabel());
}
Note: Connections are metadata — they do not change how the simulation runs. The actual data flow is determined by stream references set on each equipment.
Named Controllers
Equipment supports named controllers alongside the legacy single-controller API. This allows multiple controllers to be attached to the same equipment and retrieved by tag.
Adding Multiple Controllers
// Legacy API (still works, unchanged)
valve.setController(levelController);
// New named API — attach multiple controllers by tag
valve.addController("LC-100", levelController);
valve.addController("PC-200", pressureController);
Retrieving Controllers
// By tag
ControllerDeviceInterface lc = valve.getController("LC-100");
ControllerDeviceInterface pc = valve.getController("PC-200");
// All controllers on this equipment
Collection<ControllerDeviceInterface> all = valve.getControllers();
System.out.println("Controllers: " + all.size());
Backward Compatibility
The legacy setController() method still works and also registers the controller in the named map (using the controller’s name as the key). Existing code does not need any changes:
// Old code — still works exactly as before
valve.setController(myController);
// The controller is now also accessible via the named map
valve.getController(myController.getName()); // returns myController
valve.getControllers(); // returns [myController]
Unified Element Model
All elements that can live inside a ProcessSystem — equipment, measurement devices, and controller devices — share a common marker interface: ProcessElementInterface.
Type Hierarchy
ProcessElementInterface (extends NamedInterface, Serializable)
├── ProcessEquipmentInterface — unit operations and streams
├── MeasurementDeviceInterface — transmitters and sensors
└── ControllerDeviceInterface — PID controllers
Querying All Elements
// Get everything in the process — equipment + measurements + controllers
List<ProcessElementInterface> all = process.getAllElements();
for (ProcessElementInterface elem : all) {
System.out.println(elem.getName() + " : " + elem.getClass().getSimpleName());
}
This is useful for DEXPI export, diagram generation, and generic process analysis where you need a flat list of every element regardless of type.
Adding Controller Devices to ProcessSystem
Controller devices can be registered directly on the ProcessSystem. During transient simulation, the system automatically scans and executes all registered controllers after the equipment loop:
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(valve);
// Register controller at system level
process.add(levelController);
// During runTransient(), controllers are executed automatically
// after all equipment has been stepped
process.runTransient(1.0, calcId);
Running Simulations
Execution Methods Overview
| Method | Best For | Description |
|---|---|---|
run() |
General use | Sequential execution in insertion order |
runOptimized() |
Recommended | Auto-selects best strategy based on topology |
runParallel() |
Feed-forward processes | Maximum parallelism for no-recycle processes |
runHybrid() |
Complex processes | Parallel feed-forward + iterative recycle |
Recommended: runOptimized()
The runOptimized() method automatically analyzes your process and selects the best execution strategy:
// Recommended - auto-selects best strategy
process.runOptimized();
// With calculation ID for tracking
UUID calcId = UUID.randomUUID();
process.runOptimized(calcId);
How it works:
- No recycles detected: Uses
runParallel()for maximum speed - Recycles detected: Uses
runHybrid()which runs feed-forward sections in parallel, then iterates on recycle sections
Performance gains (typical separation train with 40 units, 3 recycles):
| Mode | Time | Speedup |
|——|——|———|
| Regular run() | 464 ms | baseline |
| Graph-based | 336 ms | 28% |
| runOptimized() | 286 ms | 38% |
Steady-State Simulation
// Basic sequential execution
process.run();
// Run with calculation ID for tracking
UUID calcId = UUID.randomUUID();
process.run(calcId);
Parallel Execution
For feed-forward processes (no recycles), parallel execution runs independent units simultaneously:
// Run independent units in parallel
try {
process.runParallel();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Note: runParallel() does not handle recycles or adjusters. Use runOptimized() for processes with recycles.
Thread Safety: Shared Stream Handling
When multiple units share the same input stream (e.g., a splitter/manifold feeding parallel branches), NeqSim automatically groups them to prevent race conditions.
How it works:
- Units at the same execution level are analyzed for shared input streams
- Units sharing an input stream are grouped together using a Union-Find algorithm
- Groups with shared streams run sequentially within the group
- Independent groups (no shared streams) run in parallel
Example - Parallel Pipelines:
// Three pipelines fed by the same manifold
Stream feedStream = new Stream("feed", fluid);
Splitter manifold = new Splitter("manifold", feedStream, 3);
AdiabaticPipe pipe1 = new AdiabaticPipe("pipe1", manifold.getSplitStream(0));
AdiabaticPipe pipe2 = new AdiabaticPipe("pipe2", manifold.getSplitStream(1));
AdiabaticPipe pipe3 = new AdiabaticPipe("pipe3", manifold.getSplitStream(2));
process.add(feedStream);
process.add(manifold);
process.add(pipe1);
process.add(pipe2);
process.add(pipe3);
// Safe: Each pipe has its own split stream (different objects)
// Pipes run in parallel without race conditions
process.runParallel();
Example - Shared Input Stream (handled automatically):
// Two units explicitly sharing the same input stream object
Stream sharedInput = valve.getOutletStream();
Heater heater1 = new Heater("heater1", sharedInput); // Same stream object
Heater heater2 = new Heater("heater2", sharedInput); // Same stream object
// NeqSim detects shared input and runs heater1 and heater2 sequentially
// Other independent units at this level still run in parallel
process.runParallel();
Applies to:
runParallel()- Groups by shared input streamsrunHybrid()- Groups in Phase 1 (feed-forward section)runTransient()- Groups at each time step
Hybrid Execution
For processes with recycles, hybrid execution combines parallel and iterative strategies:
// Hybrid: parallel feed-forward + iterative recycle
try {
process.runHybrid();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
How hybrid works:
- Phase 1 (Parallel): Run feed-forward units (before recycles) in parallel
- Phase 2 (Iterative): Run recycle section with graph-based iteration until convergence
Graph-Based Execution
Enable graph-based execution for optimized unit ordering:
// Enable graph-based execution order
process.setUseGraphBasedExecution(true);
process.run();
// Or use runOptimized() which handles this automatically
process.runOptimized();
Transient Simulation
Transient simulations use graph-based parallel execution for independent branches, applying the same shared-stream grouping as steady-state methods.
// Set time step
double dt = 1.0; // seconds
// Run single transient step
process.runTransient(dt);
// Run for specified duration
double totalTime = 3600.0; // 1 hour
for (double t = 0; t < totalTime; t += dt) {
process.runTransient(dt);
logResults(t);
}
Transient with Events
// Run transient with event handling
process.runTransient(dt, (time) -> {
if (time > 600.0) {
// Open blowdown valve after 10 minutes
bdv.setOpen(true);
}
});
Execution Strategy Analysis
Check Process Topology
// Check if process has recycles
boolean hasRecycles = process.hasRecycleLoops();
// Check if parallel execution would be beneficial
boolean useParallel = process.isParallelExecutionBeneficial();
// Get detailed partition analysis
String partitionInfo = process.getExecutionPartitionInfo();
System.out.println(partitionInfo);
Example output:
=== Execution Partition Analysis ===
Total units: 40
Has recycle loops: true
Parallel levels: 29
Max parallelism: 6
Units in recycle loops: 30
=== Hybrid Execution Strategy ===
Phase 1 (Parallel): 4 levels, 8 units
Phase 2 (Iterative): 25 levels, 32 units
Execution levels:
Level 0 [PARALLEL]: feed TP setter, first stage oil reflux, LP stream temp controller
Level 1 [PARALLEL]: 1st stage separator
Level 2 [PARALLEL]: oil depres valve
Level 3 [PARALLEL]:
--- Recycle Section Start (iterative) ---
Level 4: oil heater second stage [RECYCLE]
Level 5: 2nd stage separator [RECYCLE]
...
Get Parallel Partition Details
// Get parallel partition
ProcessGraph.ParallelPartition partition = process.getParallelPartition();
// Number of execution levels
int levels = partition.getLevelCount();
// Maximum units that can run simultaneously
int maxParallelism = partition.getMaxParallelism();
System.out.println("Execution levels: " + levels);
System.out.println("Max parallelism: " + maxParallelism);
Retrieving Equipment
By Name
// Get specific equipment
Compressor comp = (Compressor) process.getUnit("K-100");
Separator sep = (Separator) process.getUnit("HP Separator");
Stream stream = (Stream) process.getUnit("Feed");
By Type
// Get all compressors
List<CompressorInterface> compressors = process.getUnitsOfType(CompressorInterface.class);
// Get all separators
List<SeparatorInterface> separators = process.getUnitsOfType(SeparatorInterface.class);
All Equipment
// Get all equipment
List<ProcessEquipmentInterface> allUnits = process.getUnitOperations();
for (ProcessEquipmentInterface unit : allUnits) {
System.out.println(unit.getName() + ": " + unit.getClass().getSimpleName());
}
Results and Reporting
Console Display
// Display summary to console
process.display();
JSON Report
// Get JSON report
String jsonReport = process.getReport_json();
// Save to file
Files.writeString(Path.of("process_report.json"), jsonReport);
Tabular Report
// Get as table
String[][] table = process.getUnitOperationsAsTable();
// Print table
for (String[] row : table) {
System.out.println(String.join("\t", row));
}
Mass Balance
// Check overall mass balance
double totalIn = 0.0;
double totalOut = 0.0;
for (ProcessEquipmentInterface unit : process.getUnitOperations()) {
if (unit instanceof StreamInterface) {
StreamInterface stream = (StreamInterface) unit;
if (isInletStream(stream)) {
totalIn += stream.getFlowRate("kg/hr");
} else if (isOutletStream(stream)) {
totalOut += stream.getFlowRate("kg/hr");
}
}
}
double balance = (totalIn - totalOut) / totalIn * 100;
System.out.println("Mass balance closure: " + balance + "%");
Process Copying
Clone Process
// Create copy of process
ProcessSystem processCopy = process.copy();
// Modify copy without affecting original
Heater heater = (Heater) processCopy.getUnit("Heater");
heater.setOutTemperature(100.0, "C");
processCopy.run();
Deep Copy
All equipment and streams are deep-copied:
// Original
process.run();
double originalT = ((Stream) process.getUnit("Feed")).getTemperature("C");
// Copy and modify
ProcessSystem copy = process.copy();
((Stream) copy.getUnit("Feed")).setTemperature(50.0, "C");
copy.run();
// Original unchanged
assert originalT == ((Stream) process.getUnit("Feed")).getTemperature("C");
Advanced Features
Execution Strategy Selection
// Use optimized execution (recommended)
process.runOptimized(); // Auto-selects best strategy
// Or manually choose strategy:
// 1. Sequential (default)
process.run();
// 2. Graph-based ordering
process.setUseGraphBasedExecution(true);
process.run();
// 3. Parallel execution (no recycles)
process.runParallel();
// 4. Hybrid execution (recycle processes)
process.runHybrid();
Asynchronous Execution
// Run in background thread
Future<?> task = process.runAsTask();
// Do other work...
// Wait for completion
task.get();
// Or check if done
if (task.isDone()) {
System.out.println("Simulation complete");
}
Convergence Settings
// Set global convergence tolerance
process.setGlobalTolerance(1e-6);
// Set maximum iterations for recycles
process.setMaxRecycleIterations(50);
Process Modules
// Add pre-built module
ProcessModule compressorTrain = new CompressorTrainModule("HP Compression");
process.addModule(compressorTrain);
// Connect to process
compressorTrain.setInletStream(feedGas);
Stream compressed = compressorTrain.getOutletStream();
Validation
ProcessSystem provides comprehensive validation to check that all equipment is properly configured before running a simulation. This helps catch configuration errors early and provides actionable error messages.
Quick Check: isReadyToRun()
The simplest way to validate a process before execution:
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(compressor);
// Quick check - returns true if no CRITICAL errors
if (process.isReadyToRun()) {
process.run();
} else {
System.out.println("Process not ready to run");
ValidationResult result = process.validateSetup();
result.getErrors().forEach(System.out::println);
}
Detailed Validation: validateSetup()
Get a combined ValidationResult for the entire process system:
ValidationResult result = process.validateSetup();
if (!result.isValid()) {
System.out.println("Validation issues found:");
System.out.println(result.getReport());
// Iterate through specific issues
for (ValidationIssue issue : result.getIssues()) {
System.out.println(issue.getSeverity() + ": " + issue.getMessage());
System.out.println(" Fix: " + issue.getRemediation());
}
}
Severity Levels:
| Level | Description |
|——-|————-|
| CRITICAL | Blocks execution - must be fixed |
| MAJOR | Likely to cause errors during simulation |
| MINOR | May affect accuracy of results |
| INFO | Informational warnings |
Per-Equipment Validation: validateAll()
Get individual validation results for each piece of equipment:
Map<String, ValidationResult> allResults = process.validateAll();
for (Map.Entry<String, ValidationResult> entry : allResults.entrySet()) {
String equipmentName = entry.getKey();
ValidationResult equipResult = entry.getValue();
if (!equipResult.isValid()) {
System.out.println(equipmentName + " has issues:");
equipResult.getErrors().forEach(e -> System.out.println(" - " + e));
}
}
Equipment-Level Validation
Each equipment class implements validateSetup() to check equipment-specific requirements:
| Equipment | Validates |
|---|---|
| Stream | Has fluid set, temperature > 0 K |
| Separator | Inlet stream connected |
| Mixer | At least one inlet stream |
| Splitter | Inlet stream connected, split fractions sum to 1.0 |
| Tank | Has fluid or input stream |
| DistillationColumn | Feed streams connected, condenser/reboiler configured |
| Recycle | Inlet and outlet streams connected, tolerance > 0 |
| Adjuster | Target and adjustment variables set, tolerance > 0 |
| TwoPortEquipment | Inlet stream connected |
Example - Individual Equipment Validation:
Separator separator = new Separator("V-100");
// Forgot to set inlet stream
ValidationResult result = separator.validateSetup();
if (!result.isValid()) {
// Will report: "Separator 'V-100' has no inlet stream connected"
System.out.println(result.getReport());
}
Validation in AI/ML Workflows
For AI agents and automated workflows, validation provides structured feedback:
AIIntegrationHelper helper = AIIntegrationHelper.forProcess(process);
if (helper.isReady()) {
ExecutionResult result = helper.safeRun();
} else {
// Get issues as structured text for AI to parse
String[] issues = helper.getIssuesAsText();
for (String issue : issues) {
// AI can parse and fix these issues
System.out.println(issue);
}
}
See AI Validation Framework for more details on AI integration.
Examples
Simple Separation Process
ProcessSystem process = new ProcessSystem("Separator System");
// Create fluid
SystemInterface fluid = new SystemSrkEos(300.0, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.08);
fluid.addComponent("propane", 0.04);
fluid.addComponent("n-butane", 0.03);
fluid.setMixingRule("classic");
// Feed stream
Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(100000.0, "kg/hr");
process.add(feed);
// Inlet valve
ThrottlingValve inletValve = new ThrottlingValve("Inlet Valve", feed);
inletValve.setOutletPressure(30.0, "bara");
process.add(inletValve);
// HP Separator
Separator hpSep = new Separator("HP Separator", inletValve.getOutletStream());
process.add(hpSep);
// LP Valve
ThrottlingValve lpValve = new ThrottlingValve("LP Valve", hpSep.getLiquidOutStream());
lpValve.setOutletPressure(5.0, "bara");
process.add(lpValve);
// LP Separator
Separator lpSep = new Separator("LP Separator", lpValve.getOutletStream());
process.add(lpSep);
// Run
process.run();
// Results
System.out.println("HP Gas: " + hpSep.getGasOutStream().getFlowRate("MSm3/day") + " MSm3/day");
System.out.println("LP Gas: " + lpSep.getGasOutStream().getFlowRate("MSm3/day") + " MSm3/day");
System.out.println("Liquid: " + lpSep.getLiquidOutStream().getFlowRate("m3/hr") + " m3/hr");
Compression System
ProcessSystem process = new ProcessSystem("Compression System");
// Gas feed
Stream gas = new Stream("Gas Feed", gasFluid);
gas.setFlowRate(50000.0, "Sm3/hr");
gas.setTemperature(40.0, "C");
gas.setPressure(5.0, "bara");
process.add(gas);
// First stage compressor
Compressor comp1 = new Compressor("K-101", gas);
comp1.setOutletPressure(15.0, "bara");
comp1.setPolytropicEfficiency(0.78);
process.add(comp1);
// Intercooler
Cooler cooler1 = new Cooler("E-101", comp1.getOutletStream());
cooler1.setOutTemperature(40.0, "C");
process.add(cooler1);
// Second stage compressor
Compressor comp2 = new Compressor("K-102", cooler1.getOutletStream());
comp2.setOutletPressure(45.0, "bara");
comp2.setPolytropicEfficiency(0.78);
process.add(comp2);
// Aftercooler
Cooler cooler2 = new Cooler("E-102", comp2.getOutletStream());
cooler2.setOutTemperature(40.0, "C");
process.add(cooler2);
// Run
process.run();
// Total power
double totalPower = comp1.getPower("kW") + comp2.getPower("kW");
System.out.println("Total compression power: " + totalPower + " kW");
Process with Recycle
ProcessSystem process = new ProcessSystem("Recycle Process");
// Fresh feed
Stream freshFeed = new Stream("Fresh Feed", freshFluid);
freshFeed.setFlowRate(1000.0, "kg/hr");
process.add(freshFeed);
// Mixer for fresh feed and recycle
Mixer feedMixer = new Mixer("Feed Mixer");
feedMixer.addStream(freshFeed);
process.add(feedMixer);
// Reactor
GibbsReactor reactor = new GibbsReactor("Reactor");
reactor.setInletStream(feedMixer.getOutletStream());
process.add(reactor);
// Product separator
Separator productSep = new Separator("Product Sep", reactor.getOutletStream());
process.add(productSep);
// Product stream
Stream product = productSep.getLiquidOutStream();
// Recycle unreacted gas
Recycle recycle = new Recycle("Gas Recycle");
recycle.addStream(productSep.getGasOutStream());
recycle.setOutletStream(feedMixer);
recycle.setTolerance(1e-5);
process.add(recycle);
// Complete the connection
feedMixer.addStream(recycle.getOutletStream());
// Run (will iterate until recycle converges)
process.run();
System.out.println("Recycle converged: " + recycle.isConverged());
System.out.println("Product rate: " + product.getFlowRate("kg/hr") + " kg/hr");
Saving and Loading
ProcessSystem supports saving and loading to/from compressed .neqsim files and JSON state files for version control.
// Save to compressed .neqsim file (recommended)
process.saveToNeqsim("my_process.neqsim");
// Load (auto-runs after loading)
ProcessSystem loaded = ProcessSystem.loadFromNeqsim("my_process.neqsim");
// Auto-detect format by extension
process.saveAuto("my_process.neqsim"); // Compressed XStream XML
process.saveAuto("my_process.json"); // JSON state export
// JSON state for version control
ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);
state.setVersion("1.0.0");
state.saveToFile("my_process_v1.0.0.json");
For full documentation on serialization options, see Process Serialization Guide.
Related Documentation
- ProcessModel - Multi-process container
- ProcessModule - Modular process units
- Process Serialization - Save/load processes
- Graph Simulation - Graph-based execution
- Equipment Overview - Process equipment
- Controllers - PID control and adjusters
- Dynamic Simulation Guide - Transient simulation
- Extending Process Equipment - Custom equipment