Skip to main content

neqsim-dynamic-simulation

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.

Informations de source

Dépôt
equinor/neqsim
Dernière activité de la source
16 octobre 2026 à 12:56
Langue détectée de SKILL.md
anglais
Étoiles
156
Forks
73

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
neqsim-dynamic-simulation
description
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.
last_verified
2026-09-07
# Dynamic Simulation Guidance Guide for transient/dynamic process simulation in NeqSim. ## 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)`. `ProcessSystem.runTransientAdaptive(...)` is **not yet a transactional adaptive integrator**. The current implementation validates the request, advances trial state directly, estimates a temperature-change heuristic, and chooses a subsequent timestep. It does not yet provide rejected-step process/event/control rollback or a full-step versus two-half-step error estimate. Do not use it as evidence of qualified stiff/adaptive professional dynamics until the #2911 transactional-step work is complete. ## Basic Dynamic Setup ```java 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(); ``` ## Adding Measurement Devices ```java // 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 ```java // 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 Type | Typical Kp | Typical Ti (s) | Typical Td (s) | |-----------|-----------|----------------|-----------------| | Level (averaging) | 0.5-2.0 | 60-300 | 0 | | Level (tight) | 2.0-5.0 | 30-60 | 0-10 | | Pressure (gas) | 0.5-2.0 | 20-100 | 0-5 | | Flow | 0.3-1.0 | 5-30 | 0 | | Temperature | 0.5-2.0 | 60-600 | 10-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. ```java 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: ```java // 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. ### 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)). ```java // 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: ```java 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`](../../../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. ```java 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: ```java import neqsim.process.util.scenario.AntiSurgeDynamicBenchmark; AntiSurgeDynamicBenchmark bench = new AntiSurgeDynamicBenchmark(); bench.setInitialMargin(0.30); bench.setDisturbanceRate(0.020); // flow loss erodes the margin (/s)
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub