Skip to the content.

Graph-Based Process Simulation

Documentation for graph-based execution in NeqSim.

Table of Contents


Overview

Location: neqsim.process.processmodel.graph

Classes: | Class | Description | |——-|————-| | ProcessGraph | Graph representation of process | | ProcessGraphBuilder | Builder for constructing graphs | | ProcessNode | Node representing equipment | | ProcessEdge | Edge representing stream connection |

Graph-based simulation represents the process as a directed graph where:

Benefits:


Execution Strategies

Quick Start - Use runOptimized()

The recommended approach is to use runOptimized() which automatically analyzes your process and selects the best strategy:

// Auto-selects best execution strategy based on process topology
process.runOptimized();

// With calculation ID for tracking
UUID calcId = UUID.randomUUID();
process.runOptimized(calcId);

The method inspects the process for:

Execution Strategy Comparison

Strategy Method Best For When Used by runOptimized()
Sequential run() or runSequential() Recycles, multi-input equipment Has Recycle units or Mixer/HeatExchanger/etc.
Graph-based setUseGraphBasedExecution(true) Complex ordering Manual configuration only
Parallel runParallel() Feed-forward (no recycles) No recycles, no multi-input, no adjusters
Hybrid runHybrid() Processes with adjusters Has adjusters but no recycles/multi-input
Optimized runOptimized() All processes Auto-selects from above

Sequential Execution (Default)

Standard execution in insertion order:

process.run();

Graph-Based Execution

Uses topological ordering for optimal execution sequence:

// Enable graph-based ordering
process.setUseGraphBasedExecution(true);
process.run();

Parallel Execution

Executes independent units simultaneously using thread pool:

