agentleFS
equinorequinor/neqsim/.github/skills/neqsim-dynamic-simulation/SKILL.md

neqsim-dynamic-simulation

Skill167 starsChanged 7 months agoApache-2.0View on GitHub

Dynamic simulation guidance for NeqSim. USE WHEN: running transient simulations, modeling startup/shutdown, tuning PID controllers, analyzing pressure/level dynamics, performing blowdown/depressurization, or setting up measurement devices and control loops. Covers runTransient, DynamicProcessHelper, controller tuning, and dynamic equipment configuration.

The file

Show its rules and text

We split this skill into the rules it gives an agent

35 rules. About 0 are common, 16 are distinctive, and 19 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈16

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 19

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Add transient behavior to the existing equipment class through its mode/configuration APIs.

    line 13
  2. Very specific:

    Do not create a separate DynamicHeatExchanger, DynamicCompressor or other unit operation solely for a different execution mode.

    line 14
  3. Very specific:

    Use AdaptiveTransientException reason (MINIMUM_STEP, ATTEMPT_LIMIT, or UNREPRESENTABLE_INTERVAL), attempted-trial count, attempted step, …

    line 102
  4. Distinctive:

    Only this new overload uses start-of-substep event timing and caps the interval at the earliest future event across all area schedulers.

    line 107
  5. Distinctive:

    Read the two-fluid model guide for unsupported combinations and qualification limits.

    line 175
  6. Very specific:

    After the run, use ControllerPerformanceMetrics.fromEventLog(LC100.getEventLog()) (or LC100.getPerformanceMetrics()) to score the tuning …

    line 327
  7. Distinctive:

    Put a gas pressure-control valve on the outlet with a lower declared downstream pressure, actuator lag and physical-step idempotency …

    line 338
  8. Distinctive:

    Check each component inventory, the fixed-volume/internal-energy flash residual, pressure and temperature bounds, held scan order, valve …

    line 343
  9. Very specific:

    Read getLastTransientEnergyBalanceReport() for consumed ordered enthalpy/heat terms, accepted-dt elapsed time, epoch, local VU defect and …

    line 363
  10. Distinctive:

    Keep response PASS, energy FAIL/INCOMPLETE and qualification HOLD separate.

    line 369
  11. Distinctive:

    Never reconstruct consumed inlet enthalpy from post-step ports after the mixer has been refreshed.

    line 376
  12. Very specific:

    Use registered native held instruments with enabled transient sampling and SafetyFunctionChannel.highTripFromMeasurement(...) or …

    line 382
  13. Distinctive:

    Use current held conditions and a separate explicit operator reopening, retaining each native trace epoch before reset.

    line 396
  14. Very specific:

    Use actual finite-stroke ESDValve, native ESDLogic/TripValveAction, and ParallelActionGroup where simultaneous valve commands are required.

    line 408
  15. Very specific:

    After canonical PID inputs/action configuration, definition IDs and evidence references are frozen, use …

    line 416
  16. Very specific:

    Use ControlsBenchmarkSuite when a request needs a deterministic regression check across NeqSim's canonical level, pressure, cascade, …

    line 472
  17. Distinctive:

    Use this application layer when the study needs stage coordination, startup/shutdown or trip states, hot/cold recycle split, operator …

    line 571
  18. Very specific:

    When TwoFluidPipe.setIncludeMassTransfer(true) is enabled, flash-driven transfer is phase resolved.

    line 598
  19. Very specific:

    Use HydrocarbonPoolTransfer.forHydrostaticPool(...) only for stationary or slowly draining stratified terrain pools.

    line 626
  20. Distinctive:

    Use this pattern when evaluating actions such as closing an outlet valve, opening a bypass, tripping a shutdown valve, or opening a …

    line 868
  21. Distinctive:

    Run and validate the steady-state base case.

    line 871
  22. Very specific:

    Run process.runTransient(dt) for controller and inventory dynamics, or use neqsim-depressurization-mdmt for dedicated blowdown/MDMT cases.

    line 874
  23. Distinctive:

    Before relying on a mixed transient flowsheet, inspect its state-ownership inventory separately from runtime activation and physics …

    line 907
  24. Very specific:

    All built-in process-element declarations of runTransient(double, UUID) are mapped, and CI rejects a new built-in override unless it is …

    line 919
  25. Very specific:

    Always run steady state first: Call process.run() before runTransient()

    line 968
  26. Distinctive:

    Timestep size: Start with 1.0 s, reduce if oscillating (0.1-0.5 s)

    line 969
  27. Very specific:

    Liquid-outlet level-controller direction: use setReverseActing(false) so level up drives the outlet valve further open.

    line 970
  28. Very specific:

    Separator dimensions: Must set setInternalDiameter() and setSeparatorLength() for meaningful level dynamics.

    line 972
  29. Distinctive:

    Physical-step UUIDs: never reuse one non-null UUID across multiple outer timesteps.

    line 975
  30. Very specific:

    Use AcousticPumpBoundary with upstream WaterHammerPipe.BoundaryType.PUMP for a declared single-liquid forward curve and actual …

    line 1104
  31. Distinctive:

    Follow the physical inputs, equations, public example and limits.

    line 1115
  32. Distinctive:

    Do not extrapolate clamped curves, turn stopped pumps into closed valves or claim a prescribed-speed adapter supplies shaft inertia, …

    line 1116
  33. Very specific:

    Read the acoustic model, accepted histories and public evidence and hand specialist hydraulic cases to neqsim-water-hammer.

    line 1129
  34. Very specific:

    Use the existing GeometryCalculator.StratifiedPressureMoments derived centre-referenced phase first moments [m3] and …

    line 1136
  35. Distinctive:

    Do not use it as proof of dry/wet fronts, terrain pools, refill or physical evaporation.

    line 1159

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Dynamic Simulation Guidance

Guide for transient/dynamic process simulation in NeqSim.

Extend the existing unit operation

Add transient behavior to the existing equipment class through its mode/configuration APIs. Do not create a separate DynamicHeatExchanger, DynamicCompressor or other unit operation solely for a different execution mode. Internal solver/state helpers are appropriate; a distinct physical/model abstraction requires a written rationale. Keep the same streams, ports, tags, design data, controllers and DEXPI/P&ID bindings. Preserve default steady behavior and test initialization, both mode transitions, cache invalidation, conservation, repeated-step identity, rollback and serialized continuation. For the heat exchanger automation workflow, configure the existing HeatExchanger and call setDynamicModelEnabled(true) before its initial run. See the coordinated workflow.

When to Use Dynamic Simulation

  • Startup and shutdown sequences
  • Controller tuning and loop analysis
  • Pressure relief / blowdown scenarios
  • Level and pressure dynamics
  • Compressor surge analysis
  • Pipeline transients (slug flow)
  • Emergency depressurization (EDP/ESD)
  • P&ID-derived valve actions where pressure, level, controller response, or inventory release changes with time

For valve-action studies that start from P&ID symbols and plant data, also load neqsim-pid-process-operations to define the process graph, valve semantics, historian tag mapping, and event schedule before running runTransient.

Dynamic Simulation Architecture

NeqSim dynamic simulation advances a ProcessSystem with runTransient() (using the configured timestep) or runTransient(double dt, UUID id) (using an explicit timestep and calculation identifier). Pass a finite, positive timestep. Process and model entry points reject zero, negative, NaN, and infinite values before any area or equipment state changes. Adaptive stepping rejects invalid requests rather than clamping them.

