Skip to the content.

Documentation for recycle handling in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.util

Classes: | Class | Description | |——-|————-| | Recycle | Main recycle handler | | RecycleController | Advanced recycle control | | AccelerationMethod | Convergence acceleration | | BroydenAccelerator | Broyden’s method acceleration |

Recycles handle iterative loops in process flowsheets where a downstream stream feeds back into an upstream unit. Common examples:


Recycle Class

Basic Usage

import neqsim.process.equipment.util.Recycle;

// Create recycle
Recycle recycle = new Recycle("Solvent Recycle");

// Add the stream coming from downstream
recycle.addStream(returnStream);

// Set where the recycle feeds into
recycle.setOutletStream(feedMixer);

// Add to process
process.add(recycle);
process.run();

How Recycles Work

  1. Initial Estimate: Process runs with assumed recycle composition
  2. Calculate Downstream: Equipment processes the estimate
  3. Update Recycle: New recycle stream values are calculated
  4. Iterate: Repeat until convergence

Steady-to-Dynamic Handoff

Use the same Recycle object for the converged steady state and the following transient. In steady state, ProcessSystem iterates the tear stream with the configured direct-substitution, Wegstein, or Broyden method. In transient mode, the previously accepted recycle outlet is consumed upstream before the current recycle inlet is published for the next flowsheet evaluation. This ordered dependency break avoids introducing a separate transient recycle class.

process.run();

if (!recycle.solved()) {
    throw new IllegalStateException("Initial recycle did not converge");
}

recycle.setCalculateSteadyState(false);
process.runTransient(0.25, UUID.randomUUID());

The transient update temporarily uses direct substitution and restores the configured steady-state acceleration method afterward. It does not iterate the recycle loop to steady convergence inside each physical timestep and does not add independent material inventory. Equipment such as separators, piping, and volumes owns the differential state.

ProcessSystem detects Recycle and RecycleFlowCoordinator units and falls back to insertion-order sequential transient execution when required, even if parallel transient execution is enabled. A pressure-driven anti-surge topology can use RecycleFlowCoordinator to reconcile a valve-requested recycle flow with its discharge splitter while preserving mass balance.


Configuration

Tolerance

// Set convergence tolerance
recycle.setTolerance(1e-6);

// Separate tolerances for flow and composition
recycle.setFlowTolerance(1e-4);
recycle.setCompositionTolerance(1e-6);
recycle.setTemperatureTolerance(0.1);  // K
recycle.setPressureTolerance(0.01);    // bar

setFlowTolerance() retains its legacy units: kg/s below 1 kg/s loop flow and percent at or above 1 kg/s. For example, the default 0.01 permits 0.01 kg/s change on a 0.02 kg/s loop, which is 50% of that loop flow. Choose a smaller flow tolerance for small loops.

getAbsoluteFlowChange() always reports kg/hr. setAbsoluteFlowTolerance() adds an OR acceptance criterion: it can relax the legacy criterion, but it cannot make a loose legacy tolerance stricter. Tighten setFlowTolerance() first when an absolute limit should govern convergence.

Maximum Iterations

// Limit iterations
recycle.setMaximumIterations(50);

Damping

Damping helps prevent oscillation:

// Set damping factor (0-1, lower = more damping)
recycle.setDampingFactor(0.5);  // 50% of new value, 50% of old

Acceleration Methods

For faster convergence, acceleration methods can be used:

Wegstein Acceleration

import neqsim.process.equipment.util.AccelerationMethod;

recycle.setAccelerationMethod(AccelerationMethod.WEGSTEIN);

Broyden Acceleration

import neqsim.process.equipment.util.BroydenAccelerator;

BroydenAccelerator accelerator = new BroydenAccelerator();
recycle.setAccelerationMethod(accelerator);

The accelerator solves the fixed-point residual g(x) - x with a damped inverse-Jacobian step. It uses direct substitution for its first two calls; the first Broyden step is taken on the third call. Monitor convergence on a representative process before using this option for a difficult recycle.

A Recycle requires a configured tear outlet before run(); wire an existing stream using setOutletStream(stream). If missing, run() reports the recycle name and the required configuration. Before wiring, getOutletStream() returns null so process modules can inspect the connection without running the recycle.

Composition acceleration and diagnostics

Per-recycle Wegstein and Broyden acceleration operates on overall component mole fractions. Temperature, pressure and total molar flow use the current return stream values. The accelerated composition updates component inventories and is TP-flashed before publication; the gas and liquid phases retain distinct equilibrium compositions. Negative proposals are clipped and normalized; an invalid proposal or failed flash falls back to the unaccelerated return.

The composition convergence residual compares overall mole fractions before acceleration. This prevents a damped or clipped step from reporting a false converged state. A changed component list resets the acceleration history.

getCompositionWegsteinQFactors() returns one factor per component. getWegsteinQFactors() preserves the 3 + n layout, with three reserved zeros for the unaccelerated T/P/flow entries followed by the composition factors. Factors describe the proposal before clipping and normalization. The per-recycle Broyden matrix contains only the n composition coordinates. See the acceleration guide for coordinate definitions and compatibility guidance.

