| name | neqsim-unisim-reader |
| description | Reads Honeywell UniSim Design / Aspen HYSYS .usc files via COM automation and converts them to NeqSim ProcessSystem / ProcessModule structures. USE WHEN: a user has UniSim/HYSYS simulation files and wants to recreate or compare the model in NeqSim. Covers COM API navigation, component mapping, E300 fluid transfer, operation-handler registry strategy, topology reconstruction, sub-flowsheet handling, and result verification. |
| last_verified | 2026-07-04 |
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 |
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 | |
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.
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
Product singular (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 = False before reading to prevent recalculation
- UniSim uses -32767 for empty/unset values — filter these out
- Use
time.sleep() between COM calls if stability issues arise
- Property values accessed via
.GetValue(unit_string)
- Composition accessed via
.GetValues() returning a sequence
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
raw = kij_obj.Values
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:
val = 0.0
row.append(val)
bic.append(row)
Key discoveries:
pp.Kij returns a CDispatch (UniSim RealFlexVariable), NOT a Python-iterable
kij_obj.Values is the correct access pattern — returns tuple-of-tuples
kij_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()
tc = comp.CriticalTemperature.GetValue("C") + 273.15
pc = comp.CriticalPressure.GetValue("kPa") * 0.01
nbp = comp.NormalBoilingPt.GetValue("C") + 273.15
vc = comp.CriticalVolume.GetValue("m3/kgmole")
Notes:
- UniSim component collections can be 0-based or 1-based depending on the COM
collection surface. If
Components.Item(i) fails, retry Components.Item(i+1)
before dropping the component.
- For component critical properties, request
CriticalTemperature and
NormalBoilingPoint in C first and convert to K; request CriticalPressure
in kPa first 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 is NOT directly accessible via COM for all property packages.
comp.AcentricFactor may not exist. Try property-package vectors
(AcentricFactor, AcentricFactors, Omega, ACF) first, then use the
Edmister correlation as fallback when Tc, Pc, and normal boiling point exist.
- 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.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 names
TCRIT — critical temperatures (K)
PCRIT — critical pressures (bar/bara as used by NeqSim's E300 writer)
ACF — acentric factors
MW — 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 / → SystemSrkEos
EOS\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
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 → omega
component.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.
reader = UniSimReader()
model = reader.read(r'C:\path\to\model.usc')
for fp in model.fluid_packages:
print(f" {fp.name}: {fp.e300_file_path}")
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 |
pipeseg | AdiabaticPipe | Pipe segment |
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 |
virtualstreamop | UnisimCalculator | Virtual stream/topology adapter with pass-through outlet |
Columns & Absorbers
| UniSim TypeName | NeqSim Type | Description |
|---|
fractop | DistillationColumn | Fractionation column |
distillation | DistillationColumn | Distillation column |
columnop | DistillationColumn | Generic column |
reboiledabsorber | DistillationColumn | Reboiled absorber |
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 ComponentSplitter instead of a DistillationColumn.
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 uses
split0/split1 instead of gasOut/liquidOut. Non-glycol absorbers
still use DistillationColumn.
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 |
Controllers & Logic
| UniSim TypeName | NeqSim Type | Description |
|---|
pidfbcontrolop | PIDController | PID feedback controller |
surgecontroller | SurgeController | Surge controller (skipped) |
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 logic
ColumnInternals — Sub-parts of column operations (condenser, reboiler, tray sections)
LogicalOp — logical/select operations produce comments or controller metadata
BlowdownGeneSim — 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 ProcessSystem objects composed inside a
ProcessModel (multi-area plant model).
-
E300 fluid loading: When the UniSimReader.read(export_e300=True)
option was used (default), the converter uses EclipseFluidReadWrite.read()
to load the fluid with exact Tc, Pc, ω, MW, and BIPs from UniSim.
-
Recycle convergence: Auto-generated Recycle objects (in to_python() /
to_notebook()) get a real tolerance (recycle_tolerance, default 1e-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 old
1e6) 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
converter.recycle_acceleration = "WEGSTEIN"
python_code = converter.to_python()
-
Unfed columns/absorbers are skipped: A DistillationColumn/Absorber
whose feed stream could not be extracted from UniSim (UniSim fractop/
absorber COM connectivity is not always resolvable) is omitted rather
than emitted as a bare, feed-less column. A feed-less column throws on
run() and aborts the whole process.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
in converter.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 / MCP runProcess) emits recycles with only an
inlet and relies on JsonProcessBuilder's Pass-2 iterative wiring to close
forward-referenced tears. Since the multi-pass auto-run fix, when the built
process hasRecycles() the builder loops process.run() up to
MAX_AUTORUN_PASSES (15), guarding each pass (a unit throwing on an early
pass no longer aborts the whole auto-run) and stopping early on
process.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 with UniSimComparator(model, process) after
exec()-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 (a Valve 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
with UniSimReader(visible=False) as reader:
model = reader.read(r"path\to\file.usc")
print(model.summary())
converter = UniSimToNeqSim(model)
neqsim_json = converter.to_json()
for w in converter.warnings:
print(f"WARNING: {w}")
import json
from neqsim import jneqsim
ProcessSystem = jneqsim.process.processmodel.ProcessSystem
result = ProcessSystem.fromJsonAndRun(json.dumps(neqsim_json))
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()
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
jneqsim imports (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
jneqsim API calls (efficiency, outlet pressure, etc.)
- Stream wiring through outlet stream references (e.g.,
separator.getGasOutStream())
- Sub-flowsheet operations (included by default in
full_mode=True)
process.run() call at the end
The generated code uses direct NeqSim Java API calls — no JSON intermediate.
Equipment that cannot be mapped (reactors, controllers, spreadsheets) is commented
out with a skip reason. This output is ideal for:
- Code review — every connection is visible and auditable
- Manual editing — users can modify equipment parameters, add controllers
- Learning — shows the exact NeqSim API mapping for each UniSim operation
Example for a large platform model: to_python() generates ~850 lines
covering ~180 operations including Splitters, ThreePhaseSeparators, Compressors,
Coolers, Mixers, ThrottlingValves, and sub-flowsheet equipment.
Generate Jupyter Notebook (Interactive Version)
The notebook uses the exact same shared code generators as to_python() —
so the resulting process logic is always identical — but wraps equipment in
separate cells with markdown explanations (descriptions, feed tables, model
overview):
converter = UniSimToNeqSim(model)
converter.save_notebook("process.ipynb")
Or get the raw dict (nbformat v4):
nb_dict = converter.to_notebook()
The notebook contains:
- Title & overview table — components, operations, feed count, EOS
- Setup cell —
from neqsim import jneqsim + class aliases
- Fluid markdown + code — composition table and fluid creation
- Feed streams markdown + code — T/P/flow table and stream creation
- Equipment cells — one markdown description + one code cell per unit
- Run cell —
process.run()
- Results cell — prints T, P for every unit operation
- Warnings cell — any conversion assumptions or skipped items
Generate EOT / ProcessPilot Simulator
Generate a BaseSimulator subclass for the ProcessPilot-NeqSimInterface
framework (reinforcement learning / optimization):
converter = UniSimToNeqSim(model)
converter.save_eot_simulator("my_simulator.py", class_name="MySimulator")
The generated code:
- Subclasses
eot.simulators.base_simulator.BaseSimulator
- Uses
eot.components factory functions (get_stream, get_compressor, etc.)
- Implements
build_process() with all equipment
- Reports
name property from the UniSim file name
- Falls back to raw
jneqsim calls for equipment not covered by EOT factories
EOT demo notebook — shows how to instantiate, run, and step the simulator:
nb = converter.to_eot_notebook(class_name="MySimulator")
import json
with open("eot_demo.ipynb", "w") as f:
json.dump(nb, f, indent=1)
Code Sharing Architecture
The to_python(), to_notebook(), and to_eot_simulator() methods all share
the same internal code generators:
| Shared Method | Purpose |
|---|
_prepare_topology() | Topological sort, variable naming, stream resolution |
_gen_fluid_lines() | Fluid creation code (EOS, components, mixing rule) |
_gen_feed_lines() | Feed stream setup (T, P, flow, process.add()) |
_gen_equipment_lines() | Per-equipment code (properties, wiring, process.add()) |
_to_pyvar() | Convert arbitrary name to valid Python identifier |
_unique_var() | Assign unique variable name avoiding collisions |
_outlet_ref() | Resolve dot-notation outlet references |
This ensures that Python scripts, notebooks, and EOT simulators always
produce the same process logic — only the wrapping differs.
For Complex Models (Sub-Flowsheets → ProcessModule)
Large UniSim models with sub-flowsheets should be decomposed:
neqsim_json = converter.to_json(include_subflowsheets=True)
main_process = neqsim_json['process']
for sf_name, sf_process in neqsim_json.get('sub_flowsheets', {}).items():
print(f"Sub-flowsheet '{sf_name}': {len(sf_process)} operations")
6. Result Verification
Always compare UniSim and NeqSim results after conversion:
comparator = UniSimComparator(model, neqsim_process)
comparisons = comparator.compare_streams()
comparator.print_report(comparisons)
Expected Deviations
| Property | Typical Deviation | Acceptable | Notes |
|---|
| Temperature | < 1 °C | < 3 °C | Flash calculation differences |
| Pressure | 0% | 0% | Should match exactly (input) |
| Mass flow | < 0.1% | < 1% | Mass balance differences |
| Density | < 2% | < 5% | EOS differences (PR vs SRK) |
| Vapour fraction | < 0.02 | < 0.05 | Phase split sensitivity |
| Compressor power | < 5% | < 10% | Efficiency model differences |
| Heat duty | < 5% | < 10% | Enthalpy model differences |
Factors Causing Deviations
- EOS differences: UniSim PR-LK uses PR76 alpha (
m = 0.37464 + 1.54226ω − 0.26992ω²)
for ALL components, including those with ω > 0.49 (where PR78 uses a different cubic).
Fix: Use SystemPrLeeKeslerEos in NeqSim — add PRLKCORR line after EOS PR in E300 file.
This typically reduces vapour-fraction bias from −1.6% to < 0.3%.
- BIP (binary interaction parameters): UniSim PR-LK correlation BIPs are non-zero even for
pure-prediction (never user-tuned). Extract via
pp.Kij.Values (Section 1.1) and include in
E300 BIC section. Without correct BIPs, vapour fraction can differ by 5%+ and oil MW by 100%+.
- Water BIPs: PR-LK BIPs for H2O–N2 (≈−2.24), H2O–CO2 (≈−0.557), H2O–H2S (≈−0.390)
are negative (increase cross-attraction → keep water in liquid). If a file has both
water BIPs AND OMEGAA overrides, they must be used as a matched set. Using standard BIPs
with modified OmegaA causes catastrophic phase split errors.
- Water distribution (multi-stage): In UniSim with recycles, water may split across
multiple separators. NeqSim forward-flow models capture water mainly at the first stage.
+70% water flow error at secondary stages is structurally expected if recycles are missing.
- Hypothetical components: Pseudo-component property estimation differs between simulators.
Critical properties and acentric factor estimation methods vary.
- Compressor efficiency: UniSim COM sometimes returns
None for AdiabaticEfficiency.
Always check extracted efficiency values; default to 75% isentropic with a warning.
- Mixing rules: UniSim may use advanced mixing rules not available in NeqSim.
- Transport properties: Different viscosity/thermal conductivity correlations.
- Convergence: Different solver algorithms, tolerance settings, and recycle initialization.
7. Stream Topology Reconstruction
UniSim operations reference streams by name via .Feeds[] and .Products[].
The converter reconstructs the process topology:
- Identify external feeds: Streams not produced by any operation
- Build producer map:
stream_name → (operation_name, port)
- Topological sort: Kahn's algorithm to order operations by dependency
- Handle cycles: Recycle blocks break dependency loops
Dot-notation for NeqSim wiring
| Producer Type | Stream | NeqSim Reference |
|---|
| Separator (gas) | 1st product | "Sep.gasOut" |
| Separator (liquid) | 2nd product | "Sep.liquidOut" |
| 3-Phase Sep (gas) | 1st product | "Sep.gasOut" |
| 3-Phase Sep (oil) | 2nd product | "Sep.oilOut" |
| 3-Phase Sep (water) | 3rd product | "Sep.waterOut" |
| Compressor | output | "Comp.outlet" |
| Cooler/Heater | output | "Cooler.outlet" |
| Valve | output | "Valve.outlet" |
| Mixer | output | "Mixer.outlet" |
Forward Reference Handling (Recycle Loops)
When the topological sort detects cycles (equipment B referenced before it is
defined because B depends on equipment A which depends on B), the converter
creates forward reference placeholders — temporary Stream objects that
stand in for the not-yet-created equipment outlets.
How It Works
-
Cycle detection (_prepare_topology): Operations in a cycle are
identified. The back-edge producers become forward references stored in
topo['fwd_ref_placeholders'].
-
Placeholder registration (_register_fwd_placeholders): For each
forward-referenced producer, a _fwd_XXX variable is created. For
multi-outlet equipment (Separator, ThreePhaseSeparator), port-specific
placeholders are also created:
| Equipment | Placeholders Created |
|---|
V-100 (Separator) | _fwd_V_100 (generic), _fwd_V_100_gasOut, _fwd_V_100_liquidOut |
S-200 (ThreePhaseSeparator) | _fwd_S_200 (generic), _fwd_S_200_gasOut, _fwd_S_200_oilOut, _fwd_S_200_waterOut |
K-100 (Compressor) | _fwd_K_100 (generic only) |
-
Placeholder stream creation (in to_python / to_notebook /
to_eot_simulator): Each placeholder is created as a Stream with
temperature, pressure, and flow rate from the UniSim stream data for
that product outlet. Port-specific placeholders use the stream data for
each individual outlet.
-
Outlet resolution (_outlet_ref): When downstream equipment
references a forward-referenced separator's liquid outlet (e.g.
"V-100.liquidOut"), the resolver first checks for a port-specific key
(V-100.liquidOut) in fwd_ref_vars before falling back to the
generic placeholder.
-
Auto-Recycle wiring (_gen_equipment_lines): After the actual
separator is created and added to the process, Recycle objects are
automatically generated to wire each actual outlet back to its
forward reference placeholder:
_rcy_V_100_gasOut = Recycle("V-100_gasOut_loop")
_rcy_V_100_gasOut.addStream(V_100.getGasOutStream())
_rcy_V_100_gasOut.setOutletStream(_fwd_V_100_gasOut)
process.add(_rcy_V_100_gasOut)
Why Port-Specific Placeholders Matter
Without port-specific placeholders, a valve downstream of a separator's
liquid outlet would incorrectly receive the combined feed placeholder
(with gas + liquid flow). This caused 500%+ flow deviations in early
versions. Port-specific placeholders ensure each downstream unit gets
the correct phase with approximately correct T, P, and flow.
HeatExchanger Outlet Port Resolution
HeatExchanger has two feed/product sides indexed 0 (Shell) and 1 (Tube).
The converter maps downstream references to getOutStream(int(0)) or
getOutStream(int(1)) based on the product's position in the products
list (set by _extract_heatexchanger: ShellSideProduct = index 0,
TubeSideProduct = index 1).
Port naming convention: hx0 and hx1 (analogous to gasOut/liquidOut
for separators). Forward-reference placeholders are created per-side when
a HeatExchanger is in a recycle loop.
Generated code example:
E_100 = HeatExchanger("E-100")
E_100.setFeedStream(0, shell_feed)
E_100.setFeedStream(1, tube_feed)
process.add(E_100)
C_100 = Cooler("C-100", E_100.getOutStream(int(0)))
VLV_100 = ThrottlingValve("VLV-100", E_100.getOutStream(int(1)))
Important: Do NOT use getOutletStream() for HeatExchanger — it only
returns one side. Always use getOutStream(int(index)) for explicit
side selection.
Compressor Efficiency Defaults
When compressor efficiency is not available from the UniSim COM extraction
(returns None), the code applies these rules:
- If the extracted value is > 1.0, it's treated as a percentage and
converted to a fraction (e.g., 75 → 0.75)
- If adiabatic efficiency is available (0 < eff ≤ 1), it's set via
setIsentropicEfficiency()
- If only polytropic efficiency is available, it's set via
setPolytropicEfficiency() + setUsePolytropicCalc(True)
- If neither efficiency is available, a 75% isentropic default is
applied with a warning comment in the generated code
This matters because NeqSim defaults to 100% isentropic efficiency,
which produces unrealistically low outlet temperatures.
Separator Phase Detection and Entrainment Extraction
The reader now detects 2-phase vs 3-phase separators by two mechanisms:
- UniSim TypeName:
flashtank → Separator (2-phase),
sep3op → ThreePhaseSeparator (3-phase)
- WaterProduct heuristic: If a
flashtank has a WaterProduct
connected, it is automatically promoted to ThreePhaseSeparator
Orientation Detection (Vertical → GasScrubber)
The reader extracts separator orientation from UniSim COM attributes
(Orientation, VesselOrientation, SeparatorOrientation). When a
flashtank is detected as vertical, the NeqSim type is mapped to
GasScrubber instead of Separator.
| UniSim Type | Orientation | NeqSim Type |
|---|
flashtank | horizontal (default) | Separator |
flashtank | vertical | GasScrubber |
flashtank + WaterProduct | any | ThreePhaseSeparator |
sep3op | any | ThreePhaseSeparator |
GasScrubber extends Separator in NeqSim — it is a vertical vessel
optimised for removing liquid droplets from a gas stream, with K-value
sizing constraints and a default 10% liquid level.
Entrainment is extracted from UniSim COM and mapped to NeqSim
setEntrainment() calls. The reader tries multiple COM attribute names
for each entrainment direction:
| Entrainment Direction | UniSim COM Attributes (tried in order) | NeqSim setEntrainment Args |
|---|
| Liquid in gas (oil carryover) | LiqCarryOverMolFrac, LiqCarryOverFrac, LiquidInVapourFraction, LiqInVap, LiquidCarryover | (val, "volume", "product", "oil", "gas") |
| Gas in liquid (gas carry-under) | VapCarryUnderMolFrac, VapCarryUnderFrac, VapourInLiquidFraction, VapInLiq, VapourCarryunder | (val, "volume", "product", "gas", "liquid") |
| Water in oil (3-phase) | WaterInOilFraction, WaterInOil, AqInOil, AqueousInOilFraction | (val, "volume", "product", "aqueous", "oil") |
| Oil in water (3-phase) | OilInWaterFraction, OilInWater, OilInAq, OilInAqueousFraction | (val, "volume", "product", "oil", "aqueous") |
Generated Python code example:
mp_sep = ThreePhaseSeparator("20VA102", heater_mp.getOutletStream())
mp_sep.setEntrainment(0.084, "volume", "product", "aqueous", "oil")
mp_sep.setEntrainment(0.002, "volume", "product", "oil", "aqueous")
process.add(mp_sep)
Note: If the UniSim COM does not expose entrainment attributes (some
model versions or configurations may not), the extraction silently skips
them — the separator will use NeqSim defaults (zero entrainment).
8. Handling Sub-Flowsheets
UniSim uses sub-flowsheets (template operations) for modular process sections.
In NeqSim, these map to either:
- Separate ProcessSystem objects composed in a
ProcessModel
- Flattened into the main ProcessSystem (simpler but may not handle inter-area recycles)
Auto-Classification (full_mode=True, Default)
When full_mode=True (the default), the converter automatically classifies
sub-flowsheets into process (shares material streams with the main
flowsheet) and utility (isolated, e.g., heating/cooling medium loops).
Only process sub-flowsheets are included in the generated code.
Classification is done by classify_subflowsheets():
classification = converter.classify_subflowsheets()
process_sfs = converter.get_process_subflowsheets()
A sub-flowsheet is classified as process if any of its operations produce
or consume a stream that also appears in the main flowsheet or another process
sub-flowsheet. Otherwise it is utility (typically heating/cooling medium,
flare, utility water systems).
Architecture Decision
| Model Complexity | Strategy |
|---|
| Main + 1-2 small sub-flowsheets | Flatten into single ProcessSystem |
| Main + 3+ sub-flowsheets | ProcessModel with separate ProcessSystems |
| Sub-flowsheet has own fluid package | Must be separate ProcessSystem |
Sub-Flowsheet to ProcessModel Mapping
In full_mode=True, each process sub-flowsheet becomes its own
ProcessSystem, all composed inside a ProcessModel:
from neqsim import jneqsim
ProcessModel = jneqsim.process.processmodel.ProcessModel
plant = ProcessModel("Platform")
plant.add("Main", main_process)
plant.add("TPL1", tpl1_process)
plant.add("TPL2", tpl2_process)
plant.add("COL1", col1_process)
plant.run()
9. Command-Line Interface
python devtools/unisim_reader.py path/to/file.usc --summary
python devtools/unisim_reader.py path/to/file.usc --json
python devtools/unisim_reader.py path/to/file.usc --python process.py
python devtools/unisim_reader.py path/to/file.usc --notebook process.ipynb
python devtools/unisim_reader.py path/to/file.usc --eot my_sim.py --eot-class MySimulator
python devtools/unisim_reader.py path/to/file.usc --eot-notebook eot_demo.ipynb
python devtools/unisim_reader.py path/to/file.usc --save extracted.json
python devtools/unisim_reader.py path/to/file.usc --no-streams --summary
python devtools/unisim_reader.py path/to/file.usc --python p.py --notebook n.ipynb --eot s.py
python devtools/unisim_reader.py path/to/file.usc --visible --summary
10. Known Limitations
- Windows only — COM automation requires Windows + UniSim installed
- Hypothetical components — pseudo-components need manual C7+ characterization
- Tuned BIPs — Extractable via
pp.Kij.Values (see Section 1.1). The
unisim_reader.py does not yet automate this, but manual extraction is
straightforward. The returned matrix uses -32767.0 as a diagonal sentinel.
- Column internals — distillation column tray/packing details not fully mapped
- Dynamic models — only steady-state data extracted
- Control logic — PID controllers produce TODO comments, not functional controllers
- Custom correlations — UniSim's user-defined correlations not transferred
- Performance curves — compressor/pump performance maps not extracted
- Multiple fluid packages — the reader exports one E300 file per fluid
package, but most generated ProcessSystem builds still use the default
process fluid unless per-stream or per-sub-flowsheet package assignment is
explicitly wired. Treat multi-package models as verified only after the
generated JSON/Python shows the expected
e300FilePath for each area and
the build route loads E300 through EclipseFluidReadWrite.read(...).
- Absorber columns — single-feed only; multi-feed absorbers show a TODO.
Glycol/TEG contactors (name contains "glyc", "teg", or "dehydrat") are
modeled as
ComponentSplitter for water removal instead of DistillationColumn
- SetPoint / Adjuster wiring — generates skeleton code but wiring is often incomplete
- Recycle convergence — heavily circular models (5+ forward references) may
not converge with placeholder initial values; multiple
process.run() calls
or manual tuning may be needed
- Compressor efficiency — COM extraction sometimes returns
None even when
the UniSim model has efficiency data; defaults to 75% isentropic
- Spreadsheet operations — represented as
SpreadsheetBlock, but formula
fidelity depends on extracting import/export cells and formulas from COM.
- Logical operations — produce comments/controller metadata; no executable
process logic is transferred.
- Balance, virtual-stream, and template placeholders — represented through
UnisimCalculator/SubFlowsheet adapters to preserve topology. This solves
structural build gaps but not detailed specification or formula semantics.
- DistillationColumn solver divergence — NeqSim's sequential-substitution and
inside-out column solvers diverge for C3/C4-rich (NGL-range) feeds at low
pressure. Feeds with < 30% methane and significant C3+ fractions will not
converge. Lighter feeds (e.g. deethanizer with 51% CH4) converge reliably.
Build the column outside the
ProcessSystem to avoid re-run divergence.
See the TUTOR1 notebook for a worked example.
- HeatExchanger pressure drops — NeqSim
HeatExchanger does not model
pressure drops; outlet pressures equal inlet pressures. UniSim models
typically include 0.5–1.0 bar pressure drop per side. This causes small
temperature deviations in downstream equipment.
- HeatExchanger UA tuning — The
HeatExchanger.setUAvalue() parameter
must be tuned to match UniSim's heat duty. Counter-current heat balance
differences between UniSim and NeqSim typically produce 1–2°C deviation
on outlet temperatures.
- Full ProcessModel timeout — Large models with 5+ sub-flowsheets, 10+
recycles, and Adjusters may time out during
plant.run() even with relaxed
recycle tolerances. Root cause is typically the combination of Adjuster
iteration, absorber column convergence, and multi-area coordination.
Workaround: Test individual ProcessSystem areas or connected sub-paths
first, then build up incrementally. The connected main-path approach (no
recycles, manual feed data) runs in < 1 second for even large models.
- Separator liquid MW deviation — When UniSim separators have
has_water_product=False (2-product), the NeqSim Separator includes
water in the liquid phase. This causes liquid MW to be lower than UniSim's
value (water dilutes the MW). Using ThreePhaseSeparator overcorrects by
removing ALL water. Expect 20-40% liquid MW deviation at low/medium
pressures. Gas MW and temperatures are much more accurate.
- Utility sub-flowsheets excluded — In
full_mode=True, sub-flowsheets
classified as "utility" (no shared streams with process flowsheet) are
excluded. This is correct for heating/cooling medium loops but may
miss utility systems that interact with process streams through
non-standard connections.
- E300 fluid parity is necessary but not sufficient — A full-fluid E300
route fixes component-property transfer, but it does not reconcile UniSim
virtual streams, spreadsheet/balance calculations, template units, or
sub-flowsheet interface wiring. Keep the verification report split into
separate statuses for fluid export/use, structural build, and numerical
stream matching.
11. Data Class Reference
The extracted UniSim model uses these Python dataclasses:
UniSimComponent
@dataclass
class UniSimComponent:
name: str
index: int
is_hypothetical: bool
UniSimFluidPackage
@dataclass
class UniSimFluidPackage:
name: str
property_package: str
components: List[UniSimComponent]
@property
def component_names(self) -> List[str]: ...
UniSimStreamData
@dataclass
class UniSimStreamData:
name: str
temperature_C: Optional[float]
pressure_bara: Optional[float]
mass_flow_kgh: Optional[float]
molar_flow_kgmolh: Optional[float]
vapour_fraction: Optional[float]
mass_density_kgm3: Optional[float]
molecular_weight: Optional[float]
enthalpy_kJkg: Optional[float]
composition: Optional[Dict[str, float]]
n_phases: Optional[int]
viscosity_cP: Optional[float]
thermal_conductivity: Optional[float]
specific_heat_kJkgC: Optional[float]
UniSimEnergyStream
@dataclass
class UniSimEnergyStream:
name: str
heat_flow_kW: Optional[float]
UniSimOperation
@dataclass
class UniSimOperation:
name: str
type_name: str
feeds: List[str]
products: List[str]
energy_feeds: List[str]
energy_products: List[str]
properties: Dict[str, Any]
Common properties keys by operation type:
| Operation | Property Keys |
|---|
| Compressor | outlet_pressure_bara, adiabatic_efficiency, polytropic_efficiency |
| Valve | outlet_pressure_bara |
| Cooler / Heater | outlet_temperature_C, duty_kW |
| HeatExchanger | UA, duty_kW |
| Pipe | length_m, diameter_m, roughness_m |
| Pump | outlet_pressure_bara, efficiency |
UniSimFlowsheet
@dataclass
class UniSimFlowsheet:
name: str
material_streams: List[UniSimStreamData]
energy_streams: List[UniSimEnergyStream]
operations: List[UniSimOperation]
sub_flowsheets: List['UniSimFlowsheet']
UniSimModel
@dataclass
class UniSimModel:
file_path: str
file_name: str
fluid_packages: List[UniSimFluidPackage]
flowsheet: Optional[UniSimFlowsheet]
def summary(self) -> str: ...
def all_operations(self) -> List[UniSimOperation]: ...
def all_streams(self) -> List[UniSimStreamData]: ...
def to_dict(self) -> Dict: ...
12. Troubleshooting
| Issue | Cause | Fix |
|---|
pywintypes.com_error | UniSim not installed or wrong version | Verify UniSim installation |
| Empty stream values | Stream not solved in UniSim | Open file in UniSim GUI, run solver first |
-32767 values | UniSim empty marker | Filter with val > -30000 check |
| COM timeout | Large model loading | Increase time.sleep() after .Open() |
| Wrong component count | Multiple fluid packages | Check which FP the stream uses |
| Missing operations | Sub-flowsheet not recursed | Use model.all_operations() |
| Composition doesn't sum to 1 | Hypothetical components excluded | Re-normalize after filtering |
| Compressor outlet T too low | Efficiency = 100% (ideal) | Check for missing efficiency; defaults to 75% |
| Valve flow deviation 500%+ | Wrong forward ref placeholder | Port-specific placeholders now fix this |
| Separator downstream wrong phase | Generic fwd ref placeholder used | Ensure _register_fwd_placeholders ran |
| Recycle not converging | Too many forward references | Try multiple process.run() calls or tune placeholders |
AttributeError on COM property | Operation type doesn't have that property | Wrap in try/except or check TypeName |
| Column diverges / mass runaway | C3/C4-rich feed at low P | Known solver limitation; build column outside ProcessSystem; use flash separation as fallback |
| HX outlet T differs 1–2°C | UA mismatch or no ΔP modeled | Tune UA value; add note that NeqSim HX has no pressure drop |
| SalesGas T off by 5°C | Propagated HX deviation | CoolGas deviation amplified through counter-current HX; adjust UA |
13. Verified Reference Cases
| Case | File | Components | Operations | Converged | Notes |
|---|
| TUTOR1 | TUTOR1.usc | 7 (N₂, CO₂, C₁–nC₄) | 13 | 11/13 streams | DePropanizer column diverges; upstream matches within 1°C. Notebook: examples/notebooks/tutor1_gas_processing.ipynb |
| R510 SG Cond | R510 model | 31 (lumped pseudo-C7+ through C30P*) | 185 main + 8 sub-flowsheets (~250 total) | 78% isolated, 71% connected | PR-LK EOS with E300 BIPs. Full mode: 5 process + 3 utility sub-flowsheets |
TUTOR1 Lessons Learned
The TUTOR1 gas processing tutorial was the first end-to-end verified conversion.
Key findings that apply to any UniSim conversion:
-
Recycle loops converge well: The Gas/Gas HX ↔ LTS recycle loop converged
in 3 iterations using placeholder streams seeded from UniSim data.
-
HeatExchanger UA tuning: Setting UA = 35000 W/K produced CoolGas ≈ 5.5°C
vs UniSim's 6.6°C. The deviation propagates to SalesGas (15°C vs 10°C). The
UA value needs case-by-case tuning against UniSim's heat duty.
-
Column solver limitation: The DePropanizer (7 components, 24% CH4,
27% C₂, 24% C₃, 24% C₄ at 14 bara) diverged with all three solver types
(DIRECT_SUBSTITUTION, INSIDE_OUT, DAMPED_SUBSTITUTION) and multiple
configurations (1–5 trays). This is a known NeqSim limitation for NGL-range
feeds. Workaround: Build the column outside the ProcessSystem to avoid
re-run divergence affecting upstream convergence.
-
Pressure drops not modeled: UniSim's Gas/Gas HX has ~0.7 bar ΔP per side;
NeqSim passes pressure through unchanged. This is cosmetic for most
comparisons but compounds through multi-stage processes.
-
DewPoint and Balance operations: UniSim's DewPoint (balance op) and
ADJ-1 (adjuster) are not needed for mass balance; safe to skip.
-
Heating Value spreadsheet: Property-only calculations; safe to skip.
R510 SG Condensation Model — Lessons Learned
The R510 SG Condensation model is a large-scale offshore processing model with
31 components (including lumped pseudo-components C10-C11* through C30P*, aromatics,
and glycols), 8 sub-flowsheets, 13+ recycle loops, and ~250 total operations.
This is the first full-mode verified conversion with sub-flowsheet classification.
Model characteristics:
- Fluid package: PR-LK (Peng-Robinson Lee-Kesler) with 31 components
- Feed streams: 3 feeds — "Reservoir oil" (MW=58.5, 1,950,684 kg/h),
"Formation water" (MW=18.0, 398,728 kg/h), "Res gas" (MW=19.6, 30,271 kg/h)
- Sub-flowsheets: 5 process (TPL1, TPL2, TPL5, TPL7, COL1) +
3 utility (TPL3, TPL4, TPL6)
- Generated code: ~2000 lines of Python with ProcessModel architecture
Comparison Results
Isolated unit-by-unit (each unit fed with correct UniSim inlet data):
- 97 GOOD (< 5% deviation), 9 WARN (5-15%), 30 BAD (> 15%), 66 SKIPPED
- 78% match rate across 136 comparable stream properties
Connected main-path model (17 units, no recycles, error propagation):
- 11 OK, 1 WARN, 5 BAD — 71% match rate in 0.5 seconds
- Temperature accuracy: excellent (< 0.3°C for most streams)
- Gas MW matching: good (< 5%)
- Liquid MW: moderate deviation due to water handling (see below)
Key Findings
-
E300 fluid loading is essential for pseudo-components. The read(export_e300=True)
option extracts Tc, Pc, ω, MW, and BIPs for all 31 components including lumped
pseudo-components (C10-C11*, C12-C13*, etc.). Without E300 BIPs, phase split
deviations exceed 100% for heavy components.
-
Component name mapping for lumped pseudo-components. UniSim uses names like
C10-C11*, C12-C13*, C14-C15*, C16-C18*, C19-C20*, C21-C23*,
C24-C29*, C30P*. These map 1:1 to the same names in the E300 file.
Do NOT assume individual components (C10, C11, C12, ...) — the model uses
lumped groups.
-
Separator water handling (2-phase vs 3-phase).
When UniSim's sep3op has has_water_product: False (all separators in R510),
use NeqSim Separator (2-phase), NOT ThreePhaseSeparator. The 2-phase Separator
gives combined oil+water liquid matching UniSim's 2-product behavior.
ThreePhaseSeparator removes ALL water, overcorrecting liquid MW
(e.g., 201.9 vs target 98.2, while 2-phase gives 66.2 — closer overall).
-
Compressor efficiency extraction fails for some models. All 6 compressors
in R510 returned None for AdiabaticEfficiency from COM. The 75% isentropic
default causes 10-32°C outlet temperature deviations. To improve accuracy,
back-calculate efficiency from UniSim inlet/outlet data.
-
Recycle convergence. Even with setTolerance(1e6) on all 13 recycles,
the full ProcessModel with 8 areas still times out. Root cause is likely the
combination of Adjusters, absorber columns, and multi-area iteration.
Workaround: Test individual process areas or connected sub-paths first
(the 17-unit connected model runs in 0.5s). Build up to full model incrementally.
-
JSON key format. The extracted JSON uses pressure_bara (not pressure_kPa)
and mass_flow_kgh (not mass_flow_kg_h). When writing comparison scripts,
use these exact keys.
-
Sub-flowsheet mapping uses positional order. When the JSON has sub-flowsheet
operations that cannot be matched by name or stream overlap, the reader falls
back to positional matching based on the original JSON order. This handles
cases where sub-flowsheet operation names are generic (e.g., MIX-100 appears
in both main and sub-flowsheet).
-
Temperature matching is the strongest comparison metric. Temperatures
showed < 0.3°C deviation across the connected path. Gas-phase MW was within
5% for most equipment. Use temperature as the primary validation metric;
MW and flow deviations are often caused by liquid-phase water handling
rather than thermodynamic model errors.
Operation Handler Registry Lessons Learned
For large UniSim conversions, do not implement every UniSim operation as a
separate UniSim-named NeqSim equipment class. Keep the core physical API native
to NeqSim and extend the converter registry instead:
- Add or update the
UniSimOperationHandler entry with the correct
strategy and stream_role.
- Use native NeqSim equipment for real process physics.
- Use
UnisimCalculator for stream-carrying balance, virtual-stream, and
template-interface placeholders when equations are not yet extracted.
- Use
SpreadsheetBlock for spreadsheet formulas once import/export cells are
known.
- Promote an adapter to a real NeqSim equipment class only when equations,
ports, properties, and regression tests are clear.
Validate registry changes with python devtools/test_unisim_outputs.py. The
suite includes pure-Python checks for output modes, E300 transfer, operation
handler strategy, and JSON _unisim_operation_mapping summaries.