Each timestep:

  1. Flowsheet-wide settings are applied. Each setter unit receives exactly one transient call, applying its specification and advancing its own clock once.
  2. Simulation time advances, then due events and field inputs are applied.
  3. Equipment updates, including its thermodynamic calculations, run in insertion order or dependency-aware graph levels when parallel transient execution is enabled. Setter units are excluded from these explicit, semi-implicit, and parallel equipment passes. Steady-state algebraic equipment still evaluates on every requested pass, but its local clock advances once per non-null timestep calculation identifier. Reusing an identifier may refine a recycle without advancing physical time; use a new identifier for the next physical timestep.
  4. Standalone controllers run; controllers actually executed inside equipment retain their equipment-specific timing for compatibility. Execution is coalesced by object identity and the timestep calculation identifier. Merely attaching a controller to equipment that does not execute it must not suppress the standalone update; repeated standalone registration runs once per step. ControllerDeviceBaseClass also makes repeated calls with one identifier idempotent, including semi-implicit equipment passes. Custom controllers integrated by equipment must implement the same identifier contract through hasRunTransient(UUID).
  5. Measurement devices are sampled, alarms are evaluated, and history is stored.

A non-null UUID therefore identifies one physical timestep, not an entire outer time-marching simulation. Refinements of that physical step reuse the same physical-step UUID; the next accepted physical step must use a different UUID. For ordinary loops runTransient() is safe because it creates a fresh UUID each call. Deterministic safety/OTS workflows can use TransientStepIdentifier.deterministicPhysicalStep(scope, stepIndex).

The legacy two-argument ProcessSystem.runTransientAdaptive(dt, id) advances one full step and recommends a following timestep using a temperature-change heuristic. It does not reject/retry on a full-step versus two-half-step estimate. Its strict qualification blocker remains; do not infer a different contract from its name.

For opt-in numerical acceptance, both ProcessSystem and ProcessModel provide runTransientAdaptive(requestedDt, parentId, AdaptiveTransientOptions). This first profile requires concrete process/model owners, exactly aligned finite area clocks and no shared mutable participant across different areas. Use explicit minimum/maximum timestep, bounded attempts, integration order and a TransientErrorStateBinding for every unique rollback participant, including attached controllers. Each read-only serializable provider returns a nonempty vector with stable owner/variable IDs, explicit units, finite values, positive absolute tolerances and nonnegative relative tolerances. Numerical coverage must be complete and the schema/tolerances must remain fixed across all trial states. The caller remains responsible for auditing that the declared variables cover the relevant physical and control state.

The overload compares one full step with two half steps using maximum normalized error, restores rejected trials in place, and commits the actual two-half-step state. Distinct deterministic physical child UUIDs prevent idempotent controllers from skipping the second half. Use a new parent UUID on the next call; preserve result getAcceptedStepSeconds() versus getSuggestedNextStepSeconds() semantics. Execution/provider/schema failures propagate after rollback; they are not accepted as numerical progress. Exhausted bounds/attempts leave the step uncommitted. Use AdaptiveTransientException reason (MINIMUM_STEP, ATTEMPT_LIMIT, or UNREPRESENTABLE_INTERVAL), attempted-trial count, attempted step, normalized error and limiting-variable evidence for numerical failure reports. A NaN error marks an unavailable comparison; infinity marks an unbounded/overflowing error.

Only this new overload uses start-of-substep event timing and caps the interval at the earliest future event across all area schedulers. Shared due events execute before any area advances. Endpoint events remain pending until the following call, so they cannot alter the interval that precedes their trigger time. Event actions must have declared complete in-memory mutation scope; external effects are outside the rollback contract. Callbacks must terminate and avoid due-now cross-scheduler chains; the underlying scheduler has no callback budget. Newly inserted future events trigger bounded rollback/localization, while a leftover past event fails the interval. Keep exclusive ownership during adaptive execution.

Expect three physical evaluations per complete attempt plus snapshot/comparison and rejected-trial cost. Default/legacy paths gain no adaptive work. The synthetic quantitative tests establish the numerical acceptance/replay contract, while real process-control and safety studies still need method/parameter qualification, conservation/refinement evidence and explicit limits. Read the dynamic capability contract for the norm, provider contract, provenance, event and qualification boundaries.

Basic Dynamic Setup

import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.separator.Separator;
import neqsim.process.equipment.valve.ThrottlingValve;
import neqsim.process.controllerdevice.ControllerDeviceInterface;
import neqsim.process.controllerdevice.ControllerDeviceBaseClass;
import neqsim.process.measurementdevice.LevelTransmitter;
import neqsim.process.measurementdevice.PressureTransmitter;

// Build steady-state process first
SystemInterface fluid = new SystemSrkEos(273.15 + 25.0, 50.0);
fluid.addComponent("methane", 0.80);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.addComponent("n-pentane", 0.05);
fluid.setMixingRule("classic");

Stream feed = new Stream("feed", fluid);
feed.setFlowRate(10000.0, "kg/hr");

Separator sep = new Separator("HP Sep", feed);
sep.setInternalDiameter(2.0);  // m — for dynamic simulation, set directly for level dynamics
sep.setSeparatorLength(6.0);   // m — for design purposes, use SeparatorMechanicalDesign instead

ThrottlingValve gasValve = new ThrottlingValve("gas valve", sep.getGasOutStream());
gasValve.setOutletPressure(20.0, "bara");

ThrottlingValve liqValve = new ThrottlingValve("liq valve", sep.getLiquidOutStream());
liqValve.setOutletPressure(10.0, "bara");

ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(sep);
process.add(gasValve);
process.add(liqValve);

// Run steady state first
process.run();

TwoFluidPipe conservative thermal recovery

For blowdown or repressurization, the legacy post-step temperature update does not recover temperature from transported total energy. The experimental setUseConservativeEnergyRecovery(true) option must be selected before run(); it initializes named-component inventories automatically, including pure fluids. Select coupled pressure-momentum, Euler, energy enabled and slug tracking disabled. Read the two-fluid model guide for unsupported combinations and qualification limits. Inspect getLastConservativeEnergyReport() together with mass and component reports; the legacy sensible thermal report is null in this mode. A successful interval recovers common P/T from occupied volume and internal energy at each transported phase composition. It does not qualify rich-fluid evaporation rates or asset blowdown times. Regressions: TwoFluidPipeConservativeEnergyTest and ConservativeThermodynamicRecoveryTest. Keep issue #4595 open until the complete rich-phase-transition and refinement acceptance is demonstrated.

Adding Measurement Devices

// Pressure transmitter
PressureTransmitter PT100 = new PressureTransmitter("PT-100", sep);
PT100.setUnit("bara");
PT100.setMaximumValue(100.0);
PT100.setMinimumValue(0.0);
process.add(PT100);

// Level transmitter
LevelTransmitter LT100 = new LevelTransmitter("LT-100", sep);
LT100.setUnit("m");
process.add(LT100);

// Temperature transmitter
TemperatureTransmitter TT100 = new TemperatureTransmitter("TT-100", sep);
TT100.setUnit("C");
process.add(TT100);

// Flow transmitter
VolumeFlowTransmitter FT100 = new VolumeFlowTransmitter("FT-100", feed);
FT100.setUnit("kg/hr");
process.add(FT100);

Controller Configuration

PID Controller

// Level controller on liquid OUTLET valve — DIRECT acting (reverseActing = false):
// level up (error > 0) -> output up -> outlet valve opens -> level falls. Setting
// reverseActing(true) here inverts the sign and makes the loop unstable (runaway).
ControllerDeviceInterface LC100 = new ControllerDeviceBaseClass();
LC100.setControllerSetPoint(1.0);          // Target level = 1.0 m
LC100.setTransmitter(LT100);              // Controlled variable
LC100.setReverseActing(false);            // liquid-outlet level valve is direct acting
LC100.setControllerParameters(0.5, 100.0, 10.0); // Kp, Ti (s), Td (s)

// Attach controller to valve
liqValve.addController("LC-100", LC100);

// Pressure controller on gas valve
ControllerDeviceInterface PC100 = new ControllerDeviceBaseClass();
PC100.setControllerSetPoint(50.0);         // Target pressure = 50 bara
PC100.setTransmitter(PT100);
PC100.setReverseActing(false);            // Pressure up -> valve opens more
PC100.setControllerParameters(1.0, 50.0, 0.0);

gasValve.addController("PC-100", PC100);

Controller Tuning Guidelines