Direct Substitution

Simple successive substitution (default):

recycle.setAccelerationMethod("direct");

Usage Examples

Simple Solvent Recycle

ProcessSystem process = new ProcessSystem();

// Feed stream
Stream feed = new Stream("Feed", feedFluid);
process.add(feed);

// Mixer for feed and recycle
Mixer mixer = new Mixer("Feed Mixer");
mixer.addStream(feed);
process.add(mixer);

// Process unit (e.g., absorber)
Absorber absorber = new Absorber("TEG Contactor", mixer.getOutletStream());
process.add(absorber);

// Regeneration
Heater regenerator = new Heater("TEG Regenerator", absorber.getLiquidOutStream());
regenerator.setOutTemperature(200.0, "C");
process.add(regenerator);

// Cooler
Cooler cooler = new Cooler("TEG Cooler", regenerator.getOutletStream());
cooler.setOutTemperature(40.0, "C");
process.add(cooler);

// Recycle lean solvent back to mixer
Recycle solventRecycle = new Recycle("TEG Recycle");
solventRecycle.addStream(cooler.getOutletStream());
solventRecycle.setOutletStream(mixer);
solventRecycle.setTolerance(1e-5);
process.add(solventRecycle);

// Connect mixer to absorber with recycle
mixer.addStream(solventRecycle.getOutletStream());

// Run
process.run();

// Check convergence
if (solventRecycle.isConverged()) {
    System.out.println("Recycle converged in " + 
        solventRecycle.getIterations() + " iterations");
}

Reactor Recycle

// Fresh feed
Stream freshFeed = new Stream("Fresh Feed", freshFeedFluid);
process.add(freshFeed);

// Mix fresh feed with recycle
Mixer reactorFeed = new Mixer("Reactor Feed");
reactorFeed.addStream(freshFeed);
process.add(reactorFeed);

// Reactor
GibbsReactor reactor = new GibbsReactor("Synthesis Reactor");
reactor.setInletStream(reactorFeed.getOutletStream());
process.add(reactor);

// Separator
Separator productSep = new Separator("Product Separator", reactor.getOutletStream());
process.add(productSep);

// Recycle unreacted gas
Recycle gasRecycle = new Recycle("Unreacted Gas Recycle");
gasRecycle.addStream(productSep.getGasOutStream());
gasRecycle.setOutletStream(reactorFeed);
gasRecycle.setTolerance(1e-5);
gasRecycle.setDampingFactor(0.7);
process.add(gasRecycle);

reactorFeed.addStream(gasRecycle.getOutletStream());

process.run();

Nested Recycles

For processes with multiple recycle loops:

// Outer recycle (converges first)
Recycle outerRecycle = new Recycle("Outer Recycle");
outerRecycle.addStream(outerStream);
outerRecycle.setOutletStream(outerMixer);
outerRecycle.setPriority(1);  // Lower priority converges first
process.add(outerRecycle);

// Inner recycle (converges second)
Recycle innerRecycle = new Recycle("Inner Recycle");
innerRecycle.addStream(innerStream);
innerRecycle.setOutletStream(innerMixer);
innerRecycle.setPriority(2);  // Higher priority
process.add(innerRecycle);

Convergence Monitoring

Check Status

// Check if converged
boolean converged = recycle.isConverged();

// Get number of iterations
int iterations = recycle.getIterations();

// Get current error
double error = recycle.getError();

System.out.println("Recycle status:");
System.out.println("  Converged: " + converged);
System.out.println("  Iterations: " + iterations);
System.out.println("  Error: " + error);

Convergence History

// Get convergence history for debugging
double[] errorHistory = recycle.getErrorHistory();
for (int i = 0; i < errorHistory.length; i++) {
    System.out.println("Iteration " + i + ": error = " + errorHistory[i]);
}

Troubleshooting

Slow Convergence

  1. Reduce damping factor
  2. Use acceleration method
  3. Check for conflicting specifications
  4. Improve initial estimate
// Try Wegstein acceleration
recycle.setAccelerationMethod(AccelerationMethod.WEGSTEIN);
recycle.setDampingFactor(0.8);

Oscillation

  1. Increase damping
  2. Reduce step size
  3. Check for multiple solutions
// Heavy damping for oscillating systems
recycle.setDampingFactor(0.3);
recycle.setMaximumIterations(100);

Non-Convergence

  1. Check physical feasibility
  2. Verify mass balance closure
  3. Start with simpler configuration
  4. Check stream specifications
// Debug mode
recycle.setVerbose(true);
process.run();

Best Practices

  1. Tear Stream Selection: Choose streams with least impact on downstream
  2. Good Initial Estimate: Provide reasonable starting values
  3. Appropriate Tolerance: Balance accuracy vs. computation time
  4. Monitor Convergence: Check iteration count and error trends
  5. Sequential Solution: For nested loops, converge inner loops first