UniSim Design / HYSYS → NeqSim Conversion Skill
Convert Honeywell UniSim Design (.usc) files into NeqSim ProcessSystem or ProcessModule structures using Windows COM automation.
Prerequisites
- Windows only — UniSim Design must be installed (COM server)
- Python packages:
pywin32(pip install pywin32) - UniSim Design R510+ (R460+ should also work)
- COM ProgID:
UnisimDesign.Application
Core Module
The devtools/unisim_reader.py module provides three main classes:
| Class | Purpose |
|---|---|
UniSimReader |
Opens .usc files via COM, extracts all data |
UniSimToNeqSim |
Converts extracted model to NeqSim JSON builder format or standalone Python code |
UniSimComparator |
Compares UniSim vs NeqSim results for verification |
Supporting tools:
| Tool | Purpose |
|---|---|
devtools/unisim_batch_check.py |
Convert a whole corpus, one subprocess per case; timings, unmapped types, warnings, compile + undefined-name checks |
devtools/unisim_run_generated.py |
Execute every generated model in its own subprocess and report how far it gets |
devtools/unisim_probe_ops.py |
Dump the readable COM properties of an operation type |
devtools/unisim_probe_column.py |
Dump a column's attached streams and ColumnFlowsheet internals |
devtools/test_unisim_outputs.py |
Pure-Python regression tests (no COM needed) |
1. UniSim COM Object Model
UniSim's COM automation exposes the following hierarchy:
Application
├── SimulationCases
│ └── Case
│ ├── Solver (CanSolve, Converge)
│ ├── BasisManager
│ │ └── FluidPackages[]
│ │ ├── name, PropertyPackageName
│ │ └── Components[]
│ │ └── name
│ └── Flowsheet (main)
│ ├── MaterialStreams[]
│ │ ├── name
│ │ ├── Temperature.GetValue("C")
│ │ ├── Pressure.GetValue("bar")
│ │ ├── MassFlow.GetValue("kg/h")
│ │ ├── MolarFlow.GetValue("kgmole/h")
│ │ ├── MassDensity.GetValue("kg/m3")
│ │ ├── MolecularWeight.GetValue()
│ │ ├── VapourFraction.GetValue()
│ │ ├── MassEnthalpy.GetValue("kJ/kg")
│ │ ├── ComponentMolarFraction.GetValues()
│ │ └── ComponentMolarFraction.SetValues([...])
│ ├── EnergyStreams[]
│ │ ├── name
│ │ └── HeatFlow.GetValue("kW")
│ ├── Operations[]
│ │ ├── name, TypeName
│ │ ├── Feeds[] (multi-feed ops: mixers, separators)
│ │ ├── Products[] (multi-product ops: tee/splitter)
│ │ ├── FeedStream / ProductStream (single-stream ops)
│ │ ├── Product (singular, for mixers)
│ │ ├── VapourProduct, LiquidProduct, WaterProduct (separators)
│ │ ├── EnergyFeeds[], EnergyProducts[]
│ │ └── Type-specific: DutyValue, AdiabaticEfficiency,
│ │ PolytropicEfficiency, PressureDrop, Length, Diameter
│ └── Flowsheets[] (sub-flowsheets, recursive)
CRITICAL: Operation Connectivity Patterns
UniSim COM uses different property names for different operation types. You MUST check multiple patterns to extract feed/product connections:
| Operation Type | Feed Source | Product Source |
|---|---|---|
| compressor | FeedStream (single) |
ProductStream (single) |
| coolerop / heaterop | FeedStream (single) |
ProductStream (single) |
| valveop | FeedStream (single) |
ProductStream (single) |
| recycle | FeedStream (single) |
ProductStream (single) |
| pumpop / expandop | FeedStream (single) |
ProductStream (single) |
| mixerop | Feeds[] (array) |
Product (singular!) |
| teeop | FeedStream (single) |
Products[] (array) |
| flashtank | Feeds[] (array) |
VapourProduct, LiquidProduct |
| sep3op | Feeds[] (array) |
VapourProduct, LiquidProduct, WaterProduct |
| heatexop | Has shell-side / tube-side sub-objects | |
| columns (see below) | AttachedFeeds[] |
AttachedProducts[] |
WARNING: op.Products does NOT exist on mixers and separators — it throws
AttributeError. You must use op.Product (singular) for mixers and
op.VapourProduct / op.LiquidProduct for separators.
CRITICAL: Columns use AttachedFeeds / AttachedProducts
A UniSim column (distillation, columnop, absorber, reboiledabsorber,
refluxedabsorber, ratedistillation) exposes none of Feeds,
FeedStream, Products, Product or ProductStream — every one raises
AttributeError. Its external connections live on AttachedFeeds /
AttachedProducts, and those collections mix material and energy streams:
DePropanizer (TUTOR1, type=distillation)
AttachedFeeds ['TowerFeed', 'RebDuty'] <- RebDuty is ENERGY
AttachedProducts ['LiquidProd', 'Ovhd', 'CondDuty'] <- CondDuty is ENERGY
Because the generic feed extractor found nothing, every column in every case
was silently dropped from the converted flowsheet (an unfed NeqSim column
throws on run(), so the converter skipped it). Always extract columns through
their own path.
The column's ColumnFlowsheet supplies everything else:
cfs = column_op.ColumnFlowsheet
cfs.EnergyStreams # ['CondDuty', 'RebDuty'] -> classify material vs energy
cfs.MaterialStreams # incl. the external feed/products; read VapourFraction,
# Pressure off these items
cfs.RefluxRatio # 0.9999 — direct scalar, no Specifications parsing
cfs.Specifications # ['Reflux Ratio', 'Propane Fraction', 'Ovhd Vap Rate', ...]
cfs.Operations # ['Main TS', 'Condenser', 'Reboiler']
# traysection -> NumberOfTrays=10, FeedStages=['5__Main TS']
# partialcondenser / totalcondenser / condenser3op -> hasCondenser
# bpreboiler -> hasReboiler
Product order is not physical. AttachedProducts lists bottoms before
overhead in TUTOR1, but the converter maps product index 0 to the column gas
outlet. Classify instead:
- distillate = condenser internal's
AttachedProducts∩ column products - bottoms = reboiler internal's
AttachedProducts∩ column products - anything left over (and absorber feeds) is ordered by
VapourFraction, vapour-rich first — an absorber's gas feed must be index 0
CondenserPressure / ReboilerPressure do not exist on the operation; read
top/bottom pressure off the distillate/bottoms product streams instead.
Tray-index translation. UniSim numbers tray-section stages from the TOP (stage 1 = top tray). NeqSim numbers trays from the BOTTOM, with index 0 the reboiler when present and the condenser last, and the constructor argument excludes reboiler/condenser:
neqsim_index = (1 if hasReboiler else 0) + (n_trays - unisim_stage)
TUTOR1: n_trays=10, stage 5 -> tray 6 of DistillationColumn(name, 10, True, True)
An absorber has neither, so gas enters tray 0 and lean solvent tray n-1.
Feeding at n throws IllegalArgumentException: Feed tray index must be between 0 and n-1.
The recommended extraction order (as implemented in unisim_reader.py):
- Try
Feeds[]array first (multi-feed ops) - Fall back to
FeedStream(single-stream ops) - Try
Products[]array first (multi-product ops) - Try
VapourProduct/LiquidProduct/WaterProduct(separators) - Try
Productsingular (mixers) - Fall back to
ProductStream(single-stream ops)
### Key COM Patterns
```python
import win32com.client
import time
# Start UniSim
app = win32com.client.dynamic.Dispatch('UnisimDesign.Application')
app.Visible = True # or False for headless
# Open a case
case = app.SimulationCases.Open(r'C:\path\to\file.usc')
time.sleep(3) # Wait for loading
# Pause solver during extraction
solver = case.Solver
solver.CanSolve = False
# Access data
fs = case.Flowsheet
stream = fs.MaterialStreams.Item(0)
temp_C = stream.Temperature.GetValue('C')
pres_bar = stream.Pressure.GetValue('bar')
flow_kgh = stream.MassFlow.GetValue('kg/h')
comp_fracs = stream.ComponentMolarFraction.GetValues()
# Unit operations
op = fs.Operations.Item(0)
op_type = op.TypeName # e.g. "compressor", "valveop", "sep3op"
op_name = op.name
# Feed/product streams — varies by operation type!
# For single-stream ops (compressor, valve, cooler, pump, heater):
feed_name = op.FeedStream.name
prod_name = op.ProductStream.name
# For mixers: Feeds[] array + Product singular
for i in range(op.Feeds.Count):
feed_name = op.Feeds.Item(i).name
prod_name = op.Product.name # singular!
# For separators: Feeds[] + VapourProduct / LiquidProduct
for i in range(op.Feeds.Count):
feed_name = op.Feeds.Item(i).name
vap_name = op.VapourProduct.name
liq_name = op.LiquidProduct.name
# For tee/splitter: FeedStream + Products[]
feed_name = op.FeedStream.name
for i in range(op.Products.Count):
prod_name = op.Products.Item(i).name
# Close
case.Close()
app.Quit()
Important Notes
- Always call
solver.CanSolve = Falsebefore reading to prevent recalculation - UniSim uses -32767 for empty/unset values — filter these out
- Property values accessed via
.GetValue(unit_string) - Composition accessed via
.GetValues()returning a sequence
GOTCHA: the UniSim COM server is a SINGLETON
Dispatch('UnisimDesign.Application') attaches to the one running UniSim
instance, it does not start a private one. Consequences:
- Running a second COM script while a batch runs gives
com_error: The RPC server is unavailable. reader.close()callsapp.Quit(), which closes UniSim for every other script using it.- Never run two UniSim COM scripts concurrently. Subprocess isolation makes a crash survivable, but it does not make concurrency safe.
Prefer readiness polling over fixed sleeps
UniSim needs an unpredictable time to publish its automation object model. A
fixed time.sleep(3) is both slower than needed on small cases and unsafe on
large ones. UniSimReader._wait_ready(probe, timeout, description) polls a
cheap COM property instead (app.SimulationCases.Count,
case.Flowsheet.MaterialStreams.Count). Removing the two blind sleeps cut the
TUTOR1 read from 18.3 s to 6.8 s and the 66-case corpus from 816 s to 656 s.
A refused Open is usually a missing module, not a bad file
SimulationCases.Open raises a bare com_error ... E_ACCESSDENIED (-2147024891) for cases needing a UniSim extension that is not installed (the
R510 CCC Series 5\* controls cases and the EO electrical case). The files are
not read-only — verified. UniSimReader._open_case() retries once on a fresh
session (which does fix a genuinely wedged shared session) and then raises a
RuntimeError naming the likely cause.
1.1 Extracting Binary Interaction Parameters (BIPs / kij)
UniSim stores the full BIP (kij) matrix on the PropertyPackage object. The correct COM access pattern is:
fp = case.Flowsheet.FluidPackage
pp = fp.PropertyPackage
kij_obj = pp.Kij # Returns CDispatch (RealFlexVariable)
raw = kij_obj.Values # Returns tuple-of-tuples (n×n matrix)
# IMPORTANT: The .Values property returns the full symmetric matrix.
# Diagonal values are -32767.0 (sentinel for "self-interaction").
# Replace with 0.0 when parsing.
n = fp.Components.Count
comp_names = [fp.Components.Item(i).Name for i in range(n)]
bic = []
for i in range(n):
row = []
for j in range(n):
val = float(raw[i][j])
if abs(val + 32767.0) < 1.0: # diagonal sentinel
val = 0.0
row.append(val)
bic.append(row)
Key discoveries:
pp.Kijreturns aCDispatch(UniSim RealFlexVariable), NOT a Python-iterablekij_obj.Valuesis the correct access pattern — returns tuple-of-tupleskij_obj.GetValues()fails with "Invalid number of parameters"kij_obj.__call__(i,j)fails with "Does not support a collection"pp.GetInteractionParameter(i,j)returns 0.0 for PR-LK (correlation-based BIPs are stored internally, not as user-defined parameters)- The matrix is symmetric:
kij[i][j] == kij[j][i] - For PR-LK, BIPs are generated from the Lee-Kesler correlation — they are non-zero even if never explicitly tuned by the user
Common BIP patterns (PR-LK, hydrocarbon system):
- H2O–HC: +0.48 to +0.50 (strong positive interaction)
- H2O–N2: -2.24 (strong negative)
- H2O–CO2: -0.56
- CO2–C1: +0.105
- N2–C1: +0.025
- HC–HC (light–heavy): small values, typically < 0.01
1.2 Extracting Component Thermodynamic Properties
For pseudo-components and library components, extract critical properties and other thermodynamic data:
fp = case.Flowsheet.FluidPackage
pp = fp.PropertyPackage
n = fp.Components.Count
for i in range(n):
comp = fp.Components.Item(i)
name = comp.Name
mw = comp.MolecularWeight.GetValue()
# Prefer native UniSim units and convert explicitly.
# Some COM surfaces return misleading values when requesting alternate units.
tc = comp.CriticalTemperature.GetValue("C") + 273.15 # K
pc = comp.CriticalPressure.GetValue("kPa") * 0.01 # bara
nbp = comp.NormalBoilingPt.GetValue("C") + 273.15 # K
vc = comp.CriticalVolume.GetValue("m3/kgmole") # m3/kmol
# Acentric factor: the UniSim COM attribute is `Acentricity` (NOT
# `AcentricFactor`). Only fall back to Edmister if it is absent.
omega = comp.AcentricityValue
Notes:
- UniSim component collections can be 0-based or 1-based depending on the COM
collection surface. If
Components.Item(i)fails, retryComponents.Item(i+1)before dropping the component. - For component critical properties, request
CriticalTemperatureandNormalBoilingPointinCfirst and convert to K; requestCriticalPressureinkPafirst and convert to bara. Sanity-check known components after export: methane Tc ≈ 190.7 K and Pc ≈ 46.4 bara; water Tc ≈ 647.3 K and Pc ≈ 221 bara. - Acentric factor: read
comp.Acentricity/comp.AcentricityValue. This is the attribute UniSim actually exposes;AcentricFactor/Omegado NOT exist on the component COM surface. Reading the Edmister estimate instead silently changes the EOS alpha function and shifts every bubble point / TVP of the converted fluid (measured: 12–15 % low on a 23-component SRK-Peneloux oil, and0.0for the heaviest pseudos where the Edmister value exceeded theomega > 2sanity guard). WithAcentricityread correctly, a NeqSim E300 round-trip reproduces UniSim bubble points to < 0.5 %. Keep the property-package vectors (Acentricity,AcentricFactor,Omega,ACF) and Edmister only as fallbacks. - Parachor can sometimes be read via
pp.Parachor.Values(same pattern as Kij). - Volume shift:
pp.VolumShift.Values(note the UniSim spelling: "VolumShift", not "VolumeShift").
1.2.1 Reading TVP / RVP (Cold Properties) off a stream
Vapour-pressure results live on the material stream, in kPa (temperatures
in °C). Missing values return the sentinel -32767.
s = case.Flowsheet.MaterialStreams.Item(0)
s.TrueVPValue # TVP, evaluated at 37.8 C
s.RVP_37_8_DegCValue # Reid VP at 37.8 C
s.RVPASTM_D323_73_79Value # ASTM / API RVP correlations
s.RVPAPI_5B_1_1Value, s.RVPAPI_5B_1_2Value
cp = s.ColdProperty # Cold Properties utility
cp.TrueVapourPressureValue, cp.ReidVapourPressureValue
cp.FlashPointValue, cp.PourPointValue, cp.D86CurveValue
For TVP at any other temperature (e.g. a 30 °C export spec), flash a duplicate — this never touches the case:
f = s.DuplicateFluid()
f.TVFlash(30.0, 0.0) # (T [C], vapour fraction) -> bubble point
tvp_bara = f.PressureValue * 0.01
Water-free basis: assign a renormalised f.MolarFractionsValue with H2O zeroed
before the flash (the setter works); f.MassFractionsValue gives water wt%
directly. Free water only adds its own saturation pressure (~0.04 bara at 30 °C).
NeqSim comparison gotcha:
bubblePointPressureFlashis not three-phase aware. With free water in the feed it returns a grossly inflated TVP (measured 5–6.5 bara instead of 2.2) because water is treated as dissolved in the oil, andgetPhase("oil")then returns the aqueous phase. Strip water first, or useStandard_ASTM_D6377'sVPCR4_no_water/RVP_ASTM_D323_73_79variants.A TVP/RVP ratio far above ~1.3 is usually physical, not an error: a bubble point is hypersensitive to trace dissolved light ends (N2/C1/CO2) while the V/L = 4, 80 vol %-vaporised RVP test is not. It signals incompletely stabilised oil.
1.3 Generating E300 Fluid Files from UniSim Data
To create an Eclipse E300-format fluid file from UniSim-extracted properties
for loading into NeqSim via EclipseFluidReadWrite.read():
Required E300 sections (NeqSim reader will crash without these):
CNAMES— component namesTCRIT— critical temperatures (K)PCRIT— critical pressures (bar/bara as used by NeqSim's E300 writer)ACF— acentric factorsMW— molecular weights (g/mol)TBOIL— normal boiling points (K)VCRIT— critical volumes (m3/kg-mol)PARACHOR— parachor values (if unknown:4.0 * MW^0.77)SSHIFT— volume shift parameters (can be all zeros)BIC— binary interaction coefficients (lower triangular)ZI— mole fractions
Optional E300 sections (NeqSim supports these):
BICS— volume-corrected BICs at surface conditions (parsed but same format as BIC)OMEGAA— per-component OmegaA override values (one value per line +/terminator)OMEGAB— per-component OmegaB override values (same format)SSHIFTS— volume shift at surface conditions (same format as SSHIFT)PEDERSEN— keyword (no values) → activates Pedersen viscosity correlation
EOS Selection via E300:
EOS\nSRK /→SystemSrkEosEOS\nPR /\nPRCORR→SystemPrEos1978(PR1978 correction)EOS\nPR /\nPRLKCORR→SystemPrLeeKeslerEos(PR-LK, PR76 alpha for all ω)EOS\nPR /→SystemPrEos(base PR)
CRITICAL: If the BIC section is omitted, NeqSim's EclipseFluidReadWrite
will crash with a NullPointerException. Always include BIC, even if all zeros.
Loading with a forced EOS (ignores EOS keyword in file):
from neqsim import jneqsim
EclipseFluidReadWrite = jneqsim.thermo.util.readwrite.EclipseFluidReadWrite
SystemPrLeeKeslerEos = jneqsim.thermo.system.SystemPrLeeKeslerEos
# Force PR-LK regardless of EOS in file
fluid = SystemPrLeeKeslerEos(288.15, 1.01325)
fluid = EclipseFluidReadWrite.read(e300_path, fluid)
⚠️ CRITICAL WARNING — Water BIPs and OmegaA:
When water is present, the water–hydrocarbon BIPs (kij) interact dangerously with OmegaA. NEVER mix BIP conventions with OmegaA modifications:
- Standard E300 BIPs for water–HC are typically +0.48 to +0.50
- PR-LK correlation BIPs for H2O–N2 are typically −2.24 (negative!)
- H2O–CO2 is typically −0.557, H2O–H2S is typically −0.390
These negative BIPs intentionally increase cross-attraction and keep water
in the liquid phase. If you simultaneously set OMEGAA for water to a
non-standard value (e.g., 0.42748), the phase behavior will be wrong —
HP Separator vapour fraction can jump from 0.41 to 0.81 (catastrophic).
Rule: Only use OMEGAA for water together with its matched BIP set.
Default (no OMEGAA section) is safer unless you have a PVTsim-generated file
that was specifically fitted with both OMEGAA and BICs together.
NeqSim E300 component name mapping:
| E300 Name | NeqSim Maps To |
|---|---|
C1 |
methane |
C2 |
ethane |
C3 |
propane |
iC4 |
i-butane |
C4 |
n-butane |
iC5 |
i-pentane |
C5 |
n-pentane |
C6 |
n-hexane |
N2 |
nitrogen |
CO2 |
CO2 |
H2O |
water |
| All others | TBP pseudo-fraction (via addTBPfraction()) |
Note: Aromatics (Benzene, Toluene, E-Benzene, m-Xylene, etc.) are NOT in NeqSim's E300 recognized name map — they will be treated as TBP pseudo-fractions with estimated density.
E300 Fluid Export (DEFAULT — Recommended Route)
The E300 export route is the default and preferred method for transferring fluid definitions from UniSim to NeqSim. It preserves all critical properties (Tc, Pc, acentric factor, MW, BIPs, volume shifts, parachors) for both standard and hypothetical/pseudo components — including C7+ fractions that cannot be accurately recreated by component name mapping alone.
When UniSimReader.read() is called with export_e300=True (the default),
it extracts critical properties from each component in each fluid package via COM,
then writes an E300 file per fluid package to the output directory.
COM properties extracted per component:
component.CriticalTemperature→ Tc (request C first, convert to K)component.CriticalPressure→ Pc (request kPa first, convert to bara)component.AcentricFactor/ package vector / Edmister fallback → omegacomponent.MolecularWeight→ MW (g/mol)component.NormalBoilingPoint→ Tboil (request C first, convert to K)component.CriticalVolume→ Vcrit (m³/kgmol)
BIPs extracted via: FluidPackage.PropertyPackage.GetBIP(i, j) or
PropertyPackage.BinaryInteractionParameters (matrix fallback).
E300 file format keywords:
METRIC, NCOMPS, EOS, PRCORR, RTEMP, STCOND, CNAMES, TCRIT,
PCRIT, ACF, MW, TBOIL, VCRIT, SSHIFT, PARACHOR, ZI, BIC
NeqSim loading: Use EclipseFluidReadWrite.read(e300Path) in Java, or
via Python: jneqsim.thermo.util.readwrite.EclipseFluidReadWrite.read(path).
Automatic integration: When build_and_run() detects an E300 file in the
fluid section, it loads the fluid via EclipseFluidReadWrite.read() and passes
it to ProcessSystem.fromJsonAndRun(json, fluid), bypassing component name
mapping entirely.
# Example: Full E300 workflow
reader = UniSimReader()
model = reader.read(r'C:\path\to\model.usc') # auto-exports E300 files
# E300 files now available:
for fp in model.fluid_packages:
print(f" {fp.name}: {fp.e300_file_path}")
# Convert to NeqSim and run — E300 fluid used automatically
converter = UniSimToNeqSim(model)
result = converter.build_and_run()
2. Operation Type Mapping
UniSim internal operation type names (from op.TypeName) mapped to NeqSim types:
Mapping Architecture
devtools/unisim_reader.py uses a typed UniSimOperationHandler registry. Do
not add scattered local skip lists or one physical NeqSim class per UniSim name.
The registry records:
| Field | Meaning |
|---|---|
neqsim_type |
Target NeqSim type or converter pseudo-type |
strategy |
native, adapter, reference, control, column_internal, or skip |
stream_role |
material, reference, or none for topology reconstruction |
note |
Human-readable rationale written to JSON mapping summaries |
Policy: Native physical UniSim operations map to native NeqSim equipment.
UniSim-specific topology placeholders use UnisimCalculator; spreadsheets and
set/adjust logic are reference objects; controllers and logical operations do
not create material topology edges; column internals configure the column rather
than becoming standalone equipment. Generated JSON includes
_unisim_operation_mapping so imported cases can audit the strategy used for
each UniSim operation type present in the model.
Core Process Equipment
| UniSim TypeName | NeqSim Type | Description |
|---|---|---|
valveop |
ThrottlingValve |
Pressure letdown valve, choke |
sep3op |
ThreePhaseSeparator |
Three-phase separator |
flashtank |
Separator / GasScrubber |
Two-phase separator. Auto-promoted to ThreePhaseSeparator if WaterProduct connected. Vertical orientation → GasScrubber. |
mixerop |
Mixer |
Stream mixer/junction |
teeop |
Splitter |
Stream splitter/tee |
compressor |
Compressor |
Gas compressor |
coolerop |
Cooler |
Cooler/aftercooler |
heaterop |
Heater |
Heater/pre-heater |
pumpop |
Pump |
Liquid pump |
expandop |
Expander |
Turboexpander |
heatexop |
HeatExchanger |
Shell-and-tube / plate HX |
firedheaterop |
FiredHeater |
Fired heater / process furnace |
pipeseg |
AdiabaticPipe |
Pipe segment |
olgapipe |
AdiabaticPipe |
OLGA-link pipe; upgraded to PipeBeggsAndBrills when segment geometry is extracted |
sep1op / sep2op |
Separator |
Separator variants |
pemelectrolyzer |
Electrolyzer |
PEM electrolyzer (setTechnology(PEM)) |
alkalineelectrolyzer |
Electrolyzer |
Alkaline electrolyzer (ALKALINE) |
soecelectrolyzer |
Electrolyzer |
Solid-oxide electrolyzer (SOEC) |
recycle |
Recycle |
Recycle convergence block |
adjust |
Adjuster |
Process variable adjuster |
setop |
SetPoint |
Set variable/propagation |
saturateop |
StreamSaturatorUtil |
Stream saturator |
spreadsheetop |
SpreadsheetBlock |
Spreadsheet calculator/reference block; formulas need explicit import/export cell extraction |
templateop |
SubFlowsheet / UnisimCalculator |
Sub-flowsheet template or interface placeholder; JSON factory aliases placeholder builds to UnisimCalculator |
fluidizedcatalyticcrackertemplate |
SubFlowsheet |
FCC template |
isomerizationtemplate |
SubFlowsheet |
Isomerization template |
virtualstreamop |
UnisimCalculator |
Virtual stream/topology adapter with pass-through outlet |
streamcutterop |
UnisimCalculator |
Assay stream cutter; pass-through adapter |
fluidizedcatalyticcrackerop |
UnisimCalculator |
FCC — no NeqSim equivalent; pass-through keeps downstream topology |
isomerizationreactorop |
UnisimCalculator |
Isomerization reactor — pass-through adapter |
Columns & Absorbers
| UniSim TypeName | NeqSim Type | Description |
|---|---|---|
fractop |
DistillationColumn |
Fractionation column |
distillation |
DistillationColumn |
Distillation column |
columnop |
DistillationColumn |
Generic column |
reboiledabsorber |
DistillationColumn |
Reboiled absorber |
refluxedabsorber |
DistillationColumn |
Refluxed absorber |
ratedistillation |
DistillationColumn |
Rate-based column (equilibrium-stage approximation) |
threephasedistillation |
DistillationColumn |
Three-phase column |
absorberop |
Absorber |
Absorption column (see glycol note below) |
absorber |
Absorber |
Absorber (see glycol note below) |
Glycol/TEG Contactor Rule: When an Absorber operation has a name containing "glyc", "teg", or "dehydrat" (case-insensitive), the code generator produces a
ComponentSplitterinstead of aDistillationColumn. This removes water from the gas stream using the standard pattern:setSplitFactors([1.0] * (N-1) + [0.0])where water is the last component. Stream 0 = dry gas, stream 1 = removed water. Port resolution usessplit0/split1instead ofgasOut/liquidOut. Non-glycol absorbers still useDistillationColumn.
Column Internals (Sub-parts, Not Standalone)
| UniSim TypeName | NeqSim Type | Description |
|---|---|---|
partialcondenser |
ColumnInternals |
Partial condenser (skipped) |
totalcondenser |
ColumnInternals |
Total condenser (skipped) |
condenser3op |
ColumnInternals |
Three-outlet condenser (skipped) |
traysection |
ColumnInternals |
Tray section (skipped) |
bpreboiler |
ColumnInternals |
Reboiler (skipped) |
Reactors
| UniSim TypeName | NeqSim Type | Description |
|---|---|---|
reactorop |
GibbsReactor |
Generic reactor → Gibbs |
gibbsreactorop |
GibbsReactor |
Gibbs reactor |
eqreactorop |
GibbsReactor |
Equilibrium reactor → Gibbs |
equilibriumreactorop |
GibbsReactor |
Equilibrium reactor (alternate) |
convreactorop |
GibbsReactor |
Conversion reactor → Gibbs |
conversionreactorop |
GibbsReactor |
Conversion reactor (alternate) |
pfreactorop |
PlugFlowReactor |
Plug flow reactor |
kineticreactorop |
PlugFlowReactor |
Kinetic reactor → PFR |
cstrop |
StirredTankReactor |
CSTR |
gasifierop / gasifieroppy / gsfrxsecop |
GibbsReactor |
Gasifier blocks; the solid (coal/biomass) feed cannot be flashed, so the fluid feed is equilibrated |
Controllers & Logic
| UniSim TypeName | NeqSim Type | Description |
|---|---|---|
pidfbcontrolop |
PIDController |
PID feedback controller |
surgecontroller |
SurgeController |
Surge controller (skipped) |
selectionop / fanoutop |
LogicalOp |
Signal selector / fan-out; no material topology |
genesimop / machinelearningtoolop |
skipped | Non-physical utilities |
balanceop |
UnisimCalculator |
Balance/topology adapter with pass-through outlet and source-operation metadata |
logicalop |
LogicalOp |
Logic operation (skipped) |
selectop |
LogicalOp |
Selector (skipped) |
Handler Strategies and Skipped Operations
The handler registry determines whether material topology is created. The following types are recognized but do not become standalone material equipment:
SurgeController— Compressor surge control logicColumnInternals— Sub-parts of column operations (condenser, reboiler, tray sections)LogicalOp— logical/select operations produce comments or controller metadataBlowdownGeneSim— non-physical UniSim utility operation
Use UniSimReader.is_material_stream_operation(type_name) when modifying
topology reconstruction. Unknown operation types are treated as material stream
operations so skipped stream-carrying blocks remain visible as warnings.
3. Component Name Mapping
UniSim component names to NeqSim database names:
| UniSim Name | NeqSim Name |
|---|---|
Nitrogen |
nitrogen |
CO2 |
CO2 |
Methane |
methane |
Ethane |
ethane |
Propane |
propane |
i-Butane |
i-butane |
n-Butane |
n-butane |
i-Pentane |
i-pentane |
n-Pentane |
n-pentane |
n-Hexane |
n-hexane |
n-Heptane |
n-heptane |
n-Octane |
n-octane |
n-Nonane |
n-nonane |
n-Decane |
nC10 |
H2O |
water |
EGlycol |
MEG |
TEGlycol |
TEG |
DEGlycol |
DEG |
MeOH |
methanol |
Hydrogen |
hydrogen |
H2S |
H2S |
Oxygen |
oxygen |
Argon |
argon |
Helium |
helium |
nC11–nC24 |
nC11–nC24 |
Benzene |
benzene |
Toluene |
toluene |
E-Benzene |
ethylbenzene |
m-Xylene |
m-Xylene |
o-Xylene |
o-Xylene |
p-Xylene |
p-Xylene |
COS |
COS |
SO2 |
SO2 |
NH3 / Ammonia |
ammonia |
Ethylene / Ethene |
ethylene |
Propylene / Propene |
propene |
1-Butene |
1-butene |
cis-2-Butene |
c2-butene |
trans-2-Butene |
t2-butene |
Isobutene |
isobutene |
Cyclohexane |
cyclohexane |
CO / CarbonMonoxide |
CO |
DEAmine |
DEA |
MEAmine |
MEA |
MDEAmine |
MDEA |
AceticAcid |
acetic acid |
Ethanol |
ethanol |
c-Hexane |
c-hexane |
Alternate aliases: The map also includes short-form aliases like C1→methane, C2→ethane, N2→nitrogen, H2→hydrogen, O2→oxygen, Ar→argon, He→helium, iC4→i-butane, nC4→n-butane, iC5→i-pentane, nC5→n-pentane, nC6→n-hexane, etc.
Unmapped components: 12C3Oxide (propylene oxide) maps to None — it is not in the NeqSim database and will be skipped with a warning.
Hypothetical Components
UniSim components ending with * are hypothetical (pseudo-components), e.g.:
C6 GRAND*,C7 GRAND*, ...,C55-C80 GRAND*251116-01*,251115-01*(numbered hypos)
These require C7+ characterization in NeqSim. Strategies:
- Skip: Remove hypos from composition, re-normalize known components
- Approximate: Map to nearest real component by molecular weight
- Characterize: Use NeqSim's
characterisePlusFraction()with MW and density data
4. Property Package Mapping
The code variable is PROPERTY_PACKAGE_MAP (not EOS_MAP). It maps UniSim
property package names (including common spelling variants) to NeqSim EOS
model strings:
Primary Mappings
| UniSim Property Package | NeqSim EOS Model | Mixing Rule | Notes |
|---|---|---|---|
Peng-Robinson / PengRobinson / Peng Robinson |
PR |
classic |
|
Peng-Robinson - LK / Peng Robinson - LK |
PR |
classic |
|
SRK / Soave-Redlich-Kwong |
SRK |
classic |
|
CPA / CPA-SRK |
CPA |
10 |
For polar systems (water, glycols, amines) |
Glycol Package |
CPA |
10 |
Maps to CPA for MEG/TEG |
GERG 2008 |
GERG2008 |
(built-in) | Natural gas |
Sour PR / SourPR |
PR |
classic |
H2S/CO2 systems |
Sour SRK |
SRK |
classic |
H2S/CO2 systems |
Fallback Mappings (Approximated as SRK)
These UniSim packages have no direct NeqSim equivalent and fall back to SRK:
| UniSim Property Package | NeqSim Fallback | Notes |
|---|---|---|
ASME Steam |
SRK |
Water only |
MBWR |
SRK |
NeqSim has BWRS but limited components |
Lee-Kesler-Plocker |
SRK |
No LKP model |
NRTL / UNIQUAC / UNIQUAC - Ideal / Wilson |
SRK |
Activity models |
Zudkevitch Joffee / Kabadi Danner |
SRK |
Specialized EOS |
Antoine / Chao Seader / Grayson Streed |
SRK |
Legacy correlations |
DBR Amine Package |
SRK |
DBR proprietary |
OLI |
SRK |
Electrolyte package |
COMPropertyPkg |
SRK |
COM extension package |
A warning is logged when a fallback mapping is used.
5. Workflow: From .usc File to Running NeqSim Model
Full Mode (Default — Recommended)
All four output methods (to_json(), build_and_run(), to_python(),
to_notebook()) default to full_mode=True. This means:
Sub-flowsheet auto-classification: Sub-flowsheets are classified as either "process" (shares streams with the main flowsheet) or "utility" (isolated). Only process sub-flowsheets are included.
ProcessModel architecture: The main flowsheet and each process sub-flowsheet become separate
ProcessSystemobjects composed inside aProcessModel(multi-area plant model).E300 fluid loading: When the
UniSimReader.read(export_e300=True)option was used (default), the converter usesEclipseFluidReadWrite.read()to load the fluid with exact Tc, Pc, ω, MW, and BIPs from UniSim.Recycle convergence: Auto-generated
Recycleobjects (into_python()/to_notebook()) get a real tolerance (recycle_tolerance, default1e-2) plus Wegstein acceleration (recycle_acceleration, default"WEGSTEIN"), and the generated run tail iterates (setRunStep(True)loop, then a final run) so the tear streams actually converge. A very large tolerance (the old1e6) accepted the seeded tear on the first pass, so recycle-fed streams never updated and deviated strongly from UniSim. Tune via:converter = UniSimToNeqSim(model) converter.recycle_tolerance = 1e-3 # tighter tear converter.recycle_acceleration = "WEGSTEIN" # or None to disable python_code = converter.to_python()Unfed columns/absorbers are skipped: A
DistillationColumn/Absorberwhose feed stream could not be extracted from UniSim (UniSimfractop/absorberCOM connectivity is not always resolvable) is omitted rather than emitted as a bare, feed-less column. A feed-less column throws onrun()and aborts the wholeprocess.run(), so every downstream unit stops executing and stays at its seed value — this was the single biggest cause of a "runs but nothing matches" result. The skipped column is reported inconverter.warnings; reconnect its feed manually to include it.
Verification vehicle —
to_python()is still the most faithful path, but the JSON/MCP path now iterates recycles. The JSON path (build_and_run()/ProcessSystem.fromJsonAndRun/ MCPrunProcess) emits recycles with only aninletand relies onJsonProcessBuilder's Pass-2 iterative wiring to close forward-referenced tears. Since the multi-pass auto-run fix, when the built processhasRecycles()the builder loopsprocess.run()up toMAX_AUTORUN_PASSES(15), guarding each pass (a unit throwing on an early pass no longer aborts the whole auto-run) and stopping early onprocess.solved()— so nested/forward-referenced recycle loops seeded at ~0 flow now get the outer passes they need to converge on the JSON/MCP path too. The generated Python (to_python()) additionally seeds forward-reference placeholders + auto-Recycle, so it remains the most robust vehicle for a recycle-heavy plant; compare withUniSimComparator(model, process)afterexec()-ing the generated script.Residual JSON-path failures are model-specific topology gaps, not recycle convergence. If the full plant still stops (e.g. a pipe
Failed to run … — Total mass cannot be zero), trace the dead branch: the cause is usually a genuinely external UniSim stream with no producer (aValve leak-type feed), or a zero-feed pipe/riser fed by a scrubber whose own inlet mixer is only partially wired (Mixer … wired with N of M inlets). These need the missing inlet wired manually — no number of recycle passes fixes a stream that no unit produces.ProcessSystem.run()still aborts a pass at the first throwing unit, so a single unfeedable unit blocks everything downstream in that pass.
To disable full mode and get only the main flowsheet operations:
converter = UniSimToNeqSim(model)
python_code = converter.to_python(full_mode=False)
Quick Usage (Python)
from devtools.unisim_reader import UniSimReader, UniSimToNeqSim, UniSimComparator
# Step 1: Read the UniSim file (export_e300=True is default)
with UniSimReader(visible=False) as reader:
model = reader.read(r"path\to\file.usc")
# Step 2: Inspect what was extracted
print(model.summary())
# Step 3: Convert to NeqSim JSON (full_mode=True by default)
converter = UniSimToNeqSim(model)
neqsim_json = converter.to_json()
# Step 4: View warnings and assumptions
for w in converter.warnings:
print(f"WARNING: {w}")
# Step 5: Build and run in NeqSim
import json
from neqsim import jneqsim
ProcessSystem = jneqsim.process.processmodel.ProcessSystem
result = ProcessSystem.fromJsonAndRun(json.dumps(neqsim_json))
# Check for partial success (tolerant error handling)
if result.hasWarnings():
print(f"Warnings: {len(list(result.getWarnings()))}")
for w in result.getWarnings():
print(f" [{w.getCode()}] {w.getMessage()}")
if not result.isError():
process = result.getProcessSystem()
print(f"Units built: {process.size()}")
Generate Python Code (Human-Readable Alternative)
Instead of JSON, generate a standalone Python script with explicit jneqsim API calls:
converter = UniSimToNeqSim(model)
python_code = converter.to_python() # full_mode=True by default
# Save to file
with open("process.py", "w") as f:
f.write(python_code)
print(f"Generated {len(python_code.splitlines())} lines of Python")
The generated script is a complete, runnable Python file that includes:
- All
jneqsimimports (thermo systems, equipment classes) - Fluid/EOS definition with mapped composition and mixing rule
- Feed streams created with temperature, pressure, and flow rate from UniSim
- All equipment in topological order (upstream before downstream)
- Equipment properties set via
jneqsimAPI
…(truncated)