Loop TypeTypical KpTypical Ti (s)Typical Td (s)
Level (averaging)0.5-2.060-3000
Level (tight)2.0-5.030-600-10
Pressure (gas)0.5-2.020-1000-5
Flow0.3-1.05-300
Temperature0.5-2.060-60010-60

Controller Deadband (SP-PV)

ControllerDeviceBaseClass supports a native SP-PV deadband via setDeadBand(double) / getDeadBand(). While the absolute control error stays inside the band the controller output is frozen (holds the last valve position) and the integral term does not accumulate; default 0 disables it. The deadband is in the controller error unit (percent in the default percent mode, else the configured engineering unit). This is the standard DCS averaging- level deadband used to stop valve cycling.

levelController.setDeadBand(0.5);   // hold the valve while |PV - SP| <= 0.5 %

Beware the deadband limit cycle. On an integrating (level) process a deadband delays correction until the level reaches the band edge; the delayed correction then overshoots and the cycle repeats, giving a square-wave valve trace. Removing (or shrinking) the deadband is the usual fix. If the installed pip neqsim predates setDeadBand, emulate it by toggling controller mode each step: setMode(ControllerMode.MANUAL) while |PV%-SP%| <= deadband (holds output) and setMode(ControllerMode.AUTO) otherwise (bumpless resume) - this is numerically identical to the native deadband.

Dynamic level-loop recipe (get the sequence right)

A dynamic separator level loop only responds if the vessel is switched out of steady-state mode and the liquid outlet valve is direct acting. The exact, easy-to-get-wrong sequence is:

// 1. Build and solve the steady state first (sets inventory, flows, holdup).
process.run();

// 2. Switch the vessel (and its outlet valve) to dynamic mode. If this is left
//    on, the separator recomputes steady state every step and the level is
//    pinned at its default (0.5 fraction) no matter what the controller does.
sep.setCalculateSteadyState(false);
liqValve.setCalculateSteadyState(false);

// 3. Set the physical geometry and the starting liquid level (as a 0..1 fraction
//    of the vessel). Do this AFTER run() so it is not overwritten by steady state.
sep.setInternalDiameter(2.0);   // m — drives holdup volume / level dynamics
sep.setSeparatorLength(6.0);    // m
sep.setLiquidLevel(0.30);       // start at 30 %

// 4. Direct-acting level controller on the liquid OUTLET valve (see above).
LevelTransmitter LT100 = new LevelTransmitter("LT-100", sep);
LT100.setUnit("m");
ControllerDeviceInterface LC100 = new ControllerDeviceBaseClass();
LC100.setTransmitter(LT100);
LC100.setControllerSetPoint(0.30 * sep.getInternalDiameter()); // SP in the LT unit
LC100.setReverseActing(false);                 // liquid-outlet level valve = direct acting
LC100.setControllerParameters(1.0, 300.0, 0.0); // averaging level: loose Kp, long Ti, no Td
liqValve.addController("LC-100", LC100);

// 5. Advance the transient with a fresh physical-step identity each step.
for (long step = 0; step < 600; step++) {
  java.util.UUID physicalStepId =
      neqsim.process.dynamics.TransientStepIdentifier.deterministicPhysicalStep("level-loop", step);
  process.runTransient(1.0, physicalStepId);   // dt = 1 s
}

Gotchas:

  • Level pinned at 0.5 — the vessel is still in steady-state mode; call setCalculateSteadyState(false) on the separator (and its outlet valve).
  • Level runs away — the liquid-outlet level controller is reverse acting; it must be setReverseActing(false) (direct acting). A gas-outlet pressure valve is also direct acting (false); reverse acting is for cases where more output reduces the measured value (e.g. a controller manipulating an inlet/feed valve).
  • Set setLiquidLevel after run() — a steady-state solve resets the level, so set the starting level and geometry after the first run().
  • One UUID per physical step — do not create one UUID before the outer time loop and reuse it. Built-in controllers/equipment intentionally treat repeated calls with one UUID as refinements of the same physical timestep.
  • For an averaging level loop, use a loose Kp and long Ti (see the tuning table) and consider an SP-PV deadband only with care (see the limit-cycle note above).

After the run, use ControllerPerformanceMetrics.fromEventLog(LC100.getEventLog()) (or LC100.getPerformanceMetrics()) to score the tuning (IAE/ISE/ITAE, PV variability, valve travel and reversals, settling time) — see the KPI section below.

Dynamic pressure-inventory loop recipe

A pressure loop needs a real compressible inventory. A valve between two imposed-pressure streams verifies action and hydraulics, but it cannot demonstrate pressure regulation. For the canonical separator chain:

  1. Configure separator diameter, length and starting level, then disable steady-state calculation.
  2. Put a gas pressure-control valve on the outlet with a lower declared downstream pressure, actuator lag and physical-step idempotency enabled.
  3. Set the PCV embedded controller to manual when an external PID owns the command.
  4. Register a physical-step-sampled PressureTransmitter on the separator gas outlet and attach an engineering-unit, direct-acting PID to the PCV.
  5. Check each component inventory, the fixed-volume/internal-energy flash residual, pressure and temperature bounds, held scan order, valve travel and timestep sensitivity.

SeparatorPressureControlChainTest is the public reference implementation. Its SRK/classic wet-gas case uses a 2.2 m by 7.0 m separator, 55 bara initial pressure, 45 bara downstream boundary, 20 to 28 kg/s feed step, one-second scans, Kp = 6.0, Ti = 30 s and 4 s actuator lag. The 70 s regression limits peak pressure to 56.68 bara, pressure IAE to 84.6 bar-s and PCV travel to 21.38 percentage points; it also requires exact rollback/restart/repeatability and 1.0/0.5 s refinement. These values qualify only the synthetic numerical chain. Do not reuse the tuning or limits as a plant design basis.

Audit separator cumulative energy independently of response

Enable setTransientEnergyBalanceTrackingEnabled(true) on an exact concrete Separator after steady/inventory initialization. Read availability and coverage with getTransientEnergyBalanceTrackingStatus() and getTransientEnergyBalanceCoverageIssue(); null last report means no measured accepted update, not zero energy error. Reject uninstrumented subclasses, including ThreePhaseSeparator, as unsupported.

Read getLastTransientEnergyBalanceReport() for consumed ordered enthalpy/heat terms, accepted-dt elapsed time, epoch, local VU defect and final published inventory energy. Compare getAcceptedVuEnergyJ() with getPublishedEnergyJ(); do not confuse a locally accepted flash with complete-window energy closure. Apply an explicit frozen criterion through assessTransientEnergyBalance(limit); it gates maximum absolute cumulative debt using the reported initial-U scale. Keep response PASS, energy FAIL/INCOMPLETE and qualification HOLD separate. Retain the original criterion during timestep refinement and report raw J residuals, scale, extrema and sums of absolute local VU defects.

Require exact ordered external balance, on/off trajectory parity, reset/epoch, snapshot/restart and rejected-step evidence. Treat skipped steps, excluded nonzero liquid withdrawal and steady reevaluation as incomplete coverage. Never reconstruct consumed inlet enthalpy from post-step ports after the mixer has been refreshed. Read the native diagnostic contract for the bounded default-off API, scale and unsupported-mode limits.

Advanced ESD, voting and manual trip with actual inputs

Use registered native held instruments with enabled transient sampling and SafetyFunctionChannel.highTripFromMeasurement(...) or lowTripFromMeasurement(...). Derive channels from the actual executing process copy; references captured from the source graph are rejected. For an unlinked native PushButton, use manualTripFromPushButton(...). Legacy custom extractors have unverified input identity and are not a substitute for actual configured instrument readback. Duplicate channel objects and duplicate normalized channel tags are rejected. Distinct software tags do not establish hardware independence.

Preserve native semantics explicitly: response delay is continuous-demand qualification on held samples; qualified channels latch until reset; bypass keeps fixed m-out-of-n voting. Test nonoverlapping qualified pulses, pre-vote pause with no actuation, post-actuation resume, failed-child preservation and reset/retrip. Reset clears bookkeeping and does not reopen equipment or authorize operation. Use current held conditions and a separate explicit operator reopening, retaining each native trace epoch before reset.

