NeqSim’s distillation package provides equilibrium-stage columns, shortcut design,
hydraulic rating, internal recycles, side products, and a separate rate-based packed
column model for absorption and stripping. The main implementation lives in
neqsim.process.equipment.distillation.
Capability Map
| Area | Main APIs | Notes |
|---|---|---|
| Rigorous staged columns | DistillationColumn, SimpleTray, Condenser, Reboiler |
Equilibrium-stage MESH-style model with tray-by-tray flash calculations. |
| Solvers | SolverType |
Direct, damped, inside-out, adaptive matrix inside-out, Wegstein, sum-rates, Newton temperature correction, Naphtali-Sandholm, MESH residual, and AUTO with candidate tracing. |
| Formal specifications | ColumnSpecification, convenience setters |
Product purity, component recovery, reflux ratio, product flow rate, duty specifications, and staged specification homotopy for difficult product targets. |
| Side products | setGasSideDrawFraction, setLiquidSideDrawFraction, addSideDrawFlowSpecification |
Side draws are external product streams and are included in outlet stream and mass-balance reporting. |
| Pumparounds | addLiquidPumparound |
Internal liquid draw/return circuits solved as column tear variables. |
| Hardware modes | CondenserMode, ReboilerMode |
Partial or total condenser, fixed liquid reflux split, equilibrium reboiler, and vapor boilup ratio mode. |
| Hydraulics and sizing | calcColumnInternals, enableHydraulicPressureDropCoupling |
Rates tray or packing hydraulics and can couple total pressure drop back into the pressure profile. |
| Efficiency | setMurphreeEfficiency, setMurphreeEfficiencies |
Column-wide and per-stage Murphree vapor efficiency correction. |
| Shortcut design | ShortcutDistillationColumn, initializeFromShortcut |
Fenske-Underwood-Gilliland estimates and rigorous-column initialization. |
| Tray optimization | findOptimalNumberOfTrays, findEconomicOptimalTrayConfiguration |
Searches tray count and feed tray, optionally with economic ranking. |
| Dynamics | runTransient, DynamicColumnModel.EXPERIMENTAL_EULER |
Explicit-Euler holdup screening model. It is not a rigorous DAE dynamic column model. |
| Rate-based packed columns | RateBasedPackedColumn |
Segment-based packed absorption/stripping with film mass transfer, heat transfer, packing hydraulics, and JSON diagnostics. |
Tray Numbering
The DistillationColumn constructor is:
DistillationColumn column = new DistillationColumn(name, simpleTrayCount, hasReboiler,
hasCondenser);
The simpleTrayCount argument excludes the optional reboiler and condenser. Tray indices used by
addFeedStream, side draws, pumparounds, and per-stage efficiency are internal bottom-up stage
indices:
| Hardware | Internal index convention |
|---|---|
| Reboiler present | Index 0 is the reboiler. |
| Simple trays | Above the reboiler, increasing upward. |
| Condenser present | Last internal index is the condenser. |
For example, new DistillationColumn("T-100", 10, true, true) creates 12 internal stages:
reboiler, 10 simple trays, and condenser.
Basic Usage
import neqsim.process.equipment.distillation.DistillationColumn;
import neqsim.process.equipment.stream.StreamInterface;
DistillationColumn column = new DistillationColumn("Deethanizer", 10, true, true);
column.addFeedStream(feedStream, 5);
column.setTopPressure(25.0);
column.setBottomPressure(26.0);
column.setCondenserTemperature(273.15 - 10.0);
column.setReboilerTemperature(273.15 + 105.0);
column.setSolverType(DistillationColumn.SolverType.INSIDE_OUT);
column.run();
StreamInterface overhead = column.getGasOutStream();
StreamInterface bottoms = column.getLiquidOutStream();
Use getInletStreams() and getOutletStreams() for process-topology introspection. Outlet streams
include the terminal products plus any configured non-zero side-draw product streams.
Builder Pattern
The fluent builder covers the common rigorous-column setup path. Use the constructor when only one
end of the column has hardware, because the builder currently exposes withCondenserAndReboiler()
as the hardware shortcut.
import neqsim.process.equipment.distillation.ColumnSpecification;
import neqsim.process.equipment.distillation.DistillationColumn;
DistillationColumn column = DistillationColumn.builder("Deethanizer")
.numberOfTrays(15)
.withCondenserAndReboiler()
.topPressure(25.0, "bara")
.bottomPressure(26.0, "bara")
.temperatureTolerance(1.0e-3)
.massBalanceTolerance(1.0e-2)
.maxIterations(100)
.insideOut()
.internalDiameter(2.5)
.addFeedStream(feedStream, 8)
.topProductPurity("ethane", 0.95)
.bottomSpecification(new ColumnSpecification(
ColumnSpecification.SpecificationType.PRODUCT_PURITY,
ColumnSpecification.ProductLocation.BOTTOM, 0.98, "propane"))
.build();
| Builder method | Description |
|---|---|
numberOfTrays(int) |
Number of simple trays, excluding condenser and reboiler. |
withCondenserAndReboiler() |
Adds both terminal hardware items. |
topPressure(double, String), bottomPressure(double, String), pressure(double, String) |
Sets endpoint pressures. |
temperatureTolerance(double), massBalanceTolerance(double), tolerance(double) |
Sets convergence tolerances. |
maxIterations(int) |
Sets requested solver iterations. |
dampedSubstitution(), insideOut(), autoSolver() |
Selects common solver strategies. |
relaxationFactor(double) |
Starting damping factor for damped substitution. |
internalDiameter(double) |
Internal diameter in metres. |
addFeedStream(StreamInterface, int) |
Adds a feed to an internal tray index. |
topProductPurity(String, double) |
Adds a top product purity specification. |
bottomSpecification(ColumnSpecification) |
Adds any bottom-end specification object. |
build() |
Creates the configured column. |
Operating Specifications
Direct operating specifications are applied before the inner column solver runs.
column.setTopPressure(25.0);
column.setBottomPressure(26.0);
column.setCondenserTemperature(263.15);
column.setReboilerTemperature(378.15);
column.setCondenserRefluxRatio(3.0);
column.getCondenser().setHeatInput(-5.0e6);
column.getReboiler().setHeatInput(6.0e6);
column.setReboilerBoilupRatio(2.5);
Product-quality and recovery targets use ColumnSpecification. Purity and recovery targets are
dimensionless fractions from 0 to 1. Product-flow-rate targets and their absolute residuals use
the unit supplied to setTopProductFlowRate or setBottomProductFlowRate; common molar and mass
units such as mol/hr and kg/hr are supported by the product stream. Constructors that do not
supply a unit remain backward compatible and default product flow to mol/hr.
column.setTopProductPurity("ethane", 0.95);
column.setBottomProductPurity("propane", 0.98);
column.setTopComponentRecovery("ethane", 0.99);
column.setBottomComponentRecovery("propane", 0.99);
column.setBottomProductFlowRate(1000.0, "kg/hr");
ColumnSpecification topFlow = new ColumnSpecification(
ColumnSpecification.SpecificationType.PRODUCT_FLOW_RATE,
ColumnSpecification.ProductLocation.TOP, 500.0, null, "kg/hr");
topFlow.setTolerance(1.0e-3); // kg/hr, matching the target
topFlow.setMaxIterations(30);
column.setTopSpecification(topFlow);
The selected unit is retained through specification homotopy, diagnostics, warm-state identity,
copying, and serialization. Feasibility screening compares each flow target with external feed flow
in the same unit. When both terminal flow targets use the same unit, their sum is screened as well.
Mixed-unit terminal targets on a column with an external side draw are evaluated during the solve
because their product compositions may differ. getLastTopSpecificationResidual() and
getLastBottomSpecificationResidual() report flow residuals in the corresponding target unit.
Without an external side draw, top and bottom product-flow targets are not two independent
specifications: steady-state total material balance fixes one terminal flow after the feed and the
other terminal flow are known. The run preflight and validateSpecifications() therefore reject
the pair even when its targets sum exactly to feed. Use one product-flow target and an independent
purity, component-recovery, duty, or reflux specification. For the same reason, top and bottom
recovery targets for the same component are dependent without an external side draw and are
rejected before iteration. Different-component recovery targets remain independent. When an
external side draw is active, paired terminal controls can be structurally independent; paired
recoveries for one component are still screened so their sum cannot exceed the feed-component
inventory. These checks intentionally prevent rank-deficient outer solves and leave any previously
accepted tray and product state untouched.
For iterative specifications, NeqSim wraps the selected inner solver in an outer adjustment loop and
uses condenser or reboiler temperature as the manipulated variable where possible. Product purity,
component recovery, and product-flow specifications can be staged with
setSpecificationHomotopySteps(steps). Values above one ramp the effective target from the current
product value to the final user target, leaving the stored ColumnSpecification unchanged. When
AUTO is selected and an adjustable product specification is active, NeqSim uses three homotopy
stages by default unless the user has configured another stage count.
Condenser and Reboiler Modes
column.setCondenserMode(DistillationColumn.CondenserMode.PARTIAL);
column.setCondenserMode(DistillationColumn.CondenserMode.TOTAL);
column.setCondenserRefluxRatio(1.5); // required split equation for total condensation
column.setCondenserLiquidReflux(500.0, "kg/hr");
DistillationColumn.CondenserMode condenserMode = column.getCondenserMode();
column.clearCondenserRefluxRatio();
column.setReboilerMode(DistillationColumn.ReboilerMode.EQUILIBRIUM);
column.setReboilerVaporBoilupRatio(1.8);
DistillationColumn.ReboilerMode reboilerMode = column.getReboilerMode();
A total condenser requires an explicit reflux ratio so that the fully condensed liquid is divided
between reflux and distillate. Mode selection preserves an already configured ratio, so the ratio
and total-mode calls can be made in either order. An incomplete total-condenser declaration is an
error in validateSpecifications() and fails the run preflight before feed assignment or accepted
tray/product state can change. A failed total-condenser bubble-point calculation is also reported as
an exception instead of continuing with a stale thermodynamic state.
Ratio control and an adjustable endpoint product specification cannot be active at the same column
end. The ratio flash does not use the endpoint temperature that the purity, recovery, or product-flow
outer loop manipulates, so accepting both would create an ineffective control equation. Call
clearCondenserRefluxRatio() before using an adjustable top specification, or select
ReboilerMode.EQUILIBRIUM before using an adjustable bottom specification. Clearing a ratio
preserves unrelated product specifications. Specifications on a column end without the matching
condenser or reboiler are validation errors and fail during preflight.
getConvergenceDiagnostics() reports the configured condenser and reboiler modes plus whether
condenser ratio control is active. This makes retained/restarted models auditable before nearby-point
warm solves.
setReboilerVaporBoilupRatio(ratio) configures the direct reboiler mode.
setReboilerBoilupRatio(ratio) also records the target as the bottom REFLUX_RATIO
specification. Terminal ratios supplied through either the column API or the condenser/reboiler
objects must be finite and non-negative. Invalid values fail before the active ratio, stored
specification, initialization flag, or accepted warm products are changed; invalid ratio state
retained by an older serialized model also fails before a terminal flash. A valid legacy boilup
setter call marks the column for reinitialization so the next nearby-point solve cannot reuse an
incompatible terminal state. After either route,
setReboilerMode(DistillationColumn.ReboilerMode.EQUILIBRIUM) clears the active reboiler ratio and
any stored bottom reflux-ratio specification, so a later run cannot silently restore the old
ratio. Bottom purity, recovery, product-flow, and duty specifications are preserved.
setCondenserLiquidReflux(value, unit) configures the LIQUID_REFLUX_SPLIT mode. Use it instead
of calling setCondenserMode(LIQUID_REFLUX_SPLIT) directly because the fixed reflux flow is
required. The split never creates condensate to satisfy an oversized request: it returns at most the
available liquid, preserves material and energy, and leaves the column unsolved when the normalized
fixed-reflux shortfall exceeds its acceptance tolerance. The requested, available, delivered, and residual values appear
in
getConvergenceDiagnostics().
If the column rejects the tray state and installs guarded full-feed fallback products, the
condenser’s separate liquid product from that rejected state is cleared. The fallback gas and
bottom streams already contain the complete feed inventory, so exposing the old liquid product
would double-count material. Fixed-reflux delivery diagnostics are invalidated in this state;
callers must check for SolveStatus.FALLBACK_PRODUCTS and must not treat it as a rigorous
fixed-reflux solution.
A fixed liquid-reflux flow and a top REFLUX_RATIO specification are mutually exclusive because
both control the condenser reflux split. Configuration rejects either setter order, and
validateSpecifications() plus the run preflight detect contradictory state retained by an older
serialized model or introduced through direct Condenser mutation. To recover, call
setCondenserMode(DistillationColumn.CondenserMode.PARTIAL) or
setCondenserMode(DistillationColumn.CondenserMode.TOTAL) to clear fixed-flow mode, or remove the
top reflux-ratio specification.
Solver Options
| Solver type | Strategy | Typical use |
|---|---|---|
DIRECT_SUBSTITUTION |
Classic tray-by-tray substitution. | Default for simple, well-posed columns. |
DAMPED_SUBSTITUTION |
Sequential substitution with an initial fixed relaxation factor. | Stiffer cases where direct substitution overshoots. |
INSIDE_OUT |
Inside-out style flow correction with K-value tracking and polishing. | General deethanizer/depropanizer and multi-feed work. |
MATRIX_INSIDE_OUT |
Adaptive matrix warm start plus rigorous inside-out polishing. | Larger hydrocarbon fractionators where matrix setup cost is justified. |
WEGSTEIN |
Accelerated successive substitution after warm-up. | Well-conditioned fixed-point problems. |
SUM_RATES |
Flow-corrected tearing method. Native for columns without a condenser; condenser configurations remain guarded to damped substitution. | Absorbers, reboiler-only strippers, and flow-sensitive columns. |
NEWTON |
Tray-temperature Newton accelerator. | Difficult temperature convergence. It is not full simultaneous MESH Newton. |
NAPHTALI_SANDHOLM |
Guarded simultaneous correction of MESH blocks after inside-out warm start, with early return to coordinated fallback after repeated non-descent steps. | Residual-driven hydrocarbon fractionators. |
MESH_RESIDUAL |
Inside-out initialization plus full residual auditing. | Material, equilibrium, summation, energy, product-draw, and spec residual checks. |
AUTO |
Runs a feasibility pre-screen and copy-based candidate probes. Fixed-specification reboiler-only strippers try native SUM_RATES first; other configurations retain the relaxed damped base and guarded fallback ladder. |
Agent workflows and uncertain cases where robust automatic selection and diagnostics are useful. |
The NEWTON backtracking search evaluates bounded step lengths down to 0.125. It retains the
trial with the lowest finite temperature residual, even if no trial is a descent step, and then
keeps the applied state, step diagnostic, and residual diagnostic aligned. Use
getLastNewtonLineSearchStepLength(), getLastNewtonLineSearchResidual(), and
getLastNewtonLineSearchTrialCount() to audit that decision. A NEWTON result still requires the
ordinary column material, energy, specification, physical-state, and optional MESH gates; these
line-search diagnostics are evidence about globalization, not an independent convergence claim.
The NEWTON finite-difference sweeps and final WEGSTEIN synchronization always use unit
relaxation. They therefore install one owned clone of each already-flashed internal outlet directly
as the target tray inlet, without previous-stream arrays or a second cache clone. The owned
snapshot still follows the established relaxation and reflash path, preserving downstream
tear-state thermodynamic semantics. Audit the work with
getLastAcceleratedFullTraySweepCount() and
getLastAcceleratedInternalStreamTransferCount(). The counters describe attempted accelerator
work, survive accepted AUTO candidate adoption, and can remain nonzero if a coordinated fallback
later completes the route; they are performance diagnostics rather than convergence gates.
column.setSolverType(DistillationColumn.SolverType.AUTO);
column.run();
DistillationColumn.SolverType selected = column.getLastSolverTypeUsed();
AUTO keeps the requested solver type as AUTO, while getLastSolverTypeUsed() reports the
concrete solver that completed the run. Inspect getLastAutoSolverSummary() or
getConvergenceDiagnostics() to see the feasibility pre-screen, candidate list, residuals,
iteration counts, solve times, and fallback notes. For product-specification cases, also inspect
getLastSpecificationHomotopyStepCount() to confirm whether staged continuation was used.
Exact unchanged-input reuse is conditional on both an identical problem fingerprint and the same active convergence-gate configuration that was recorded after the accepted public solve. Changing an enforced mass, energy, MESH, product-draw, specification, or other tolerance disqualifies the zero-iteration cache hit. Tolerances for disabled energy and MESH gates, and outer tear tolerances when no tear variable is configured, do not participate in the cache key. The next invocation executes the solver path after an active-gate change and either meets the new contract or reports non-convergence explicitly.
Side Draws
Side draws withdraw a fraction of tray vapor or liquid traffic. They are true external product
streams: getOutletStreams() includes them, and getMassBalance(unit) subtracts them from the
feed-product balance. The sequential tray solvers and NAPHTALI_SANDHOLM both remove the withdrawn
phase from inter-tray material and energy traffic; the Naphtali-Sandholm solver also fingerprints the
configured split so a changed draw cannot reuse an incompatible warm state. The inside-out stage used
by MESH_RESIDUAL similarly retains an accepted tray and product state for exact unchanged-input
reuse when no outer column tear variables are active; changed feed, configuration, tray, or product
state invalidates that fingerprint. Pumparounds, hydraulic pressure coupling, and adjustable side-draw
flow specifications keep their coordinated outer initialization path. An exact liquid
side-draw fraction of 1.0 leaves zero internal downflow across that stage, so NeqSim routes an
explicit or reused NAPHTALI_SANDHOLM selection to MESH_RESIDUAL. Fractions below 1.0
retain positive internal traffic and remain eligible for the simultaneous solver.
Multiple external feeds may use different component subsets or component ordering, provided their thermodynamic models can be mixed by NeqSim. Column product reconciliation accumulates feed and side-draw inventories by component name on the combined column basis; it does not assume that every feed shares the first feed’s component-array indices. This keeps total-condenser material closure deterministic for, for example, a main C3-C5 feed plus a C3-C4 side feed on another tray. Repeated and nearby-point solves retain the same named component basis.
The named basis also applies when a partial condenser, vapor-boilup control, external side draw, and
pumparound are active together. The side draw remains an external product, while the pumparound
draw and return remain one internal circulation with the configured utility duty. Pumparounds use
the coordinated outer tear around the residual-monitored MESH_RESIDUAL inner solver; direct
substitution is not a robust choice for this fully coupled configuration. The methods
isLastColumnTearConverged() and
getLastColumnTearResidual() report that convergence, and exact sequential-state reuse stays
disabled while the nonlocal return is active. A changed external feed therefore re-solves both the
terminal products and pumparound state on the same component basis.
column.setGasSideDrawFraction(6, 0.05);
column.setLiquidSideDrawFraction(4, 0.10);
StreamInterface gasSideDraw = column.getSideDrawStream(6,
DistillationColumn.SideDrawPhase.GAS);
List<StreamInterface> allSideDraws = column.getSideDrawStreams();
For target side-product flow rates, use side-draw flow specifications. The column adjusts the corresponding tray split fraction as an outer tear variable.
DistillationColumn.ColumnSideDrawSpecification spec = column.addSideDrawFlowSpecification(6,
DistillationColumn.SideDrawPhase.GAS, 100.0, "kg/hr");
spec.setTolerance(1.0e-4);
spec.setMaxIterations(15);
column.setMaxColumnTearIterations(20);
column.setColumnTearTolerance(1.0e-4);
column.run();
double actualDraw = spec.getLastActualFlowRate();
double drawResidual = spec.getLastRelativeResidual();
boolean tearConverged = column.isLastColumnTearConverged();
Each tray-phase pair has one manipulated split fraction and therefore accepts at most one flow
specification. Adding a second target for the same tray and phase fails immediately with an
IllegalArgumentException; opposite phases on the same tray and the same phase on different trays
remain independent specifications. This degrees-of-freedom check prevents contradictory targets
from alternately overwriting one tear variable. Older serialized columns retaining duplicates also
fail before solver iteration and report the affected tray and phase.
If the requested side-draw flow is physically impossible, the split is bounded by available tray traffic and the latest tear-variable diagnostics report non-convergence.
For one independent side-draw flow specification, every proposed fraction is solved on a cold copied column state. Only rigorous or reconciled inner-column results can update the controller or become the public result; fallback-product and failed candidates are rejected, and the last accepted state is retained. The safeguarded search interpolates or explores from accepted flow observations only, avoiding feedback from invalid inner states.
Use getLastColumnTearRejectedCandidateCount(), getLastColumnTearRollbackCount(),
getLastColumnTearInnerIterationCount(), and getLastColumnTearCandidateHistory() to audit that
search. The history includes the proposed fraction, observed flow, inner status, and acceptance
decision for each trial. These diagnostics are transient and reset when the column is copied or
deserialized.
Pumparounds
Liquid pumparounds are internal draw/return circuits. They are neither external feeds nor external
products, so their return and draw streams do not appear in getInletStreams() or
getOutletStreams().
DistillationColumn.ColumnPumparound pumparound = column.addLiquidPumparound("PA-1", 4, 6,
0.15, 10.0);
column.setMaxPumparoundIterations(12);
column.setPumparoundTolerance(1.0e-4);
column.run();
StreamInterface returnStream = pumparound.getReturnStream();
double dutyKw = pumparound.getDuty("kW");
double latestChange = column.getLastPumparoundRelativeChange();
Each draw tray exposes one liquid pumparound fraction and one draw stream, so it can feed at most
one pumparound circuit. A zero-fraction standby circuit still owns that draw tray. Registering
another circuit for the same draw tray throws IllegalArgumentException without replacing the
first circuit; different draw trays remain independent. Legacy serialized columns containing
duplicate draw ownership fail before solver iteration, and validateSetup() reports
pumparound.degreesOfFreedom.
The temperatureDrop argument is in Kelvin. Positive values cool the returned liquid; negative
values heat it. A non-finite or below-zero-K return temperature fails explicitly. The column uses
a thermodynamic snapshot while seeding tray profiles, so initialization cannot change the public
return temperature. After a converged solve, the draw-to-return temperature difference therefore
equals the configured drop; the return flow and composition remain coupled through the outer tear
iteration.
ColumnPumparound.getDuty() reports return-stream enthalpy minus draw-stream enthalpy in watts;
getDuty(String) converts that value to another supported power unit. Cooling therefore has negative duty
and heating positive duty. Before the first return update the duty is Double.NaN. The public column energy
diagnostic includes the liquid draw as a tray outlet while the return remains a tray inlet, so their
enthalpy difference is retained as pumparound utility duty without treating the internal circulation
as an external feed or product.
Hydraulics and Pressure-Drop Coupling
calcColumnInternals() evaluates tray or packing hydraulics for the latest column state.
import neqsim.process.equipment.distillation.internals.ColumnInternalsDesigner;
column.setInternalDiameter(2.5);
ColumnInternalsDesigner designer = column.calcColumnInternals("sieve");
double totalPressureDropPa = designer.getTotalPressureDrop();
Hydraulic coupling is opt-in. When enabled, the column rates internals, converts total hydraulic pressure drop to a linear pressure profile between top and bottom, and re-solves until the hydraulic tear variable is within tolerance.
column.enableHydraulicPressureDropCoupling("sieve");
column.run();
double coupledPressureDropPa = column.getLastHydraulicPressureDropPa();
double hydraulicResidual = column.getLastHydraulicPressureDropResidual();
Supported internals names include common tray types such as "sieve", "valve",
"bubble-cap", and packed-column mode "packed", depending on available internals data.
Efficiency and Internals
Murphree vapor efficiency can be set globally or per stage.
column.setMurphreeEfficiency(0.70);
column.setMurphreeEfficiency(3, 0.65);
double stageEfficiency = column.getMurphreeEfficiency(3);
column.clearPerStageMurphreeEfficiency();
Use mechanical-design classes and calcColumnInternals() for actual-tray counts, HETP-style
height estimates, flooding, weeping, entrainment, downcomer backup, and pressure-drop checks. The
findEconomicOptimalTrayConfiguration methods also use tray efficiency to convert theoretical
stages to actual trays for cost ranking.
Shortcut Initialization and Tray Optimization
ShortcutDistillationColumn provides Fenske-Underwood-Gilliland estimates. A rigorous column can
use those estimates directly as a starting design.
DistillationColumn.ShortcutInitializationResult init = column.initializeFromShortcut(feedStream,
"ethane", "propane", 0.98, 0.98, 1.3);
if (init.isFeasible()) {
column.setSolverType(DistillationColumn.SolverType.INSIDE_OUT);
column.run();
}
Search utilities are available for tray count, feed tray, and economic ranking.
column.setMaxTrayOptimizationCandidates(200);
column.setMaxTrayOptimizationTimeSeconds(20.0);
int trays = column.findOptimalNumberOfTrays(0.95, "ethane", true, 30);
DistillationColumn.EconomicTrayOptimizationResult economic =
column.findEconomicOptimalTrayConfiguration(0.95, "ethane", true, 30);
Optimization mutates the column to the selected feasible candidate. Capture or copy the model first if the current tray count must be preserved.
Dynamic Screening Model
The current distillation dynamics are explicitly experimental:
getDynamicColumnModel() returns DynamicColumnModel.EXPERIMENTAL_EULER, and
isDynamicColumnModelExperimental() returns true.
column.setDynamicColumnEnabled(true);
column.setDynamicEnergyEnabled(true);
column.setTrayWeirHeight(0.05);
column.setTrayWeirLength(1.0);
column.setTrayDryPressureDrop(200.0);
The transient model uses explicit-Euler tray holdup updates, Francis-weir liquid overflow, optional per-tray energy tracking, and a simplified vapor hydraulic relation. Treat it as a screening tool for qualitative inventory response, not as a replacement for commercial DAE dynamic simulators in control-system design or safety-critical trip studies.
Rate-Based Packed Column
Use RateBasedPackedColumn for counter-current packed absorption and stripping when equilibrium
stages are not the right model.
import neqsim.process.equipment.distillation.RateBasedPackedColumn;
RateBasedPackedColumn absorber = new RateBasedPackedColumn("CO2 absorber", gasIn, liquidIn);
absorber.setColumnDiameter(1.2);
absorber.setPackedHeight(6.0);
absorber.setNumberOfSegments(12);
absorber.setPackingType("Pall-Ring-50");
absorber.setTransferComponents("CO2");
absorber.run();
StreamInterface treatedGas = absorber.getGasOutStream();
String report = absorber.toJson();
The rate-based model exposes segment profiles, component-transfer totals, pressure-drop and flood fraction diagnostics, film/heat-transfer model choices, and equation-oriented residual diagnostics.
Diagnostics and Results
| Getter | Description |
|---|---|
solved() |
Current convergence flag, including active side-draw, pumparound, and hydraulic outer tears. |
getLastSolverTypeUsed() |
Concrete solver that completed the latest run, especially useful when requested solver is AUTO. |
getLastSolveStatus() |
Strict solve status: rigorous convergence, reconciled products, fallback products, failure, or not run. |
getLastSolveStatusReason() |
Concise explanation for fallback or rejected candidate states. |
getLastAutoSolverSummary() |
Candidate trace from AUTO, including the feasibility pre-screen and per-candidate residual metrics. |
getLastIterationCount() |
Inner solver iteration count. |
getLastSolveTimeSeconds() |
Latest solve wall time. |
getLastTemperatureResidual() |
Average tray-temperature residual in Kelvin. |
getLastMassResidual() |
Relative mass-balance residual. |
getLastEnergyResidual() |
Relative enthalpy-balance residual. |
getEnergyBalanceError() |
Maximum tray/column enthalpy imbalance, including external side draws, pumparound draw/return duty, and excluding zero-flow phase templates. |
getLastTopSpecificationResidual(), getLastBottomSpecificationResidual() |
Endpoint spec errors. |
getLastSpecificationResidual() |
Maximum absolute endpoint spec error. |
getSpecificationHomotopySteps() |
Configured number of staged continuation targets for adjustable product specifications. |
getLastSpecificationHomotopyStepCount() |
Number of specification continuation stages completed by the latest solve. |
getLastColumnTearIterationCount() |
Outer side-draw, pumparound, and hydraulic tear iterations. |
getLastColumnTearResidual() |
Maximum outer tear residual. |
isLastColumnTearConverged() |
Whether active side-draw, pumparound, and hydraulic tear variables met tolerance. |
getLastColumnTearCandidateHistory() |
Accepted/rejected single-side-draw attempts, including a guarded continuation retry when a cold solve fails after an accepted state exists. |
ColumnPumparound.getDuty(), getDuty(String) |
Latest pumparound cooler/heater duty, negative for cooling and positive for heating. |
getLastPumparoundRelativeChange() |
Maximum latest pumparound return-flow change. |
getLastHydraulicPressureDropPa() |
Latest coupled hydraulic pressure drop in Pa. |
getLastHydraulicPressureDropResidual() |
Relative pressure-profile change from hydraulic coupling. |
getLastMeshResidualNorm() |
Full scaled MESH residual infinity norm. |
getLastMeshMaterialResidualNorm() |
Component material residual norm. |
getLastMeshEquilibriumResidualNorm() |
Phase-equilibrium residual norm. |
getLastMeshSummationResidualNorm() |
Mole-fraction summation residual norm. |
getLastMeshEnergyResidualNorm() |
Tray energy residual norm. |
getLastMeshProductDrawResidualNorm() |
Terminal product-draw residual norm. |
getLastMeshSpecificationResidualNorm() |
Active endpoint specification residual norm. |
getLastMeshResidualVector() |
Copy of the complete scaled residual vector. |
An accepted inner tray solution is not sufficient when an outer tear variable is active. If the
side-draw, pumparound, or hydraulic tear stops or exhausts its iteration budget above tolerance,
solved() returns false, getLastSolveStatus() returns FAILED, and
getLastSolveStatusReason() reports the outer residual, tolerance, and iteration count. Product
streams remain available for diagnostics but must not be treated as a converged process result.
The MESH residual gate is diagnostic-only for legacy sequential solvers by default. It is effective
by default for NAPHTALI_SANDHOLM and MESH_RESIDUAL; call
setEnforceMeshResidualTolerance(true) to make it part of the convergence contract for other
solvers.
Common Workflows
NGL Fractionation
- Build the feed with an EOS suitable for the hydrocarbon range, set mixing rule, and run the feed stream.
- Initialize with
ShortcutDistillationColumnorinitializeFromShortcutwhen light and heavy keys are known. - Run
INSIDE_OUTorAUTOfirst; useMESH_RESIDUALorNAPHTALI_SANDHOLMfor residual audit. - Add product purity or component recovery specs.
- Rate internals and enable hydraulic pressure-drop coupling only after a stable base case exists.
Absorber or Stripper
Use DistillationColumn without condenser/reboiler for simple equilibrium-stage absorber or
stripper studies, or RateBasedPackedColumn for packed mass-transfer studies.
DistillationColumn absorber = new DistillationColumn("Absorber", 10, false, false);
absorber.addFeedStream(gasStream, 0);
absorber.addFeedStream(leanSolvent, 9);
absorber.setSolverType(DistillationColumn.SolverType.SUM_RATES);
absorber.run();
For a stripper with a reboiler and no condenser, use new DistillationColumn("Stripper", 8, true,
false). Explicit SUM_RATES and AUTO use the native sum-rates path for a fixed-specification
reboiler-only stripper. Condenser-only and full condenser/reboiler columns remain guarded to damped
substitution because reflux and overhead-energy coupling are not represented directly by the
sum-rates accelerator.
Separated terminal products use a canonical trace-phase rule: if the intended gas or liquid phase
contains all but 1e-8 of the product mole inventory, the smaller phase is merged into that intended
outlet while preserving every component mole. This prevents a parts-per-billion phase from changing
the reported product phase count solely because two converged sequential solvers approached a dew-
or bubble-point boundary from opposite sides. Material phase fractions above 1e-8 are retained.
Troubleshooting
| Symptom | Recommended checks |
|---|---|
| No convergence | Verify tray numbering, feed condition, endpoint temperatures, pressure profile, and component list. Start with DIRECT_SUBSTITUTION, DAMPED_SUBSTITUTION, or AUTO, then inspect getConvergenceDiagnostics(). |
| Oscillation | Reduce aggressive condenser/reboiler specs, set a lower relaxation factor, or use DAMPED_SUBSTITUTION. |
| Specification does not close | Check getLastTopSpecificationResidual(), getLastBottomSpecificationResidual(), feasible product split, and whether the required condenser/reboiler exists. For sharp purity, recovery, or product-flow targets, use setSpecificationHomotopySteps(steps) or AUTO. |
| Side-draw spec reports non-converged | The target may exceed available tray traffic or feed component inventory. Inspect getSideDrawStream(...), side-draw fraction, and getLastColumnTearResidual(). |
| Pumparound fails | Check draw fraction, tray numbers, and return temperature. A return below 0 K is rejected. |
| Hydraulic coupling fails | Run without coupling first, set a positive internal diameter, and verify calcColumnInternals(...) succeeds for the selected internals type. |
| Unexpected dynamic result | Confirm the model is acceptable for screening. The current formulation is EXPERIMENTAL_EULER, not a rigorous industrial DAE. |