Skip to the content.

Comprehensive documentation for control valves in NeqSim process simulation.

Table of Contents


Overview

Location: neqsim.process.equipment.valve

NeqSim provides specialized control valves for process automation and safety systems:

Class Description Typical Application
CheckValve Non-return valve Pump/compressor protection
LevelControlValve Level regulation Separator liquid discharge
PressureControlValve Pressure regulation Gas pressure letdown
ESDValve Emergency shutdown Process isolation
PSDValve Process shutdown Overpressure protection

LevelControlValve and PressureControlValve extend ThrottlingValve through ControlValve and add an embedded incremental proportional command. Safety-valve subclasses have separate qualification limits described below.


Check Valve

A check valve (non-return valve) prevents reverse flow automatically based on differential pressure.

Features

Applications

Java Example

import neqsim.process.equipment.valve.CheckValve;

// Create check valve on pump discharge
CheckValve checkValve = new CheckValve("CV-101", pumpDischargeStream);
checkValve.setCrackingPressure(0.2);  // Opens at 0.2 bar differential
checkValve.setCv(250.0);              // Flow coefficient when fully open
checkValve.run();

// Check valve state
if (checkValve.isOpen()) {
    System.out.println("Forward flow - valve open");
    double flowRate = checkValve.getOutletStream().getFlowRate("kg/hr");
} else {
    System.out.println("Reverse flow blocked - valve closed");
}

Python Example

from neqsim import jneqsim

CheckValve = jneqsim.process.equipment.valve.CheckValve

# Create check valve
check_valve = CheckValve("CV-101", pump_discharge)
check_valve.setCrackingPressure(0.2)  # 0.2 bar
check_valve.setCv(250.0)
check_valve.run()

print(f"Valve open: {check_valve.isOpen()}")
print(f"Cracking pressure: {check_valve.getCrackingPressure()} bar")

Key Methods

Method Description
setCrackingPressure(double) Set minimum ΔP to open valve (bar)
getCrackingPressure() Get cracking pressure
isOpen() Check if valve is currently open

Level Control Valve

A level control valve (LCV) modulates opening to maintain vessel level at setpoint.

Features

Control Actions

Action Description Use Case
DIRECT Increasing measured level increases opening and outflow Liquid discharge (most common)
REVERSE Increasing measured level decreases opening Process-specific inverse configurations

Java Example

import neqsim.process.equipment.valve.LevelControlValve;
import neqsim.process.equipment.valve.LevelControlValve.ControlAction;

// Create separator with liquid outlet
Separator separator = new Separator("V-101", feedStream);
Stream liquidOut = separator.getLiquidOutStream();

// Create level control valve
LevelControlValve lcv = new LevelControlValve("LCV-101", liquidOut);
lcv.setLevelSetpoint(50.0);          // Control to 50% level
lcv.setControllerGain(3.0);          // Proportional gain
lcv.setControlAction(ControlAction.DIRECT);
lcv.setMeasuredLevel(45.0);          // Current level from transmitter
lcv.setCv(150.0);
lcv.run();

System.out.println("Valve opening: " + lcv.getPercentValveOpening() + "%");
System.out.println("Level error: " + lcv.getControlError() + "%");

Python Example

from neqsim import jneqsim

LevelControlValve = jneqsim.process.equipment.valve.LevelControlValve

# Create level control valve on separator liquid outlet
lcv = LevelControlValve("LCV-101", separator.getLiquidOutStream())
lcv.setLevelSetpoint(50.0)       # 50% level setpoint
lcv.setControllerGain(3.0)       # Proportional gain
lcv.setMeasuredLevel(45.0)       # Current measured level
lcv.setCv(150.0)
lcv.run()

print(f"Valve opening: {lcv.getPercentValveOpening()}%")
print(f"Control error: {lcv.getControlError()}%")

Key Methods

Method Description
setLevelSetpoint(double) Set target level (0-100%)
setMeasuredLevel(double) Update measured level from transmitter
setControllerGain(double) Set proportional gain (1-10 typical)
setControlAction(ControlAction) DIRECT or REVERSE
getControlError() Get setpoint - measured level
setFailSafePosition(double) Position on loss of signal (%)

Pressure Control Valve