For quantitative cases use event-exact timing and whole-window DynamicScenarioCriterion.EvaluationMode.MAINTAIN_BOUNDS for pressure envelopes and no-trip invariants. Set its positive deadline to the full intended event window and require start/end coverage. Default reach-by-deadline can pass an initial normal sample despite a later violation. Neither mode proves behavior between samples. Read actual accepted physical UUID and dt from ProcessSystem.getCalculationIdentifier() and getTimeStep(); rounding wall-time/nominal-dt fails on real short final alignment steps.

Use actual finite-stroke ESDValve, native ESDLogic/TripValveAction, and ParallelActionGroup where simultaneous valve commands are required. Legacy HIPPS immediate commands or stored closure-time metadata do not prove physical travel. Retain exact native target identities and supported trip-action readback; an energizing/opening/opaque action with the same target is not equivalent trip configuration. Command issuance, each actual valve closure, pressure response and failed/slow/inhibited negative controls need separate assertions.

After canonical PID inputs/action configuration, definition IDs and evidence references are frozen, use EngineeringSimulationRunner.runStrict(project) for direct typed project evidence. Do not attach unrelated same-name results. The public AdvancedV101SafetyStudy fixture uses actual V100/V101 inventories, three PI loops and 17 devices (16 held plus 1 discrete manual input); its 18 profiles plus five half-dt cases retain expected native FAIL results. Keep per-vessel energy coverage/debt and independent raw consumed-boundary audits separate from response. The frozen 1e-5 energy limit and qualification HOLD remain even if whole-slice residuals partly cancel. This is a proposed design study, not SIL, ISO/IEC or construction approval. See the closed-loop verification contract and public study resource README for the tested model and commands.

Loop-tuning KPIs (ControllerPerformanceMetrics)

ControllerPerformanceMetrics (neqsim.process.controllerdevice.ControllerPerformanceMetrics) computes the standard loop-tuning KPIs from a controller event log (or from raw time / PV / SP / output arrays) so tuning studies report consistent numbers without re-implementing the definitions. It is the preferred way to compare two PID tunings on the same disturbance.

Metrics: getIntegralAbsoluteError() (IAE), getIntegralSquaredError() (ISE), getIntegralTimeAbsoluteError() (ITAE, time referenced to the first sample), getProcessValueStandardDeviation() (PV variability), getPeakAbsoluteError(), getControllerOutputTravel() (total valve travel), getControllerOutputReversals() (valve direction reversals), and getSettlingTime() (time of the last sample outside the settling band, default 2 % of max(|SP|, 1)).

// After a runTransient loop with a logging controller:
ControllerPerformanceMetrics kpi = LC100.getPerformanceMetrics();          // from getEventLog()
// or, explicitly / with a custom settling band:
ControllerPerformanceMetrics kpi2 =
    ControllerPerformanceMetrics.fromEventLog(LC100.getEventLog(), 0.05);   // 5 % band

double iae = kpi.getIntegralAbsoluteError();
double valveTravel = kpi.getControllerOutputTravel();
int reversals = kpi.getControllerOutputReversals();
double settlingTime = kpi.getSettlingTime();
logger.info("IAE={} travel={} reversals={} settle={} s", iae, valveTravel, reversals, settlingTime);

// Or build directly from arrays (e.g. PV/OP pulled from a historian):
ControllerPerformanceMetrics kpi3 =
    ControllerPerformanceMetrics.fromArrays(time, pv, sp, op);
  • Integral criteria use trapezoidal integration over the sample intervals, so irregular time steps are handled correctly.
  • A lower IAE/ISE/ITAE means tighter regulation; lower valve travel and reversals means gentler actuator duty. Tuning trade-offs usually pit the two against each other (tighter control costs more valve movement).
  • resetEventLog() on the controller before the disturbance so the KPIs cover only the window of interest.

Canonical controls qualification suite

Use ControlsBenchmarkSuite when a request needs a deterministic regression check across NeqSim's canonical level, pressure, cascade, split-range, anti-surge, and compressor speed/recycle control structures:

import neqsim.process.controllerdevice.ControlsBenchmarkSuite;

ControlsBenchmarkSuite.Report report = ControlsBenchmarkSuite.runCanonicalSuite();
boolean qualified = report.isPassed();

Inspect report.getCases() for the uniform time/PV/SP/output traces, ControllerPerformanceMetrics, physical acceptance detail, and final errors. The report also exposes the dedicated AgentBenchmarkSuite verdict through getAgentBenchmarkReport(). See docs/benchmarks/controls_benchmark.md for equations, declared challenges, numerical gates, and current CI reference values.

The suite qualifies deterministic control execution and KPI reporting against transparent surrogate plants. It is not a substitute for field tuning, vendor compressor data, severe-slugging qualification, a safety-instrumented function, or commissioning validation.

Anti-Surge Control (dynamic)

AntiSurgeController (neqsim.process.controllerdevice.AntiSurgeController) is a purpose-built reverse-acting PI controller that reads the compressor getDistanceToSurge() and drives a recycle (spill-back) ThrottlingValve open when the margin falls below the set point, then closes it again on recovery.

import neqsim.process.controllerdevice.AntiSurgeController;

// distance to surge ~ (operating flow / surge flow - 1); only meaningful once a
// compressor chart with an active surge curve exists.
AntiSurgeController asc = new AntiSurgeController("anti-surge", compressor, recycleValve);
asc.setSurgeMarginSetPoint(0.10);   // protect a 10% distance-to-surge margin
asc.setProportionalGain(400.0);     // percent opening per unit margin error
asc.setIntegralTime(20.0);          // s
asc.setOpeningRange(0.0, 100.0);    // valve opening clamp (%) with anti-windup
asc.setActive(true);
recycleValve.addController("anti-surge", asc);

Control law each transient step: error = setPoint - distanceToSurge, integral += Kp/Ti * error * dt, opening = clamp(Kp*error + integral) with anti-windup; the controller applies the opening directly to the recycle valve.

Reproducible benchmark. AntiSurgeDynamicBenchmark (neqsim.process.util.scenario.AntiSurgeDynamicBenchmark) drives the real controller against a transparent first-order gas-path surrogate m_{k+1} = m_k - d*dt + a*(u/100)*dt (m = distance to surge, d = disturbance rate, a = recycle authority, u = valve opening %). It is deterministic and always converges, so it is the preferred way to verify or tune the control law:

import neqsim.process.util.scenario.AntiSurgeDynamicBenchmark;

AntiSurgeDynamicBenchmark bench = new AntiSurgeDynamicBenchmark();
bench.setInitialMargin(0.30);
bench.setDisturbanceRate(0.020);   // flow loss erodes the margin (/s)
bench.setRecycleAuthority(0.060);  // fully open recycle restores margin (/s)
bench.setTimeStep(1.0);
bench.setNumberOfSteps(120);
bench.getController().setSurgeMarginSetPoint(0.10);
bench.run(false);                   // open loop  -> surges (margin < 0)
bench.run(true);                    // closed loop -> margin held at set point
boolean safe = bench.isSurgeAvoided();

Critical gotchas when wiring a full dynamic recycle flowsheet:

  • A fixed-factor Splitter (setSplitFactors([0.97, 0.03])) pins the recycle fraction in dynamic mode, so the anti-surge valve has no authority over the actual recycle flow — the controller can hit 100% with no effect. Let the recycle flow be set by the valve (setCv/resistance), or use the steady-state anti-surge Calculator pattern instead.
  • Once the operating point crosses left of the surge line, getDistanceToSurge() clamps at -1.0 and the steady solver cannot climb back out; a flowsheet driven into deep surge will not self-heal even after the inlet is reopened. Apply gradual/ramped disturbances and keep the machine off deep surge.
  • Aggressive proportional gain can slam the recycle valve to its minimum opening, starve a stream, and trigger an SRK flash NaN (PhaseSrkEos:molarVolume ... NaN). Keep gains moderate and the valve off hard minimum.
  • To demonstrate or tune the control law cleanly, prefer AntiSurgeDynamicBenchmark (or a transparent gas-path surrogate) over a full recycle flowsheet that can stick in deep surge.