// For feed-forward processes (no recycles)
try {
    process.runParallel();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

How it works:

  1. Builds dependency graph
  2. Partitions into execution levels
  3. Runs units at each level in parallel
  4. Waits for level completion before next level

Hybrid Execution

Combines parallel and iterative execution for processes with recycles:

// For processes with recycles
try {
    process.runHybrid();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

How it works:

  1. Phase 1 (Parallel): Run feed-forward units in parallel
  2. Phase 2 (Iterative): Run recycle section with convergence iteration

Automatically selects the best strategy based on process topology:

// Auto-selects best execution strategy
process.runOptimized();

Decision logic (in order of priority):

Condition Strategy Reason
Has Recycle units runSequential() Recycles require full iterative convergence
Has multi-input equipment runSequential() Mixer, Manifold, etc. need correct stream ordering
Has Adjuster units runHybrid() Adjusters need iteration but feed-forward can parallelize
Feed-forward only runParallel() Maximum speed with no dependencies

Multi-input equipment includes:

Note: hasRecycles() checks for explicit Recycle unit operations, not graph-based cycle detection.


Analyzing Execution Strategy

Check Process Topology

// Check if process has Recycle units (requires iterative execution)
boolean hasRecycles = process.hasRecycles();

// Check if process has Adjuster units (requires iteration)
boolean hasAdjusters = process.hasAdjusters();

// Check if process has multi-input equipment (requires sequential)
// Includes: Mixer, Manifold, HeatExchanger, TurboExpanderCompressor, etc.
boolean hasMultiInput = process.hasMultiInputEquipment();

// Check if parallel execution would be beneficial (graph-based)
boolean beneficial = process.isParallelExecutionBeneficial();

// Get detailed partition analysis
System.out.println(process.getExecutionPartitionInfo());

Understanding runOptimized() Selection

// What will runOptimized() do for my process?
if (process.hasRecycles()) {
    System.out.println("Will use: runSequential() - Recycle units detected");
} else if (process.hasMultiInputEquipment()) {
    System.out.println("Will use: runSequential() - Multi-input equipment detected");
} else if (process.hasAdjusters()) {
    System.out.println("Will use: runHybrid() - Adjusters with parallel feed-forward");
} else {
    System.out.println("Will use: runParallel() - Full parallel execution");
}

Example Partition Analysis Output

=== Execution Partition Analysis ===
Total units: 40
Has recycle loops: true
Parallel levels: 29
Max parallelism: 6
Units in recycle loops: 30
  - 1st stage compressor
  - 2nd stage separator
  ...

=== 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, export oil
  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

// Get detailed partition info
ProcessGraph.ParallelPartition partition = process.getParallelPartition();

System.out.println("Execution levels: " + partition.getLevelCount());
System.out.println("Max parallelism: " + partition.getMaxParallelism());

// Iterate through levels
for (List<ProcessNode> level : partition.getLevels()) {
    System.out.println("Level has " + level.size() + " units");
}

ProcessGraph Class

Basic Usage

import neqsim.process.processmodel.graph.ProcessGraph;
import neqsim.process.processmodel.graph.ProcessGraphBuilder;

// Build graph from process
ProcessGraphBuilder builder = new ProcessGraphBuilder();
ProcessGraph graph = builder.build(processSystem);

// Execute in topological order
graph.execute();

Graph Properties

// Get number of nodes (equipment)
int nodeCount = graph.getNodeCount();

// Get number of edges (connections)
int edgeCount = graph.getEdgeCount();

// Check for cycles (recycles)
boolean hasCycles = graph.hasCycles();

Graph Construction

Automatic Construction

// Build from existing process
ProcessGraphBuilder builder = new ProcessGraphBuilder();
ProcessGraph graph = builder.build(process);

Manual Construction

ProcessGraph graph = new ProcessGraph();

// Add nodes
graph.addNode(feed);
graph.addNode(heater);
graph.addNode(separator);

// Add edges (connections)
graph.addEdge(feed, heater);
graph.addEdge(heater, separator);

With Metadata

// Add node with properties
Map<String, Object> props = new HashMap<>();
props.put("criticality", "high");
props.put("maintainPriority", 1);
graph.addNode(compressor, props);

Graph Analysis

Topological Sort

// Get execution order
List<ProcessEquipmentInterface> order = graph.topologicalSort();

for (int i = 0; i < order.size(); i++) {
    System.out.println((i+1) + ". " + order.get(i).getName());
}

Find Cycles

// Identify recycle loops
List<List<ProcessEquipmentInterface>> cycles = graph.findCycles();

for (List<ProcessEquipmentInterface> cycle : cycles) {
    System.out.println("Cycle found:");
    for (ProcessEquipmentInterface node : cycle) {
        System.out.println("  - " + node.getName());
    }
}

Critical Path

// Find longest path (critical path)
List<ProcessEquipmentInterface> criticalPath = graph.findCriticalPath();

System.out.println("Critical path:");
for (ProcessEquipmentInterface node : criticalPath) {
    System.out.println("  " + node.getName());
}

Visualization

Export to DOT Format

// Export for Graphviz visualization
String dot = graph.toDOT();
Files.writeString(Path.of("process_graph.dot"), dot);

// Generate image with Graphviz:
// dot -Tpng process_graph.dot -o process_graph.png

Export to JSON

// Export graph structure to JSON
String json = graph.toJSON();
Files.writeString(Path.of("process_graph.json"), json);

Usage Examples

Parallel Compression Train

ProcessSystem process = new ProcessSystem();

// Feed splitter
Splitter splitter = new Splitter("Feed Splitter", feedStream);
splitter.setSplitRatios(new double[]{0.5, 0.5});
process.add(splitter);

// Parallel compressor trains (can execute simultaneously)
Compressor comp1 = new Compressor("K-101", splitter.getOutletStream(0));
comp1.setOutletPressure(80.0, "bara");
process.add(comp1);

Compressor comp2 = new Compressor("K-102", splitter.getOutletStream(1));
comp2.setOutletPressure(80.0, "bara");
process.add(comp2);

// Merger
Mixer mixer = new Mixer("Discharge Mixer");
mixer.addStream(comp1.getOutletStream());
mixer.addStream(comp2.getOutletStream());
process.add(mixer);

// Build graph
ProcessGraph graph = new ProcessGraphBuilder().build(process);

// Parallel execution - K-101 and K-102 run simultaneously
graph.setExecutionStrategy(ExecutionStrategy.PARALLEL);
graph.execute();

Complex Flowsheet Analysis

// Build graph from complex process
ProcessGraph graph = new ProcessGraphBuilder().build(process);

// Analyze structure
System.out.println("Process structure:");
System.out.println("  Equipment count: " + graph.getNodeCount());
System.out.println("  Connections: " + graph.getEdgeCount());
System.out.println("  Has recycles: " + graph.hasCycles());

// Identify independent sections
List<Set<ProcessEquipmentInterface>> sections = graph.findConnectedComponents();
System.out.println("  Independent sections: " + sections.size());

// Find potential bottlenecks (high in-degree)
for (ProcessEquipmentInterface node : graph.getNodes()) {
    int inDegree = graph.getInDegree(node);
    if (inDegree > 2) {
        System.out.println("  Potential bottleneck: " + node.getName() + 
            " (" + inDegree + " inputs)");
    }
}

Recycle Identification

ProcessGraph graph = new ProcessGraphBuilder().build(process);

// Find all recycle streams
List<List<ProcessEquipmentInterface>> cycles = graph.findCycles();

System.out.println("Recycle loops identified:");
for (int i = 0; i < cycles.size(); i++) {
    System.out.println("Recycle " + (i+1) + ":");
    List<ProcessEquipmentInterface> cycle = cycles.get(i);
    for (ProcessEquipmentInterface node : cycle) {
        System.out.println("  -> " + node.getName());
    }
    
    // Suggest tear stream (node with lowest "impact")
    ProcessEquipmentInterface tearStream = graph.suggestTearStream(cycle);
    System.out.println("  Suggested tear stream: " + tearStream.getName());
}

Performance Optimization

Identify Parallel Opportunities

// Get execution levels
List<List<ProcessEquipmentInterface>> levels = graph.getExecutionLevels();

// Count parallel opportunities
int parallelOps = 0;
for (List<ProcessEquipmentInterface> level : levels) {
    if (level.size() > 1) {
        parallelOps += level.size() - 1;
    }
}

System.out.println("Parallel execution opportunities: " + parallelOps);
System.out.println("Potential speedup: " + 
    (double)graph.getNodeCount() / levels.size() + "x");

Subgraph Extraction

// Extract subgraph for specific section
Set<ProcessEquipmentInterface> compressionUnits = process.getUnitsOfType(
    CompressorInterface.class).stream().collect(Collectors.toSet());

ProcessGraph compressionGraph = graph.extractSubgraph(compressionUnits);

// Analyze compression section separately
compressionGraph.execute();


ProcessModel Execution

When combining multiple ProcessSystem instances into a ProcessModel, execution follows a similar pattern:

Running ProcessModel

import neqsim.process.processmodel.ProcessModel;

// Create and populate model
ProcessModel model = new ProcessModel();
model.add("Upstream", upstreamProcess);
model.add("Compression", compressionProcess);
model.add("Export", exportProcess);

// Run until convergence (uses optimized execution internally)
model.run();

// Check convergence
if (model.isModelConverged()) {
    System.out.println("Converged in " + model.getLastIterationCount() + " iterations");
}

Execution Options

// Continuous mode (default) - iterates until convergence
model.setRunStep(false);
model.run();

// Step mode - run one iteration at a time
model.setRunStep(true);
model.run();  // One step for each ProcessSystem
model.run();  // Next step...

// Asynchronous execution
Future<?> task = model.runAsTask();
// ... do other work ...
task.get();  // Wait for completion

Optimized Execution in ProcessModel

Each ProcessSystem within a ProcessModel uses runOptimized() by default:

// Enable/disable optimized execution for contained ProcessSystems
model.setUseOptimizedExecution(true);  // Default
model.run();