A pressure control valve (PCV) modulates opening to maintain pressure at setpoint.

Features

Control Modes

Mode Description Application
DOWNSTREAM Control outlet pressure Gas letdown stations
UPSTREAM Control inlet pressure Back-pressure control
DIFFERENTIAL Control ΔP across valve Flow control

Java Example

import neqsim.process.equipment.valve.PressureControlValve;
import neqsim.process.equipment.valve.PressureControlValve.ControlMode;

// Create pressure control valve
PressureControlValve pcv = new PressureControlValve("PCV-101", hpGasStream);
pcv.setPressureSetpoint(25.0);       // Control to 25 bara downstream
pcv.setControlMode(ControlMode.DOWNSTREAM);
pcv.setControllerGain(5.0);          // Proportional gain
pcv.setCv(300.0);
pcv.run();

System.out.println("Valve opening: " + pcv.getPercentValveOpening() + "%");
System.out.println("Outlet pressure: " + pcv.getOutletStream().getPressure("bara") + " bara");
System.out.println("Control error: " + pcv.getControlError() + " bar");

Python Example

from neqsim import jneqsim

PressureControlValve = jneqsim.process.equipment.valve.PressureControlValve

# Create pressure control valve for gas letdown
pcv = PressureControlValve("PCV-101", hp_gas_stream)
pcv.setPressureSetpoint(25.0)  # 25 bara target
pcv.setControllerGain(5.0)
pcv.setCv(300.0)
pcv.run()

print(f"Outlet pressure: {pcv.getOutletStream().getPressure('bara'):.1f} bara")

Key Methods

Method Description
setPressureSetpoint(double) Set target pressure (bara)
setControlMode(ControlMode) DOWNSTREAM, UPSTREAM, DIFFERENTIAL
setControllerGain(double) Proportional gain (1-10 typical)
getProcessVariable() Get measured pressure
getControlError() Get setpoint - PV
setAutoMode(boolean) Enable/disable automatic control

ESD Valve

An ESD valve (Emergency Shutdown Valve / Isolation Valve XV) is a fail-closed valve for emergency isolation.

Features

Design Philosophy

State Description
Energized Valve is open (normal operation)
De-energized Valve closes (emergency or loss of power)

Java Example

import neqsim.process.equipment.valve.ESDValve;
import java.util.UUID;

// Create ESD inlet valve (normally open)
ESDValve esdValve = new ESDValve("ESD-XV-101", feedStream);
esdValve.setStrokeTime(10.0);  // 10 seconds to close
esdValve.setCv(500.0);         // Large Cv for minimal ΔP when open

// Normal operation - valve open
esdValve.energize();
esdValve.run();
System.out.println("Normal: Valve " + (esdValve.isEnergized() ? "OPEN" : "CLOSED"));

// Emergency shutdown
esdValve.deEnergize();  // Triggers closure

// Dynamic simulation of closure
double dt = 0.5;  // 0.5 second time steps
for (double t = 0; t <= 12.0; t += dt) {
    esdValve.runTransient(dt, UUID.randomUUID());
    System.out.printf("t=%.1f s: Opening = %.1f%%%n", 
        t, esdValve.getPercentValveOpening());
}

Integration with ESD Controller

// ESD controller monitors process conditions
if (pressure > highPressureSetpoint || fireDetected || manualESDActivated) {
    esdValve.deEnergize();  // Initiate emergency closure
}

Python Example

from neqsim import jneqsim
import uuid

ESDValve = jneqsim.process.equipment.valve.ESDValve

# Create ESD valve
esd_valve = ESDValve("ESD-XV-101", feed_stream)
esd_valve.setStrokeTime(10.0)  # 10 seconds closure
esd_valve.setCv(500.0)

# Normal operation
esd_valve.energize()
esd_valve.run()

# Emergency shutdown
esd_valve.deEnergize()

# Simulate closure
dt = 0.5
for i in range(25):
    esd_valve.runTransient(dt, uuid.uuid4())
    print(f"Opening: {esd_valve.getPercentValveOpening():.1f}%")

Key Methods