For coordinated compressor-train studies, use CompressorAntiSurgeApplication (neqsim.process.equipment.compressor) as the supervisory scan layer. Each StageApplication can bind directly to real NeqSim topology objects with bindTopology(process, compressor, hotRecycleValve, coldRecycleValve, recycleCooler, suctionMixer, hotRecycle, coldRecycle). A scan then writes hot/cold recycle valve openings and optional compressor speed runback to the real units, and runDynamicStep(scanInput, dt) advances the bound ProcessSystem with runTransient().

Use this application layer when the study needs stage coordination, startup/shutdown or trip states, hot/cold recycle split, operator diagnostics, commissioning checks, or speed runback in one executable dynamic model. Keep Recycle blocks algebraic unless they have explicit transient inventory support; valve, compressor, cooler, mixer, and volume-capable equipment should carry the dynamic response. The application layer reports NOT_CERTIFIED_FOR_PROTECTION and is for simulation/advisory studies, not a certified machinery-protection package.

For production-readiness evidence, pass the actual compressor cases and transient response limits to CompressorProtectionQualificationCalculation; it checks map margins, extrapolation, driver/start-up/rundown, response time, rotor separation, settle-out and vendor acceptance without certifying the protection system. For piping, pass ordered TwoFluidPipe, water-hammer, or externally governed solver samples to TransientPipingQualificationCalculation. That module checks acoustic resolution and line-pack balance before pressure, slug, velocity and stress limits, and deliberately does not treat a quasi-steady time series as distributed-transient evidence. Production mode requires the controlled context attribute distributedTransientModel=approved.

TwoFluidPipe Phase Appearance and Disappearance

Read the TwoFluidPipe evidence matrix before selecting a steady or transient configuration. It is the canonical distinction between implemented, numerically verified, experimentally qualified, failed, and unsupported behavior.

When TwoFluidPipe.setIncludeMassTransfer(true) is enabled, flash-driven transfer is phase resolved. In conservative-energy Euler mode on a dry gas/oil grid, use conserved component inventory differences from the local flashed targets, rather than assigning all new oil its final equilibrium composition. Carry opposing species transfers with their actual donor momentum and keep one total-energy owner. This remains an approximate relaxation, not predictive evaporation. Positive aqueous inventory/equilibrium retains the preceding source contract below. For a dry hydrocarbon finite-rate sensitivity, use HydrocarbonPoolTransfer only with conservative-energy Euler. Supply component $k_i$ values (m/s), evaporation enthalpies (J/kg), interface-width fraction and available heat (W/m); never infer them from the relaxation time. The closure uses the same equilibrium component target, component inventory, donor momentum and total-energy owners. Require zero-drive/area, donor/heat-limit, unseeded appearance, refinement and ledger gates. Label results parameterized screening until coefficients pass independent public calibration and validation. Do not call this elapsed pool-depletion, physical restart or pigging qualification. The 100 W/m finite-heat 780 s rich-gas cycle is numerical mesh/time evidence only. It crosses the cubic EOS diagnostic $V/B=1.75$ label transition by explicitly retaining the requested root while the positive-compressibility, Jacobian and energy-admissibility guards remain active. Do not clip composition or change the configured root to manufacture a restart result. For a conservative pressure-driven restart, select CONSTANT_PRESSURE, optionally set an explicit reservoir with setInletBoundaryFluid(...), and ramp from getInletFacePressure("Pa"). The solver reflashes the reservoir at the prescribed pressure and resolves signed phase speed from acoustic impedance; it no longer treats pressure traction as zero-advection refill. The isolated-reservoir route reuses one flash across internal stages at the same pressure; verify the performance safeguard with getPressureInletFlashCount(). A live connected stream remains uncached. The complete declared cycle closes mass, named-component and energy ledgers on 12/24 cells and 0.5/0.25 s intervals, but the time-refined trace retains one small unfiltered opening reversal. Treat this as numerical boundary/restart evidence, not independent surge, field, gelled-oil or repeated-cycle validation. Use HydrocarbonPoolTransfer.forHydrostaticPool(...) only for stationary or slowly draining stratified terrain pools. Use forStratifiedFlow(...) for a flowing circular-stratified interface: its local holdup chord preserves integrated area and component sources under subdivision of a uniform state, including nonuniform cells and finite heat limits. Independent per-cell horizontal puddles introduce cell-length dependence during a flowing restart. The two area assumptions are distinct models; retain the historical hydrostatic-cycle failure as evidence rather than claiming the flowing closure validates static-pool convergence. Inspect interface-area, equilibrium-appearance and unresolved-trace-area diagnostics. Representable long-cell trace wedges retain their leading-edge depth without a liquid floor; any reported fallback remains a numerical-resolution warning. Configure deliberate wall inventory with setInitialWallTemperatureProfile(...) before run() and retain the wall profile through every event/replay. In multilayer mode, its one temperature per cell initializes every radial layer uniformly; the layers evolve independently and a new steady run clears the old radial inventory. Mesh refinement must preserve physical terrain breakpoints and outlet length; a "last three cells" level-outlet rule compares different pipes. The wall-coupled 12/24-cell cycle fixes the final 1250 m at -333.5 m on both meshes and retains the 10% peak/final inventory gates. Consult its hydrocarbon-pool-transfer receipt for the executed result. Maximum single-cell interface area is extensive and is not a convergence target. A moving pig still needs separate distributed moving-volume, face-flux and pressure-work conservation and qualified inlet states. For a declared restart-opening window, use TwoFluidPipeReport.analyzePressureTrace(...) to report unfiltered extrema, peak-to-peak pressure, maximum sample rate, total/excess variation and reversal count with an explicit Pa gate. Preserve the sample interval and do not promote this diagnostic to a surge qualification without mesh/time and independent evidence. Condensation must use equilibrium hydrocarbon-liquid and aqueous-liquid mass contributions; never use the current cell water cut to identify a phase that is not yet present. For evaporation, withdraw from the actual oil and water conservative inventories and bound each withdrawal by phase mass / relaxation time. An absent phase must have exactly zero evaporation source.

Transferred momentum follows donor velocity. During condensation, gas loses mass and momentum at gas velocity and the receiving oil/water phases gain that momentum. During evaporation, each liquid loses momentum at its own velocity and gas receives their sum. Validate both invariants:

gas mass source + oil mass source + water mass source = 0
gas momentum source + oil momentum source + water momentum source = 0

For a phase-transition regression, cross a real SRK/CPA dew point in both directions without finite oil or water seeding. Check gas, oil, water, liquid, and total closure with TwoFluidMassBalanceReport; sweep nearby temperatures, refine time step and mesh, repeat the run, and compare rigorous flash with FlashTable. The flash table must retain the oil/aqueous liquid mass split. Record EOS, mixing rule, composition, absolute pressure, temperature, mass-transfer relaxation time, and units. With named-component transport, freeze component source compositions and their partial-enthalpy latent source from the same RHS-stage equilibrium state as the hydrodynamic phase source. A disappearing phase uses its conserved donor composition for the property state; a receiving phase still uses only the equilibrium flash composition. Require phase, total, named-component, interphase, bounded-fraction, and latent-inclusive energy closure.

