| name | neqsim-dynamic-equipment-implementation |
| description | Implement and test NeqSim process-equipment runTransient support. USE WHEN: adding dynamic simulation behavior to any ProcessEquipmentInterface class, including inventory volume, pressure/level/temperature state, controller response, and JUnit regression tests for transient timesteps. |
| last_verified | 2026-06-25 |
Dynamic Equipment Implementation
Use this skill when a NeqSim equipment class needs real dynamic simulation support instead of the default steady-state fallback or unsupported transient behavior.
This complements neqsim-dynamic-simulation, which explains how to run dynamic studies. This skill explains how to implement and test the Java code that makes equipment participate correctly in ProcessSystem.runTransient(dt, id).
Local Architecture
SimulationInterface.runTransient(double dt, UUID id) defaults to steady-state run(id) when getCalculateSteadyState() is true, otherwise throws UnsupportedOperationException.
ProcessSystem.runTransient(dt, id) steps each ProcessEquipmentInterface through a skip-aware wrapper and calls unit.runTransient(dt, id).
- Equipment that has real dynamic behavior must override
runTransient(double dt, UUID id).
- Existing reference implementations include
Separator, ThreePhaseSeparator, Tank, Compressor, ThrottlingValve, SafetyValve, BlowdownValve, PipeBeggsAndBrills, WaterHammerPipe, SimpleReservoir, and WellFlow.
- All dynamic equipment code must remain Java 8 compatible, use Log4j2 for logging, and pass Spotless formatting.
Implementation Decision Tree
-
Is the equipment purely algebraic?
- Examples: simple pressure drop, splitter ratio, ideal heater with no metal/fluid holdup.
- Implement only if needed to propagate controller or actuator dynamics; otherwise the steady-state fallback may be sufficient.
-
Does the equipment hold mass or energy inventory?
- Examples: separator, tank, column tray/section, adsorber bed, exchanger volume, pipe segment, reactor, accumulator.
- Add explicit volume/holdup state and integrate component moles and internal energy over
dt.
-
Does the equipment have actuator or mechanical state?
- Examples: compressor shaft speed, valve opening, pump ramp, fan speed, bed switching, recycle valve position.
- Add bounded state variables, ramp-rate limits, and controller hooks.
-
Does the equipment need thermal inertia?
- Examples: heat exchangers, heaters/coolers, fired heaters, reactors, pipes.
- Add fluid volume and optional wall/metal heat capacity. Preserve energy balance and avoid instant outlet jumps unless the equipment is intentionally algebraic.
Required Code Pattern
For any new transient implementation:
- Add serializable state fields with defaults that preserve existing steady-state behavior.
- Add public setters/getters for dynamic configuration such as volume, residence time, metal mass, ramp rate, or initial inventory.
- Override
runTransient(double dt, UUID id).
- Keep the steady-state branch first:
- If
getCalculateSteadyState() is true, call run(id), increaseTime(dt), set/finish the calculation identifier consistently with the class pattern, and return.
- Initialize transient state lazily on the first dynamic step from the last steady-state solution.
- Run controllers or actuator logic before solving the equipment response when the class exposes controller behavior.
- Integrate component and energy balances over the timestep:
- Accumulation = inlet rates minus outlet rates plus generation or consumption.
- Energy accumulation = inlet enthalpy rates minus outlet enthalpy rates plus heat/work terms.
- Clamp tiny negative mole inventories to zero; do not allow negative total volume, negative pressure, or invalid phase amounts.
- Re-flash the updated inventory with the appropriate flash operation (
TPflash, TVflash, PHflash, or VUflash) and call initProperties() or the class-equivalent physical-property initialization before reading transport properties.
- Update every outlet stream from the new state and preserve mass/energy consistency.
- Call
increaseTime(dt) and set the calculation identifier before returning.
Equipment Configuration Guidance
Dynamic behavior needs physical capacity. Prefer existing mechanical-design data when it already exists, but expose a simple runtime configuration path for simulations:
- Vessel-like equipment: volume, diameter, length, liquid level, gas headspace, max/min operating pressure.
- Heat-transfer equipment: process-side volume, optional utility-side volume, UA, metal mass, metal heat capacity, ambient heat loss.
- Rotating equipment: inertia, rated speed, ramp rates, driver power limit, recycle or minimum-flow logic.
- Valves and dampers: opening fraction, stroke time, fail action, Cv/characteristic, controller attachment.
- Pipes: length, diameter, roughness, elevation profile, segment inventory, heat-transfer environment.
- Reactors and beds: bed volume, void fraction, catalyst/solid heat capacity, reaction source terms, residence time.
Do not hide required dynamic capacity behind mechanical design only. Task notebooks and MCP workflows need direct, documented setters for dynamic parameters.
Testing Requirements
Create focused JUnit 5 tests under the matching package in src/test/java/neqsim/....
Minimum test coverage for each dynamic implementation:
- Steady-state compatibility:
runTransient(dt, id) with calculateSteadyState=true matches or preserves the existing run(id) behavior.
- Dynamic branch executes: with
calculateSteadyState=false, runTransient(dt, id) does not throw and advances equipment time.
- Inventory response: a flow, heat, pressure, valve-opening, speed, or level disturbance changes a physically relevant state in the expected direction.
- Mass balance: integrated inlet minus outlet accumulation matches inventory change within a reasonable tolerance.
- Energy or temperature response: when relevant, heat/work input changes internal energy, outlet temperature, or pressure consistently.
- Controller interaction: if the equipment has controllers, verify one timestep calls/runs controller logic and applies bounded output.
- Bounds and robustness: zero/low flow, empty inventory, small
dt, and repeated timesteps remain finite and non-negative.
- Serialization/copy safety: if new fields are non-serializable, mark them
transient; otherwise verify normal equipment copy behavior still works.
Use physical assertions, not private implementation details. Prefer monotonic or bounded assertions over fragile exact transient values unless a regression baseline is intentional.
Validation Commands
After editing Java files in the NeqSim repo, run:
mvnw.cmd spotless:apply
mvnw.cmd test "-Dtest=YourDynamicTest"
mvnw.cmd spotless:check
When public APIs or JavaDoc were added or changed, also run:
mvnw.cmd javadoc:javadoc
When only agent/skill files were edited, run:
python devtools/verify_skills_agents.py
python devtools/generate_agent_skill_map.py
python devtools/verify_skills_agents.py
Common Pitfalls
- Forgetting to set
calculateSteadyState=false in tests, so the test never exercises the new dynamic branch.
- Adding a dynamic branch that calls
run(id) internally every timestep and erases the inventory state.
- Reading viscosity, density, or thermal conductivity after a flash without physical-property initialization.
- Updating outlet streams but not the internal inventory, or updating inventory but leaving outlet streams stale.
- Re-flashing every outlet stream after an inventory flash. For phase-separating equipment, rebuild outlets from the
freshly flashed inventory phases, preserve configured valve-capacity flow rates, and initialize properties without
another equilibrium flash. This keeps outlets current and avoids multiplying expensive CPA flashes per timestep.
- Introducing Java 9+ syntax such as
var, List.of, Map.of, text blocks, or String.repeat.
- Adding
System.out.println in tests or examples; use assertions or Log4j2 logger output.
- Adding non-serializable fields to equipment without
transient.
Handoff Checklist
Before considering a dynamic equipment implementation complete, confirm:
- The equipment has a documented physical capacity model or a documented reason why it remains algebraic.
runTransient(dt, id) has both steady-state fallback and real dynamic branch behavior.
- New setters/getters have JavaDoc and Java 8-compatible signatures.
- Tests exercise the real dynamic branch and at least one process-level
ProcessSystem.runTransient(dt, id) path when practical.
- Spotless and the focused test pass.