Method Description
energize() Power actuator - allows valve to open
deEnergize() Remove power - initiates closure
isEnergized() Check energization state
setStrokeTime(double) Set closure time (seconds)
getStrokeTime() Get closure time
setFailSafePosition(double) Position on de-energize (0=closed)
startPartialStrokeTest() Initiate PST
hasTripCompleted() Check if closure is complete

PSD Valve

A PSD valve (Process Shutdown Valve) automatically closes on high-high (HIHI) pressure alarm.

Features

Java Example

import neqsim.process.equipment.valve.PSDValve;
import neqsim.process.measurementdevice.PressureTransmitter;
import neqsim.process.alarm.AlarmConfig;

// Create pressure transmitter with alarm configuration
PressureTransmitter PT = new PressureTransmitter("PT-101", separatorInlet);
PT.setAlarmConfig(AlarmConfig.builder()
    .highHighLimit(55.0)
    .deadband(1.0)
    .delay(0.5)
    .unit("bara")
    .build());

// Create PSD valve linked to transmitter
PSDValve psdValve = new PSDValve("PSD-101", feedStream);
psdValve.linkToPressureTransmitter(PT);
psdValve.setClosureTime(2.0);  // 2 seconds fast closure

// In dynamic simulation loop
for (int i = 0; i < 100; i++) {
    process.runTransient(0.1, UUID.randomUUID());
    
    if (psdValve.hasTripped()) {
        System.out.println("PSD valve tripped on HIHI alarm!");
        break;
    }
}

// Reset after alarm clears (operator action)
psdValve.reset();

Python Example

from neqsim import jneqsim

PSDValve = jneqsim.process.equipment.valve.PSDValve

# Create PSD valve
psd_valve = PSDValve("PSD-101", feed_stream)
psd_valve.linkToPressureTransmitter(pressure_transmitter)
psd_valve.setClosureTime(2.0)  # Fast closure

# Check trip status
if psd_valve.hasTripped():
    print("Valve tripped on HIHI!")
    # After condition clears
    psd_valve.reset()

Key Methods

Method Description
linkToPressureTransmitter(MeasurementDeviceInterface) Connect to PT
setClosureTime(double) Set closure time (seconds)
hasTripped() Check if valve has tripped
reset() Reset trip state (requires operator action)
setTripEnabled(boolean) Enable/disable automatic trip
getPressureTransmitter() Get linked transmitter

Dynamic Simulation

ThrottlingValve, ControlValve, LevelControlValve, PressureControlValve, ESDValve, and BlowdownValve have qualified transient paths. Other subclasses must be audited individually; inheriting from a qualified valve does not by itself establish complete dynamic or transaction coverage.

Valve Travel Dynamics

// Configure valve travel times
valve.setOpeningTravelTime(5.0);   // 5 seconds to fully open
valve.setClosingTravelTime(3.0);   // 3 seconds to fully close

// Simulate opening
valve.setTargetPercentValveOpening(100.0);  // Command full open
for (double t = 0; t <= 6.0; t += 0.1) {
    valve.runTransient(0.1, UUID.randomUUID());
    System.out.printf("t=%.1f: Actual opening = %.1f%%%n", 
        t, valve.getPercentValveOpening());
}

Transactional actuator steps

ThrottlingValve, the field-free ControlValve specialization, LevelControlValve, PressureControlValve, ESDValve, and BlowdownValve participate in ProcessSystem transient step transactions. Their snapshots retain the actual and requested opening, travel model, deadband, stiction and hysteresis memory, flow and thermodynamic cache, outlet-stream state, physical-step identifier, embedded LCV/PCV setpoint, measurement, mode, gain, error and scan identity, and the ESD/blowdown state machine. Attached base PID controllers remain separate transaction participants; the process transaction validates and restores both objects without replacing either one.

Rollback also restores the active sizing method’s choked-flow switch and $x_T$ factor in place. Keep the mechanical design, sizing method, and other sizing configuration fixed during a trial. Every attached controller must be independently enumerated; a duplicate controller tag that hides an attached controller blocks transaction coverage. Subclasses of ESDValve and BlowdownValve must extend the snapshot and explicitly qualify their own state before participating.

Physical-step idempotency is explicit and default-off for legacy compatibility:

valve.setPhysicalStepIdempotencyEnabled(true);