The conservative Lagrangian slug/film + named-component + phase-transfer + thermal combination is currently verified only with single-stage Euler. Reject a multi-stage selection before mutation until intermediate phase appearance owns stage-local component inventories. Signed outlet backflow with named-component transport requires setOutletBoundaryFluid(externalFluid) before setAllowOutletPhaseBackflow(true); this explicitly owns reversed phase composition, density, and enthalpy. The last interior composition is not a valid inflow boundary. Treat the synthetic same-gas pressure-ramp regression as boundary-contract evidence only, not complete restart or evaporation-rate qualification. When the physical outlet is a gas takeoff above retained liquid, use setGasOnlyOutletTakeoff(true) with a constant-pressure outlet; do not zero liquid inventories or manufacture a separator mixture. The mode closes external oil/water mass, component and advected-energy flux while preserving their traction and internal drainage. None of these numerical contracts establishes equivalence with a commercial transient multiphase simulator.

A negative prescribed inlet mass rate is pipe outflow: phase split, composition and enthalpy come from the first finite-volume cell, not the external feed. Audit countercurrent boundary cases with getLastBoundaryPhaseMomentumFluxes() and getLastBoundaryPhasePressureForces() so donor advection and interior pressure traction remain distinct. These are boundary diagnostics, not a whole-domain momentum-conservation claim or physical restart qualification.

TwoFluidPipe Regime-Transition Continuation

Transient wall-friction, interfacial-friction, interfacial-area, and entrainment sources must consume the dimensionless normalized weights from FlowRegimeDetector.classify(...). Do not reintroduce detectFlowRegime(...) as a hard source switch: the steady hold-up and transient momentum paths would then see different closures at the same state. Keep stratified geometry active whenever a stratified weight is non-zero. Pure-regime endpoints must reproduce the original closure exactly.

Blend interfacial forces directly as the weighted sum of each regime's shear times area; separately averaging the two factors introduces spurious cross terms. Verify the actual momentum source and equal/opposite phase reactions in transition tests.

This continuation does not authorize changes to regime criteria, transition-band widths, hold-up correlations, or regime-specific friction models. It remains experimental until the public Tengesdal envelope, the liquid-rich 1,800 s inventory criterion, conservation, nonlinear convergence, nearby operating points, refinement, and runtime gates all pass. Continue to reject a completed time loop whose sticky coupled-solver diagnostics fire.

TwoFluidPipe Coupled Pressure-Momentum Gate

For a liquid-rich pressure outlet that physically permits phase fallback, the coupled path requires all four options. Keep the nonlinear controls explicit in reproducible studies:

pipe.setEnableInterfacialPressure(true);
pipe.setImplicitInterfacialPressureCoupling(true);
pipe.setEnableCoupledPressureMomentum(true);
pipe.setAllowOutletPhaseBackflow(true);
pipe.setCoupledPressureMomentumMaximumIterations(24); // default
pipe.setCoupledPressureMomentumRelativeVolumeTolerance(1.0e-7); // default

The previous budget of 12 stopped the public Tengesdal progress case near a 6e-7 relative cell-volume residual, above the 1e-7 gate. The current 16-section Test 3 probe completes 50/50 calls of 0.1 s and a 24-section refinement completes 100/100 calls of 0.05 s. Neither rejects a nonlinear substep, and phase and total discrete mass residuals remain below 1e-9.

A coupled call that exhausts its adaptive retries throws with accepted/requested elapsed time, residual/tolerance, iterations/cap, and limiter state. Do not catch that exception and advance the flowsheet clock. Successful completion still requires the sticky diagnostics to be inspected after the full window:

if (pipe.isTransientOutletBackflowClamped()
    || pipe.isTransientCoupledPressureMomentumFailureDetected()
    || pipe.isTransientCoupledPressureMomentumCorrectionLimited()
    || pipe.getTransientCoupledPressureMomentumRejectedSubsteps() > 0) {
  throw new IllegalStateException("TwoFluidPipe transient is not qualified");
}

These flags reset on the next steady run(). A pressure-limiter event is not itself a rejected substep because the volume residual can converge while the bounded correction is active, but it is mandatory qualification evidence. Use isCoupledPressureMomentumPressureCorrectionLimited() only for the latest correction; use the isTransient... form for the complete window. The current Tengesdal progress run still activates that flag and spans -18.55 to 6.88 kg/s liquid outlet versus the stored 0.375 to 4.03 kg/s comparison. It is therefore numerical-progress evidence only, not sustained-severe-slugging or commercial-parity evidence. Never tune a public closure to the commercial trace; validate the next boundary-coupling increment against the public Tengesdal experiment, conservation, nearby points, and mesh/time-step refinement.

The unsplit transactional callback exposes directional gas/oil/water face diagnostics: separate forward/reverse mass transport, donor advective momentum, pressure traction, physical cell lengths, and phase mass sources. The experimental directional-complementarity option augments the nonlinear vector with signed mass magnitude, advective momentum and a donor multiplier per internal phase face, conservatively replaces the free flux, and carries solved faces into prepared-step verification. Exactly absent donors use exact donor-presence and zero-momentum rows; do not add an inventory floor. This is not yet a qualified phase-transition path: the six unchanged 0.1 s lanes pass. The nonlinear path may now land exactly on a zero phase/face bound, project only outward search components at an already active exact bound, select the donor from the unmodified signed free flux between frozen Jacobians, and release an exact donor row when the selected donor is present. Rejected line-search trials use independent active-set copies. These are conservative transition mechanics, not a tolerance, inventory clip, or fitted donor smear. Exact disappearance and opposite-donor unit cases pass, but the directly affected nine unchanged five-second lanes still fail line search at 0.63125--1.83515625 s and the complete 15-case gate remains failed. Retain all five-second, 180 s and 600 s gates.

Make every cycle-count assertion feasible for its sampled window. A detector that reports N completed intervals needs N + 1 events, so the settled window must span at least N times the largest admissible cycle period, plus phase and boundary margin. Keep shorter runs as numerical characterization when they cannot contain the qualification cycle count; enforce the complete cycle gate in the long-duration qualification run.

Running Dynamic Simulation

// Timestep loop
double dt = 1.0;  // seconds
int nSteps = 3600; // 1 hour

// Storage for time history
double[] time = new double[nSteps];
double[] pressure = new double[nSteps];
double[] level = new double[nSteps];

for (int i = 0; i < nSteps; i++) {
    time[i] = i * dt;

    // Introduce disturbance at t = 300 s
    if (i == 300) {
        feed.setFlowRate(15000.0, "kg/hr");  // Step change +50%
    }

    process.runTransient(dt);

    pressure[i] = PT100.getMeasuredValue();
    level[i] = LT100.getMeasuredValue();
}

For large flowsheets with independent branches, enable process.setParallelTransientEnabled(true) and set the maximum worker count with process.setTransientThreadPoolSize(n). The per-process worker pool is created lazily and reused across timesteps; changing the worker count or disabling the option retires it. Execution follows cached process-graph levels, so upstream groups complete before downstream equipment is submitted while independent groups within a level remain parallel. A worker exception propagates to the caller with its runtime type and message, cancels later queued groups, prevents downstream dependency levels from being submitted, and skips controller, measurement/alarm/history, timestep-counter, and calculation-identifier commit phases. The process clock, due-event effects, and equipment state already mutated by a same-level sibling or earlier unit are not rolled back; treat that timestep as incomplete. If the caller is interrupted while waiting, NeqSim restores the interrupt status, cancels queued work without interrupting equipment already updating state, does not submit downstream levels, and likewise aborts the remaining timestep phases. Graph ordering does not define transient recycle convergence or transactional rollback, so keep parallel execution off for recycle loops and other implicit couplings until their transient contract is explicitly supported.

Python Dynamic Simulation

from neqsim import jneqsim
import numpy as np
import matplotlib.pyplot as plt

# Build process (same pattern as Java)
# ... create fluid, equipment, controllers ...

process.run()  # Steady state

dt = 1.0
n_steps = 3600
times = np.zeros(n_steps)
pressures = np.zeros(n_steps)
levels = np.zeros(n_steps)

for i in range(n_steps):
    times[i] = i * dt
    if i == 300:
        feed.setFlowRate(15000.0, "kg/hr")

    process.runTransient(dt)
    pressures[i] = PT100.getMeasuredValue()
    levels[i] = LT100.getMeasuredValue()

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))

