Dynamic Simulation Guide
Comprehensive guide to transient and dynamic simulation in NeqSim.
Table of Contents
- Overview
- Steady-State vs Transient Mode
- Basic Transient Setup
- Time Stepping
- Equipment with Transient Support
- Control Systems
- Pipeline Dynamics
- Vessel Depressurization
- Calculation Identifiers
- Best Practices
- Examples
- Related Documentation
Overview
NeqSim supports both steady-state and transient (dynamic) simulation modes. Dynamic simulation enables modeling of:
- Process Dynamics: Startup, shutdown, upset conditions
- Control Systems: PID controllers responding to disturbances
- Flow Transients: Slug flow, pressure waves, liquid accumulation
- Depressurization: Blowdown, emergency relief scenarios
- Reservoir Depletion: Time-dependent production
When to Use Dynamic Simulation
| Scenario | Mode |
|---|---|
| Design calculations | Steady-state |
| Equipment sizing | Steady-state |
| Controller tuning | Transient |
| Startup/shutdown analysis | Transient |
| Safety studies (blowdown) | Transient |
| Slug catcher sizing | Transient |
| Training simulators | Transient |
Steady-State vs Transient Mode
Steady-State Mode (Default)
In steady-state mode, each run() call calculates the equilibrium solution assuming constant boundary conditions:
ProcessSystem process = new ProcessSystem();
process.add(stream);
process.add(separator);
process.add(compressor);
// Steady-state - each run() finds equilibrium
process.run();
Transient Mode
In transient mode, equipment remembers state between time steps. Enable by setting setCalculateSteadyState(false):
// Enable transient mode on equipment
separator.setCalculateSteadyState(false);
pipeline.setCalculateSteadyState(false);
// Time step through simulation
UUID id = UUID.randomUUID();
for (int step = 0; step < 100; step++) {
process.runTransient(1.0, id); // 1 second time step
}
Key Differences
| Aspect | Steady-State | Transient |
|---|---|---|
| State memory | None | Equipment retains state |
| Method | run() |
runTransient(dt, id) |
| Holdup | Ignored | Tracked (level, pressure) |
| Controllers | Converged | Time-stepping response |
| Initialization | Automatic | Requires steady-state first |
Basic Transient Setup
Step-by-Step Setup
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemSrkEos;
import java.util.UUID;
// 1. Create thermodynamic system
SystemInterface fluid = new SystemSrkEos(298.15, 50.0);
fluid.addComponent("methane", 0.9);
fluid.addComponent("ethane", 0.1);
fluid.setMixingRule("classic");
// 2. Build process flowsheet
Stream feed = new Stream("Feed", fluid);
feed.setFlowRate(1000.0, "kg/hr");
Separator separator = new Separator("V-100", feed);
separator.setInternalDiameter(2.0);
separator.setSeparatorLength(5.0);
ThrottlingValve gasValve = new ThrottlingValve("Gas Valve", separator.getGasOutStream());
gasValve.setOutletPressure(20.0, "bara");
ThrottlingValve liquidValve = new ThrottlingValve("Liq Valve", separator.getLiquidOutStream());
liquidValve.setOutletPressure(20.0, "bara");
// 3. Create process system
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(gasValve);
process.add(liquidValve);
// 4. Initialize with steady-state
process.run();
System.out.println("Initial level: " + separator.getLiquidLevel());
// 5. Switch to transient mode
separator.setCalculateSteadyState(false);
// 6. Run transient simulation
UUID calcId = UUID.randomUUID();
double dt = 1.0; // 1 second time step
for (int step = 0; step < 60; step++) {
process.runTransient(dt, calcId);
if (step % 10 == 0) {
System.out.printf("t=%d s, Level=%.3f m, P=%.2f bara%n",
step, separator.getLiquidLevel(), separator.getPressure());
}
}
Time Stepping
Choosing Time Step Size
The time step should be small enough to capture dynamics but large enough for efficiency:
| Application | Typical Time Step |
|---|---|
| Fast control loops | 0.1 - 1.0 s |
| Separator level control | 1.0 - 10.0 s |
| Pipeline transients | 0.5 - 5.0 s |
| Reservoir depletion | 3600 s (1 hour) to 86400 s (1 day) |
| Blowdown studies | 0.1 - 1.0 s |
CFL Condition for Pipelines
For pipeline transients, the time step should satisfy the CFL (Courant-Friedrichs-Lewy) condition:
\[\Delta t \leq \frac{\Delta x}{v + c}\]Where:
- Δx = segment length
- v = flow velocity
- c = speed of sound
Variable Time Stepping
double t = 0;
double tEnd = 3600; // 1 hour
double dt = 1.0;
while (t < tEnd) {
process.runTransient(dt, calcId);
// Adjust time step based on rate of change
double rateOfChange = Math.abs(separator.getLiquidLevel() - previousLevel) / dt;
if (rateOfChange > 0.1) {
dt = Math.max(0.1, dt / 2); // Decrease time step
} else if (rateOfChange < 0.01) {
dt = Math.min(10.0, dt * 1.5); // Increase time step
}
t += dt;
previousLevel = separator.getLiquidLevel();
}
Equipment with Transient Support
Separators
Separator separator = new Separator("V-100", feed);
separator.setInternalDiameter(2.0);
separator.setSeparatorLength(5.0);
separator.setLiquidLevel(0.5); // Initial level (m)
separator.setCalculateSteadyState(false);
// During transient, level changes based on in/out flow imbalance
separator.runTransient(dt, id);
double newLevel = separator.getLiquidLevel();
Pipelines (PipeBeggsAndBrills)
PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("Pipeline", feed);
pipe.setLength(10000); // 10 km
pipe.setDiameter(0.3); // 0.3 m
pipe.setNumberOfIncrements(50);
pipe.setCalculateSteadyState(false);
// Transient tracks pressure waves and liquid holdup
pipe.runTransient(dt, id);
Tanks
Tank tank = new Tank("T-100", feed);
tank.setVolume(100.0); // m³
tank.setCalculateSteadyState(false);
// Tank pressure/composition evolves based on in/out flows
tank.runTransient(dt, id);
Compressors
Compressor comp = new Compressor("K-100", feed);
comp.setOutletPressure(100.0, "bara");
// Enable dynamic features (state machine, events)
comp.setDynamicSimulationEnabled(true);
comp.setRotorInertia(100.0); // kg·m²
// Compressor responds to speed changes, surge, etc.
comp.runTransient(dt, id);
Reservoirs
SimpleReservoir reservoir = new SimpleReservoir("Field");
reservoir.setReservoirFluid(fluid);
reservoir.setGasVolume(1e9, "Sm3");
StreamInterface producer = reservoir.addGasProducer("GP-1");
producer.setFlowRate(5.0, "MSm3/day");
// Run for one day
reservoir.runTransient(86400, id);
double remainingGas = reservoir.getGasInPlace("Sm3");
Control Systems
Adding Controllers for Dynamic Simulation
Controllers require transmitters to measure process variables:
// Level transmitter
LevelTransmitter levelTransmitter = new LevelTransmitter("LT-100", separator);
levelTransmitter.setMinimumValue(0.0);
levelTransmitter.setMaximumValue(2.0);
// Level controller
ControllerDeviceBaseClass levelController = new ControllerDeviceBaseClass("LC-100");
levelController.setTransmitter(levelTransmitter);
levelController.setControllerSetPoint(0.5); // 0.5 m target level
levelController.setControllerParameters(0.5, 100.0, 0.0); // Kp, Ti, Td
levelController.setReverseActing(true); // Open valve to lower level
// Connect controller to valve
liquidValve.setController(levelController);
Pressure Controller
// Pressure transmitter
PressureTransmitter pressureTransmitter = new PressureTransmitter("PT-100", separator);
pressureTransmitter.setMinimumValue(0.0);
pressureTransmitter.setMaximumValue(100.0);
// Pressure controller
ControllerDeviceBaseClass pressureController = new ControllerDeviceBaseClass("PC-100");
pressureController.setTransmitter(pressureTransmitter);
pressureController.setControllerSetPoint(50.0); // 50 bara target
pressureController.setControllerParameters(1.0, 50.0, 0.0);
gasValve.setController(pressureController);
Flow Controller
// Volume flow transmitter
VolumeFlowTransmitter flowTransmitter = new VolumeFlowTransmitter("FT-100", feed);
flowTransmitter.setMeasuredPhase("gas");
flowTransmitter.setMinimumValue(0.0);
flowTransmitter.setMaximumValue(10000.0);
ControllerDeviceBaseClass flowController = new ControllerDeviceBaseClass("FC-100");
flowController.setTransmitter(flowTransmitter);
flowController.setControllerSetPoint(5000.0); // Target flow
flowController.setControllerParameters(0.1, 200.0, 0.0);
inletValve.setController(flowController);
Controller Tuning
// PID parameters
double Kp = 1.0; // Proportional gain
double Ti = 100.0; // Integral time (seconds)
double Td = 0.0; // Derivative time (seconds)
controller.setControllerParameters(Kp, Ti, Td);
// Action direction
controller.setReverseActing(true); // Increase output to decrease PV
controller.setReverseActing(false); // Increase output to increase PV
Pipeline Dynamics
Transient Multiphase Flow
The TransientPipe class provides drift-flux based transient simulation:
import neqsim.fluidmechanics.flowsystem.twophaseflowsystem.twophasepipeflowsystem.TwoPhasePipeFlowSystem;
// Create transient pipeline
TransientPipe pipeline = new TransientPipe("FL-100", feed);
pipeline.setLength(5000.0); // 5 km
pipeline.setDiameter(0.25);
pipeline.setNumberOfNodes(50);
pipeline.setElevationProfile(elevations); // Terrain effects
// Configure flow regime detection
pipeline.setFlowRegimeDetectionMethod("mechanistic");
// Run transient
pipeline.setCalculateSteadyState(false);
for (int step = 0; step < 600; step++) {
pipeline.runTransient(1.0, id);
// Track slugs
SlugTracker slugs = pipeline.getSlugTracker();
System.out.println("Active slugs: " + slugs.getSlugCount());
}
Slug Tracking
// Get slug statistics
SlugTracker tracker = pipeline.getSlugTracker();
int slugCount = tracker.getSlugCount();
double avgSlugLength = tracker.getAverageSlugLength();
double maxSlugVolume = tracker.getMaxSlugVolume();
double slugFrequency = tracker.getSlugFrequency();
System.out.printf("Slugs: %d, Avg length: %.1f m, Frequency: %.2f /min%n",
slugCount, avgSlugLength, slugFrequency * 60);
Vessel Depressurization
Blowdown Simulation
// Create vessel with initial conditions
SystemInterface vesselFluid = new SystemSrkEos(350.0, 100.0);
vesselFluid.addComponent("methane", 0.8);
vesselFluid.addComponent("ethane", 0.15);
vesselFluid.addComponent("propane", 0.05);
vesselFluid.setMixingRule("classic");
Tank vessel = new Tank("V-100", vesselFluid);
vessel.setVolume(50.0); // 50 m³
// Blowdown valve to atmosphere
ThrottlingValve blowdownValve = new ThrottlingValve("BDV", vessel.getOutletStream());
blowdownValve.setOutletPressure(1.5, "bara");
blowdownValve.setCv(50.0);
ProcessSystem blowdown = new ProcessSystem();
blowdown.add(vessel);
blowdown.add(blowdownValve);
// Initialize
blowdown.run();
vessel.setCalculateSteadyState(false);
// Run blowdown for 15 minutes
UUID id = UUID.randomUUID();
double dt = 0.5; // 0.5 second steps
ArrayList<double[]> results = new ArrayList<>();
for (double t = 0; t <= 900; t += dt) {
blowdown.runTransient(dt, id);
results.add(new double[] {
t,
vessel.getPressure(),
vessel.getTemperature() - 273.15, // °C
blowdownValve.getFlowRate("kg/hr")
});
}
// Report minimum temperature (for MDMT assessment)
double minTemp = results.stream()
.mapToDouble(r -> r[2])
.min()
.orElse(Double.NaN);
System.out.println("Minimum temperature: " + minTemp + " °C");
Fire Case Depressurization
// Add heat input for fire case
vessel.setHeatInput(1000000.0); // 1 MW fire load
for (double t = 0; t <= 900; t += dt) {
blowdown.runTransient(dt, id);
// Check for two-phase relief (wetted surface)
double liquidFraction = vessel.getLiquidVolumeFraction();
if (liquidFraction > 0) {
System.out.println("Two-phase relief at t=" + t);
}
}
Calculation Identifiers
Why IDs Matter
The calculation identifier (UUID) ensures all equipment in a process system is synchronized during transient runs:
UUID calcId = UUID.randomUUID();
// All equipment should have same ID after runTransient
process.runTransient(dt, calcId);
// Verify synchronization
for (ProcessEquipmentInterface eq : process.getUnitOperations()) {
if (!eq.getCalculationIdentifier().equals(calcId)) {
System.err.println("Equipment out of sync: " + eq.getName());
}
}
Detecting Stale States
// After transient step, check all equipment updated
UUID expectedId = process.getCalculationIdentifier();
boolean allSynced = true;
for (ProcessEquipmentInterface eq : process.getUnitOperations()) {
if (!eq.getCalculationIdentifier().equals(expectedId)) {
allSynced = false;
System.err.println("Stale: " + eq.getName());
}
}
if (!allSynced) {
// Reinitialize process
process.run();
}
Best Practices
1. Always Initialize with Steady-State
// CORRECT: Initialize first
process.run(); // Steady-state initialization
separator.setCalculateSteadyState(false);
process.runTransient(dt, id);
// WRONG: Skip initialization
separator.setCalculateSteadyState(false);
process.runTransient(dt, id); // May fail or give wrong results
2. Set Transient Mode on Dynamic Equipment Only
// Equipment that needs transient mode:
separator.setCalculateSteadyState(false); // Has holdup
tank.setCalculateSteadyState(false); // Has volume
pipeline.setCalculateSteadyState(false); // Has segments
// Equipment that typically stays steady-state:
// - Streams (boundary conditions)
// - Heat exchangers (fast thermal equilibrium)
// - Compressors (unless modeling inertia)
3. Match Controller Time Constants to Time Step
double dt = 1.0; // 1 second time step
// Controller integral time should be >> dt
double Ti = 100.0; // 100 seconds >> 1 second
// Avoid Ti close to dt (causes oscillation)
// Ti = 1.0 would be problematic with dt = 1.0
4. Log Key Variables for Debugging
try (PrintWriter log = new PrintWriter("transient.csv")) {
log.println("time,pressure,temperature,level,flow");
for (double t = 0; t < tEnd; t += dt) {
process.runTransient(dt, id);
log.printf("%.1f,%.2f,%.2f,%.3f,%.1f%n",
t,
separator.getPressure(),
separator.getTemperature("C"),
separator.getLiquidLevel(),
gasValve.getFlowRate("kg/hr"));
}
}
5. Use try-finally for Clean Shutdown
try {
process.run();
separator.setCalculateSteadyState(false);
for (int step = 0; step < 100; step++) {
process.runTransient(dt, id);
}
} finally {
// Reset to steady-state for future runs
separator.setCalculateSteadyState(true);
}
Examples
Example 1: Feed Rate Step Change
// Initialize at 1000 kg/hr
feed.setFlowRate(1000.0, "kg/hr");
process.run();
separator.setCalculateSteadyState(false);
// Step change to 1500 kg/hr
feed.setFlowRate(1500.0, "kg/hr");
// Observe response
for (int step = 0; step < 300; step++) {
process.runTransient(1.0, id);
System.out.printf("t=%d s, Level=%.3f m%n", step, separator.getLiquidLevel());
}
Example 2: Slug Arrival at Separator
See transient_slug_separator_control_example.md
Example 3: Compressor Startup
Compressor comp = new Compressor("K-100", feed);
comp.setDynamicSimulationEnabled(true);
comp.setRotorInertia(150.0);
// Start from standby
comp.setState(CompressorState.STANDBY);
process.run();
// Initiate startup
comp.startStartupSequence();
for (int step = 0; step < 600; step++) {
process.runTransient(0.5, id);
System.out.printf("t=%.1f s, Speed=%.0f RPM, State=%s%n",
step * 0.5,
comp.getSpeed(),
comp.getState());
if (comp.getState() == CompressorState.RUNNING) {
System.out.println("Startup complete at t=" + step * 0.5 + " s");
break;
}
}
Related Documentation
Core Transient Documentation
- Process Transient Simulation Guide - Control loop patterns
- Pipeline Transient Simulation - PipeBeggsAndBrills details
- Transient Multiphase Pipe - Drift-flux model
Equipment-Specific
- Compressor Dynamic Features - State machines, events
- Controllers - PID control for dynamics
- Separators - Level tracking
Interactive Notebooks (Google Colab)
These Colab notebooks provide hands-on dynamic simulation examples:
| Notebook | Description |
|---|---|
| Dynamic Simulation Basics | Introduction to transient simulation |
| Dynamic Compressor | Compressor dynamics and control |
| Single Component Dynamics | Single component transient behavior |
| Dynamic Separator | Separator level dynamics and control |
Local Examples
Process Logic
- Process Logic Framework - runTransient integration
- Integrated Workflow Guide - Transient scenario analysis