When enabled, one non-null physical-step UUID advances actuator state at most once. Repeating a hydraulic calculation with the same UUID can update flow state but cannot apply travel, trip time, embedded control command, blowdown time, or trip time twice. Rejected full-step/two-half-step trials restore the control, actuator, and safety state before retry. When disabled, legacy loops that reuse one UUID across multiple physical timesteps continue to advance on every call.

For FIRST_ORDER_LAG, the implemented step is the exact constant-command response

\[x_{n+1}=x_n+\left(1-e^{-\Delta t/\tau}\right)(u-x_n).\]

The regression benchmark uses $x_0=20\%$, $u=80\%$, and $\tau=4\ \mathrm{s}$; one-second and 0.125-second steps give the same eight-second endpoint to $10^{-10}$ percentage points. Separate tests qualify asymmetric rate limits, deadband, breakaway stiction, reversal hysteresis, fail-close ESD travel, commanded blowdown opening, rollback/replay, and controller-chain coverage.

Other ThrottlingValve subclasses currently fail the transaction coverage audit until they extend the snapshot for their own mutable state. This explicitly includes HIPPSValve and PSDValve; their legacy non-transactional execution remains available. Transaction coverage establishes deterministic modeled response only. It does not establish valve sizing, actuator vendor qualification, SIL, voting, independence, proof-test interval, or suitability for a project safety function.

Embedded LCV/PCV scan contract and limits

For a dynamic LCV scan, the caller first publishes the measured level with setMeasuredLevel(). The valve then computes the incremental command, applies actuator travel and nonlinearities, and solves the pressure-flow relation. A direct-acting discharge LCV uses

\[u_{n+1}=u_n-K_p(SP-PV).\]

so a measured level above setpoint opens the valve. The valve does not discover a separator or level transmitter implicitly; the tagged study model must wire and schedule that measurement.

A dynamic PCV samples the inlet/outlet pressure state from the preceding process scan, computes its command, and then runs the actuator and hydraulics once. This is an explicit one-scan measurement delay. Downstream mode opens for pressure below setpoint; upstream and differential modes close for pressure below setpoint. When outlet pressure is imposed with setOutletPressure(), it is an algebraic boundary: changing opening changes flow but cannot make the imposed pressure respond. A closed pressure-control study therefore needs a qualified pressure inventory downstream or upstream. An attached external controller has final authority in the base actuator scan; do not combine it with the embedded automatic command unless that ownership is intentional.

The regression contract checks documented command direction against actual valve flow, exact first-order actuator response, one control update per UUID, mass closure, in-place rollback/replay, complete transaction coverage, and Java restart equivalence. These tests qualify modeled response; they do not supply transmitter filtering/delay, fail-action actuation, controller tuning, vendor rangeability, or project safety-credit evidence.

Level Control Dynamic Response

// Simulate level disturbance
double level = 50.0;
double dt = 1.0;

for (int i = 0; i < 100; i++) {
    // Disturbance at t=20s
    if (i == 20) level = 70.0;
    
    lcv.setMeasuredLevel(level);
    lcv.runTransient(dt, UUID.randomUUID());
    
    // Level responds to valve opening
    double flowOut = lcv.getOutletStream().getFlowRate("kg/hr");
    level = level - (flowOut - inflow) * dt / vesselVolume;
    
    System.out.printf("t=%d: Level=%.1f%%, Opening=%.1f%%%n", 
        i, level, lcv.getPercentValveOpening());
}

API Reference

Common Base Methods (from ThrottlingValve)

Method Description
setCv(double, String) Set flow coefficient (“US” or “SI”)
getCv(String) Get flow coefficient
setOutletPressure(double, String) Set outlet pressure
setPercentValveOpening(double) Set valve position (0-100%)
getPercentValveOpening() Get commanded position
run() Execute steady-state calculation
runTransient(double dt, UUID id) Execute dynamic step

Valve Sizing

// Size valve for service
valve.setCv(calculateRequiredCv(flow, dP, fluid));
valve.setValveCharacteristic("equal_percentage");

// Check valve selection
double maxCv = valve.getCv("US");
double actualCv = valve.getActualCv();  // At current opening
double rangeability = maxCv / actualCv;