ax1.plot(times / 60, pressures)
ax1.set_ylabel("Pressure (bara)")
ax1.set_xlabel("Time (min)")
ax1.grid(True)

ax2.plot(times / 60, levels)
ax2.set_ylabel("Level (m)")
ax2.set_xlabel("Time (min)")
ax2.grid(True)
plt.tight_layout()

P&ID Valve-Action Dynamic Studies

Use this pattern when evaluating actions such as closing an outlet valve, opening a bypass, tripping a shutdown valve, or opening a drain/vent:

  1. Run and validate the steady-state base case.
  2. Define an event schedule with action type, affected valve, start time, and ramp duration.
  3. Map each P&ID valve to the correct NeqSim role: control valve, boundary switch, check-valve direction constraint, or blowdown/flare path.
  4. Run process.runTransient(dt) for controller and inventory dynamics, or use neqsim-depressurization-mdmt for dedicated blowdown/MDMT cases.
  5. Save time series for pressure, level, temperature, valve position, flow, and any flare or vent stream.

Minimum result keys: max_pressure_bara, max_level_m, min_temperature_C, peak_flare_flow_kg_s, time_to_alarm_s, and time_to_new_steady_state_s.

Depressurization / Blowdown

// For vessel depressurization, use the safety/depressuring agent
// Key pattern: open a blowdown valve at t=0 and track P, T vs time

ThrottlingValve bdv = new ThrottlingValve("BDV", sep.getGasOutStream());
bdv.setOutletPressure(1.0, "bara");  // Vent to atmosphere
bdv.setCv(500.0);  // Valve Cv

// Controller: fully open at t=0
// Or use step change in valve opening

Transfer Function Blocks

For more advanced control logic:

import neqsim.process.controllerdevice.TransferFunctionBlock;

TransferFunctionBlock leadLag = new TransferFunctionBlock();
// Configure lead-lag, deadtime, filters as needed

Common Pitfalls

Before relying on a mixed transient flowsheet, inspect its state-ownership inventory separately from runtime activation and physics qualification:

neqsim.process.dynamics.DynamicCapabilityReport report =
    neqsim.process.dynamics.DynamicCapabilityReport.from(process);
if (!report.isFullyAudited()) {
  throw new IllegalStateException(report.getReviewItems().toString());
}
report.assertStrictTransientReady();

All built-in process-element declarations of runTransient(double, UUID) are mapped, and CI rejects a new built-in override unless it is classified or cites an explicit repository ADR. Custom/downstream overrides remain UNCLASSIFIED_DYNAMIC until reviewed. isFullyAudited() is an inventory signal only: it does not certify numerical stability, conservation, benchmark parity, controls performance, or safety suitability. In particular, PipeBeggsAndBrills has distributed profile state but no conservative line-pack/storage term; do not use its capability label to claim severe-slugging or liquid-rich transient validity. Use TwoFluidPipe only when the requested configuration fits a numerically verified or experimentally qualified evidence-matrix row. Route failed or unsupported severe-slugging, slug-statistics, and boundary-composition cases to a separately qualified model or controlled experimental study.

For closed or near-static terrain initialization, the interior pressure operator is analytically well balanced for a common mixture pressure: 90 flat/affine/curved, constant/smooth/alternating-density, 12--192-cell pure-gas/oil cases and a separate 45-case pure-water extension remain below 1e-10 m/s2 with exactly zero local mass and energy rates. Its signed energy correction is Q * deltaP with the mass donor and its diagnostic is transaction-safe. This is numerical fixed-point evidence only. Do not use it to qualify a two-density stationary pool, a moving interface, shutdown/restart, severe slugging, or pigging; those still require their separate evidence-matrix gates.

For a gas/one-liquid circular section, the default-off setStratifiedHydrostaticPressureMomentsEnabled(true) option adds the phase-density pressure moments to internal and external momentum traction and the matching Q * F / A total-energy work with the mass donor. Exact phase endpoints, closed-domain force telescoping, signed flow, rollback and default-off restoration are covered by StratifiedPressureMomentCouplingTest. Simultaneous positive oil and water and diameter steps reject explicitly. This completes an operator prerequisite only; do not route it as evidence of elapsed pool retention, shutdown/cooldown, restart, severe slugging, hydraulic pigging or predictive evaporation.

For phase appearance, retain both face fractions in GeometryCalculator.calculateStratifiedPressureMomentsFromHoldups(gas, liquid, diameter). The minority-area inversion uses relative accuracy and algebraic reflection; do not pass the legacy general holdup-to-depth approximation into pressure work, where its absolute area cutoff distorts traces. The enabled historical 12/24-cell ramp currently stops at 0.25 s on the unchanged face-volume closure guard. Keep the executable coupling rejection record attached; instantaneous operator identities do not qualify full elapsed transient operation.

For an explicitly reduced-order dry-gas pig calculation, inspect IdealGasPiggingModel. It advances immutable conservative upstream/downstream gas inventories with PigMotion, moving-volume work and the existing IdealGasReleaseModel bypass. Preserve the user-declared maximum time, donor-mass fraction and pig-travel fraction controls, and inspect total mass, combined energy, contact impact and timestep-refinement evidence. This is two well-mixed calorically perfect gas chambers, not distributed pipe dynamics. Launcher/receiver contact uses PigMotion.advanceToPosition; that API finds the first crossing on either side of a stop/reversal and retains the analytical work ledger, but the caller still owns the physical response to other position events. The Hendrix 62 m/52 mm dimensions do not qualify synthetic pressures, friction or bypass parameters. Do not infer TwoFluidPipe moving boundaries, liquid sweeping, receiver surge capacity, physical restart, severe slugging, pig trains or experimental/field validation.

  1. Always run steady state first: Call process.run() before runTransient()
  2. Timestep size: Start with 1.0 s, reduce if oscillating (0.1-0.5 s)
  3. Liquid-outlet level-controller direction: use setReverseActing(false) so level up drives the outlet valve further open. Reverse acting on this configuration drives the loop the wrong way.
  4. Controller windup: Large setpoint changes can cause integral windup
  5. Separator dimensions: Must set setInternalDiameter() and setSeparatorLength() for meaningful level dynamics. For dynamic simulation, set directly on the separator; for design purposes, configure via SeparatorMechanicalDesign (see neqsim-api-patterns skill)
  6. Measurement range: Set min/max on transmitters to match process range
  7. Enable dynamic (inventory) mode for level loops: after process.run() (steady), call setCalculateSteadyState(false) on the separator AND every valve, then separator.setLiquidLevel(startFraction), before runTransient. If steady-state mode is left on, the separator liquid level stays pinned at its default (0.5) and the level controller never acts. The valve Cv is auto-derived from the steady solve. A liquid-outlet level valve is setReverseActing(false) (level up -> valve opens); put a pressure controller on the gas-outlet valve so the vessel pressure is held and the level loop is isolated.
  8. Physical-step UUIDs: never reuse one non-null UUID across multiple outer timesteps. Reuse is reserved for refinement/evaluation of one physical step.

Pluggable Integrator Strategies

ProcessSystem accepts a pluggable IntegratorStrategy, but this hook does not by itself make the complete flowsheet a stiff vector ODE/DAE system. Implementations live in neqsim.process.dynamics:

StrategyClassNotes
Explicit EulerExplicitEulerIntegratorDefault; fast, conditionally stable
BDF-1 (Implicit Euler)BDFIntegratorExperimental local strategy. Newton + FD Jacobian; can fall back to explicit Euler when Newton diverges. Inspect lastStepFellBack() and do not treat fallback runs as qualified stiff/DAE evidence.
import neqsim.process.dynamics.BDFIntegrator;
import neqsim.process.dynamics.ExplicitEulerIntegrator;
import neqsim.process.dynamics.IntegratorStrategy;

BDFIntegrator bdf = new BDFIntegrator();
process.setIntegratorStrategy(bdf);
// process.setIntegratorStrategy(new ExplicitEulerIntegrator()); // explicit default
// process.setIntegratorStrategy(null);  // reset to default ExplicitEulerIntegrator
IntegratorStrategy current = process.getIntegratorStrategy();

For multi-area plants the strategy is propagated to every child area: plant.setIntegratorStrategy(new BDFIntegrator()).

A professional stiff-solver acceptance path must fail loudly or expose explicit fallback diagnostics; silent fallback is not an acceptable qualification result.

Event Scheduling (ESD, IOA, setpoint changes)

Time-stamped events (ESD trips, valve closures, setpoint ramps) are managed by EventScheduler in neqsim.process.dynamics. ProcessSystem.runTransient(dt, id) advances the process clock, then fires events with time <= currentTime before equipment runs.

import neqsim.process.dynamics.EventScheduler;

EventScheduler events = new EventScheduler();
events.scheduleEvent(120.0, "ESD trip", new Runnable() {
  public void run() { esdValve.setPercentOpen(0.0); }
});
events.scheduleEvent(300.0, "Setpoint ramp", new Runnable() {
  public void run() { pressureController.setControllerSetPoint(45.0); }
});
process.setEventScheduler(events);

for (int i = 0; i < nSteps; i++) {
  process.runTransient(dt);   // fresh physical-step ID; due events fire automatically
}

int fired = events.getFiredEvents().size();
int pending = events.getPendingEvents().size();

For event-aware/adaptive work, inspect event state without mutating it and checkpoint scheduler bookkeeping explicitly:

EventScheduler.Snapshot eventState = events.snapshot();
double nextEventTime = events.getNextEventTime();
java.util.List<EventScheduler.ScheduledEvent> due = events.getDueEvents(process.getTime());

// If a trial is rejected, restore pending/fired membership.
events.restore(eventState);

restore(...) restores only scheduler pending/fired membership. It cannot undo an already executed Runnable side effect. A rejected trial must defer external actions until acceptance or restore every object those actions mutate as part of the same transaction. This is required before safety/OTS event replay can be called transactional.

For multi-area plants install the scheduler once on the ProcessModel; it is propagated to every child area, and plant.runTransient(dt, id) advances all areas:

plant.setEventScheduler(events);
plant.runTransient(dt, java.util.UUID.randomUUID());

Note: EventScheduler is declared transient on ProcessSystem because event Runnable payloads (lambdas, anonymous classes) are usually not serializable. Re-install the scheduler after deserialising a saved process. EventScheduler.Snapshot itself can be serialized when its event actions are serializable, which is useful for explicit checkpoint/restart coordination.

New Measurement Devices (v3.11)

Three new measurement devices in neqsim.process.measurementdevice complement the existing PT/TT/LT/FT family:

ClassReadsUnit
DifferentialPressureTransmitter(name, high, low)pHigh - pLow across two streamsbar
CompositionAnalyzer(name, stream, component, phase)Mole fraction; phase OVERALL / GAS / LIQUIDmole/mole
FlowRatioMeter(name, num, den, basis)Flow ratio; basis MASS / MOLE / VOLUMEdimensionless
import neqsim.process.measurementdevice.DifferentialPressureTransmitter;
import neqsim.process.measurementdevice.CompositionAnalyzer;
import neqsim.process.measurementdevice.FlowRatioMeter;

DifferentialPressureTransmitter dpdt = new DifferentialPressureTransmitter("dPT-1", upstream, downstream);
CompositionAnalyzer ax = new CompositionAnalyzer("AX-1", sweetGas, "methane",
    CompositionAnalyzer.AnalyzerPhase.GAS);
FlowRatioMeter rxn = new FlowRatioMeter("FR-1", recycleStream, feedStream,
    FlowRatioMeter.FlowBasis.MASS);

Use one physical pressure-, temperature- or flow-transmitter sample per control scan

For qualified controller or alarm studies, explicitly enable physical-step sampling on the concrete PressureTransmitter, TemperatureTransmitter or VolumeFlowTransmitter and register it in the ProcessSystem. The process samples once after events and field inputs and before equipment/controllers; controller, history and alarm consumers share that held value. For a flow loop, use for example FT100.setUnit("kg/hr") followed by FT100.setTransientSamplingEnabled(true). Declare the physical scan interval, configured phase/unit, filter time constant, integer sample delay, noise basis and any sensor-fault rate. Flow conversions to kg/hr, m^3/hr, Nm^3/day and Sm^3/day are frozen at the sample instant. Direct pressure/temperature reads retain the legacy read-driven modifier path; direct flow reads remain raw. Treat a stuck or biased FT as a study failure mode, not an independent safeguard. Vendor dynamics, alarm/trip integrity, SIL, voting and independence remain external evidence.

Safety Shutdown forward acoustic pump boundary

Use AcousticPumpBoundary with upstream WaterHammerPipe.BoundaryType.PUMP for a declared single-liquid forward curve and actual positive-speed table. Its constructor fits copied reference data through existing PumpChart, checks monotonicity/domain/units and preserves Java restart state. Configure before run(); changing the boundary requires explicit reinitialization. Actual-speed and valve-travel knots use the same pipe clock. Use an explicit empty eventSchedule for pump-only WaterHammerStudy JSON. Study output includes aligned speed/inlet-flow/inlet-pressure/hydraulic-work histories. Unsupported curve transitions return an error with last accepted state and internal outlet history, never a successful truncated trip.

Follow the physical inputs, equations, public example and limits. Do not extrapolate clamped curves, turn stopped pumps into closed valves or claim a prescribed-speed adapter supplies shaft inertia, four-quadrant operation, check-valve disc/contact slam, NPSH/cavity physics, global rollback or integrated Safety Shutdown qualification. Existing canonical coordinator and ESD APIs own those integrations; do not create another scheduler or pressure-flow architecture.

Safety Shutdown ideal acoustic lift/contact handoff

The experimental AcousticLiftCheckValve couples declared translating mechanics and signed jet/swept flow inside existing WaterHammerPipe substeps. Numerical inelastic contact and local Java restart do not qualify continuous impact/grazing timing, a full pump trip or global coordinator rollback. Keep the existing physical-step/event identities and ESD chain; no parallel scheduler or solver. Read the acoustic model, accepted histories and public evidence and hand specialist hydraulic cases to neqsim-water-hammer. Asset/vendor, phase-change, structural and integrated Safety Shutdown remain NOT_EVALUATED.

Cross-sectional gravitational energy budget

Use the existing GeometryCalculator.StratifiedPressureMoments derived centre-referenced phase first moments [m3] and getGravityPotentialEnergyPerLength(gasDensity, liquidDensity, normalGravity) [J/m] for an explicit static normal-gravity budget. No solver state or serialized field is added. See the executed analytical/high-precision energy record. This does not resolve the documented dry/wet pressure impulse or qualify dynamic pools, inlet refill or hydrocarbon evaporation; those still require conservative phase-front transport/work and heat/mass transfer.

Optional Rusanov phase-flux contract

The default remains AUSM+. AUSMPlusFluxCalculator.calcRusanovFlux is an explicit local query, and TwoFluidPipe.setRusanovInternalFluxEnabled(true) selects the qualified two-donor operator on independent gas/oil/water internal faces after setEnableWaterOilSlip(true). Its dissipated energy is alpha * (rho * H - p) with total specific enthalpy; exact absence ignores unused properties and positive traces retain the same formula. SRK gas/oil controls distinguish EOS-volume internal energy from physical-density mechanical energy using explicit pressure-volume work; do not mix these API conventions in a reference. See the executed conservative-flux evidence. Forward and reverse donor streams remain separate through Runge-Kutta staging, pressure work and transactional component transport; their difference must reproduce the hydrodynamic net. The combined-liquid split, pressure interpolation, reconstructed slug faces and one-direction unsplit complementarity route reject this option. Apply a wave-speed Courant bound and inspect component, phase, total-energy and rollback reports. Do not use it as proof of dry/wet fronts, terrain pools, refill or physical evaporation.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.