Skip to content

Commit b6dbff0

Browse files
committed
unism conversion
1 parent d978f9f commit b6dbff0

File tree

4 files changed

+215
-40
lines changed

4 files changed

+215
-40
lines changed

.github/agents/unisim.reader.agent.md

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,22 @@ Examine what was extracted:
5353
|---------|-------------|
5454
| ≤ 20 operations, no sub-flowsheets | Single ProcessSystem via JSON |
5555
| > 20 operations, 1-2 sub-flowsheets | Flatten into single ProcessSystem |
56-
| > 20 operations, 3+ sub-flowsheets | ProcessModule with multiple ProcessSystems |
56+
| > 20 operations, 3+ sub-flowsheets | ProcessModel with multiple ProcessSystems |
5757
| Different fluid packages per sub-FS | Separate ProcessSystems mandatory |
5858

59+
**Full mode (default):** Since `full_mode=True` is now the default for all
60+
conversion methods (`to_python()`, `to_notebook()`, `to_json()`,
61+
`build_and_run()`), sub-flowsheet classification and ProcessModel generation
62+
happen automatically. Process sub-flowsheets (those sharing streams with the
63+
main flowsheet) become separate `ProcessSystem` areas composed into a
64+
`ProcessModel`. Utility sub-flowsheets are excluded. Classification uses
65+
`classify_subflowsheets()` and `get_process_subflowsheets()` internally.
66+
67+
**E300 fluid export:** When `export_e300=True` (default), the reader extracts
68+
Tc, Pc, omega, MW, and BIPs from UniSim COM and writes E300 files. The
69+
generated code loads the fluid via `EclipseFluidReadWrite.read()` for lossless
70+
transfer of hypothetical/pseudo component properties.
71+
5972
### Step 3: Handle Component Mapping
6073

6174
Map UniSim components to NeqSim names using the skill's Component Name Mapping table.
@@ -83,7 +96,7 @@ print("Assumptions:", converter.assumptions)
8396
**Option B — Python code (for human review and editing):**
8497
```python
8598
converter = UniSimToNeqSim(model)
86-
python_code = converter.to_python()
99+
python_code = converter.to_python() # full_mode=True by default
87100

88101
# Save as a standalone, runnable script
89102
with open("process.py", "w") as f:
@@ -92,13 +105,15 @@ print(f"Generated {len(python_code.splitlines())} lines of Python")
92105
```
93106

94107
The generated Python script uses explicit `jneqsim` API calls — every stream,
95-
equipment item, and connection is visible and editable. This is ideal when the
96-
user wants to inspect, modify, or learn from the converted process.
108+
equipment item, and connection is visible and editable. With `full_mode=True`
109+
(default), all equipment from the main flowsheet AND process sub-flowsheets is
110+
included, composed into a `ProcessModel`. This is ideal when the user wants to
111+
inspect, modify, or learn from the converted process.
97112

98113
**Option C — Jupyter notebook (for interactive exploration):**
99114
```python
100115
converter = UniSimToNeqSim(model)
101-
converter.save_notebook("process.ipynb")
116+
converter.save_notebook("process.ipynb") # full_mode=True by default
102117
```
103118

104119
The notebook wraps the same code from `to_python()` in separate cells with
@@ -356,6 +371,12 @@ unrealistically low outlet temperatures (observed: -24.9°C deviation).
356371

357372
### Recycle Convergence Limitations
358373

374+
The generated code sets `setTolerance(1e6)` on all Recycle objects to prevent
375+
single-pass timeout. Even so, models with many recycles (13+) composed into
376+
a `ProcessModel` may still time out during `plant.run()`. **Workaround:**
377+
test connected sub-paths incrementally (main-path-only first, then add
378+
sub-flowsheets one at a time).
379+
359380
Models with many forward references (5+) may not converge on the first
360381
`process.run()`. The placeholder initial values (from UniSim stream data)
361382
may not be close enough. Possible mitigation:
@@ -384,7 +405,9 @@ fix applies to all three output modes. This is by design.
384405

385406
---
386407

387-
## Verified Reference Case: TUTOR1
408+
## Verified Reference Cases
409+
410+
### TUTOR1 (Simple — 7 Components)
388411

389412
The `TUTOR1.usc` UniSim tutorial has been fully converted and verified. Use it
390413
as a reference pattern for any conversion workflow:
@@ -395,6 +418,25 @@ as a reference pattern for any conversion workflow:
395418
- **Result**: 11/13 streams match within 1°C and 2% flow. DePropanizer column
396419
does not converge (known NeqSim limitation for NGL-rich feeds).
397420

421+
### R510 SG Condensation (Complex — 31 Components, 8 Sub-Flowsheets)
422+
423+
A large industrial model verified with `full_mode=True`:
424+
425+
- **Components**: 31 (lumped pseudo-components C10-C11* through C30P*), PR-LK EOS
426+
- **Operations**: ~250 total across 8 sub-flowsheets (5 process, 3 utility)
427+
- **Feeds**: 3 (Reservoir oil MW=58.5, Formation water, Res gas MW=19.6)
428+
- **Isolated unit comparison**: 97 GOOD / 9 WARN / 30 BAD (78% match rate)
429+
- **Connected main-path model**: 11 OK / 1 WARN / 5 BAD (71% match rate)
430+
- **Temperature accuracy**: < 0.3°C throughout connected model
431+
- **Scripts**: `output/run_comparison_v2.py` (isolated), `output/run_connected_model.py` (connected)
432+
433+
**Key findings from R510:**
434+
1. E300 fluid loading preserves all 31 lumped pseudo-component properties losslessly
435+
2. All separators have `has_water_product: False` — use 2-phase `Separator` (not `ThreePhaseSeparator`)
436+
3. Compressor efficiencies not extracted from COM → 75% default causes 10-32°C T deviation
437+
4. Full ProcessModel with 13+ recycles may time out — test sub-paths incrementally
438+
5. JSON keys are `pressure_bara` and `mass_flow_kgh` (verify before comparison scripts)
439+
398440
### Key Patterns from TUTOR1
399441

400442
1. **Recycle loop handling**: Create a placeholder stream for the LTS gas

.github/skills/neqsim-unisim-reader/SKILL.md

Lines changed: 158 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -580,19 +580,43 @@ A warning is logged when a fallback mapping is used.
580580

581581
## 5. Workflow: From .usc File to Running NeqSim Model
582582

583+
### Full Mode (Default — Recommended)
584+
585+
All four output methods (`to_json()`, `build_and_run()`, `to_python()`,
586+
`to_notebook()`) default to **`full_mode=True`**. This means:
587+
588+
1. **Sub-flowsheet auto-classification**: Sub-flowsheets are classified as
589+
either "process" (shares streams with the main flowsheet) or "utility"
590+
(isolated). Only process sub-flowsheets are included.
591+
2. **ProcessModel architecture**: The main flowsheet and each process
592+
sub-flowsheet become separate `ProcessSystem` objects composed inside a
593+
`ProcessModel` (multi-area plant model).
594+
3. **E300 fluid loading**: When the `UniSimReader.read(export_e300=True)`
595+
option was used (default), the converter uses `EclipseFluidReadWrite.read()`
596+
to load the fluid with exact Tc, Pc, ω, MW, and BIPs from UniSim.
597+
4. **Recycle tolerance**: All auto-generated Recycle objects have
598+
`setTolerance(1e6)` to prevent convergence blocking during initial runs.
599+
600+
To disable full mode and get only the main flowsheet operations:
601+
602+
```python
603+
converter = UniSimToNeqSim(model)
604+
python_code = converter.to_python(full_mode=False)
605+
```
606+
583607
### Quick Usage (Python)
584608

585609
```python
586610
from devtools.unisim_reader import UniSimReader, UniSimToNeqSim, UniSimComparator
587611

588-
# Step 1: Read the UniSim file
612+
# Step 1: Read the UniSim file (export_e300=True is default)
589613
with UniSimReader(visible=False) as reader:
590614
model = reader.read(r"path\to\file.usc")
591615

592616
# Step 2: Inspect what was extracted
593617
print(model.summary())
594618

595-
# Step 3: Convert to NeqSim JSON
619+
# Step 3: Convert to NeqSim JSON (full_mode=True by default)
596620
converter = UniSimToNeqSim(model)
597621
neqsim_json = converter.to_json()
598622

@@ -623,7 +647,7 @@ Instead of JSON, generate a standalone Python script with explicit `jneqsim` API
623647

624648
```python
625649
converter = UniSimToNeqSim(model)
626-
python_code = converter.to_python(include_subflowsheets=True)
650+
python_code = converter.to_python() # full_mode=True by default
627651

628652
# Save to file
629653
with open("process.py", "w") as f:
@@ -638,7 +662,7 @@ The generated script is a **complete, runnable Python file** that includes:
638662
4. All equipment in **topological order** (upstream before downstream)
639663
5. Equipment properties set via `jneqsim` API calls (efficiency, outlet pressure, etc.)
640664
6. Stream wiring through outlet stream references (e.g., `separator.getGasOutStream()`)
641-
7. Sub-flowsheet operations (if `include_subflowsheets=True`)
665+
7. Sub-flowsheet operations (included by default in `full_mode=True`)
642666
8. `process.run()` call at the end
643667

644668
The generated code uses **direct NeqSim Java API calls** — no JSON intermediate.
@@ -661,12 +685,12 @@ overview):
661685

662686
```python
663687
converter = UniSimToNeqSim(model)
664-
converter.save_notebook("process.ipynb")
688+
converter.save_notebook("process.ipynb") # full_mode=True by default
665689
```
666690

667691
Or get the raw dict (nbformat v4):
668692
```python
669-
nb_dict = converter.to_notebook(include_subflowsheets=True)
693+
nb_dict = converter.to_notebook() # full_mode=True by default
670694
```
671695

672696
The notebook contains:
@@ -976,42 +1000,54 @@ them — the separator will use NeqSim defaults (zero entrainment).
9761000
UniSim uses sub-flowsheets (template operations) for modular process sections.
9771001
In NeqSim, these map to either:
9781002

979-
1. **Separate ProcessSystem objects** composed in a `ProcessModule`
1003+
1. **Separate ProcessSystem objects** composed in a `ProcessModel`
9801004
2. **Flattened into the main ProcessSystem** (simpler but may not handle inter-area recycles)
9811005

1006+
### Auto-Classification (full_mode=True, Default)
1007+
1008+
When `full_mode=True` (the default), the converter automatically classifies
1009+
sub-flowsheets into **process** (shares material streams with the main
1010+
flowsheet) and **utility** (isolated, e.g., heating/cooling medium loops).
1011+
Only process sub-flowsheets are included in the generated code.
1012+
1013+
Classification is done by `classify_subflowsheets()`:
1014+
1015+
```python
1016+
classification = converter.classify_subflowsheets()
1017+
# Returns: {'TPL1': 'process', 'TPL3': 'utility', 'COL1': 'process', ...}
1018+
process_sfs = converter.get_process_subflowsheets()
1019+
# Returns: ['TPL1', 'TPL2', 'TPL5', 'TPL7', 'COL1']
1020+
```
1021+
1022+
A sub-flowsheet is classified as **process** if any of its operations produce
1023+
or consume a stream that also appears in the main flowsheet or another process
1024+
sub-flowsheet. Otherwise it is **utility** (typically heating/cooling medium,
1025+
flare, utility water systems).
1026+
9821027
### Architecture Decision
9831028

9841029
| Model Complexity | Strategy |
9851030
|-----------------|----------|
9861031
| Main + 1-2 small sub-flowsheets | Flatten into single ProcessSystem |
987-
| Main + 3+ sub-flowsheets | ProcessModule with separate ProcessSystems |
1032+
| Main + 3+ sub-flowsheets | ProcessModel with separate ProcessSystems |
9881033
| Sub-flowsheet has own fluid package | Must be separate ProcessSystem |
9891034

990-
### Example: Large Platform Model Structure
1035+
### Sub-Flowsheet to ProcessModel Mapping
9911036

992-
```
993-
Main Flowsheet (146 operations)
994-
├── Satellite (18 operations) — well stream preparation
995-
├── LP_Inlet (16 operations) — low pressure inlet
996-
├── HP_Inlet (6 operations) — high pressure inlet
997-
├── TPL1 (6 operations) — test separator
998-
├── DPC_UNIT (20 operations) — dew point control
999-
└── HM (41 operations) — heating medium system
1000-
```
1037+
In `full_mode=True`, each process sub-flowsheet becomes its own
1038+
`ProcessSystem`, all composed inside a `ProcessModel`:
10011039

1002-
This would become:
10031040
```python
10041041
from neqsim import jneqsim
1005-
ProcessModule = jneqsim.process.processmodel.ProcessModule
1006-
1007-
module = ProcessModule("Platform")
1008-
module.add(main_process) # Main separation & compression
1009-
module.add(satellite_process) # Satellite wells
1010-
module.add(lp_inlet_process) # LP inlet
1011-
module.add(hp_inlet_process) # HP inlet
1012-
module.add(dpc_process) # Dew point control
1013-
# HM (heating medium) typically not modeled in NeqSim
1014-
module.run()
1042+
ProcessModel = jneqsim.process.processmodel.ProcessModel
1043+
1044+
plant = ProcessModel("Platform")
1045+
plant.add("Main", main_process)
1046+
plant.add("TPL1", tpl1_process)
1047+
plant.add("TPL2", tpl2_process)
1048+
plant.add("COL1", col1_process)
1049+
# Utility sub-flowsheets (TPL3, TPL4, TPL6) are excluded
1050+
plant.run()
10151051
```
10161052

10171053
---
@@ -1090,6 +1126,24 @@ python devtools/unisim_reader.py path/to/file.usc --visible --summary
10901126
must be tuned to match UniSim's heat duty. Counter-current heat balance
10911127
differences between UniSim and NeqSim typically produce 1–2°C deviation
10921128
on outlet temperatures.
1129+
19. **Full ProcessModel timeout** — Large models with 5+ sub-flowsheets, 10+
1130+
recycles, and Adjusters may time out during `plant.run()` even with relaxed
1131+
recycle tolerances. Root cause is typically the combination of Adjuster
1132+
iteration, absorber column convergence, and multi-area coordination.
1133+
**Workaround**: Test individual ProcessSystem areas or connected sub-paths
1134+
first, then build up incrementally. The connected main-path approach (no
1135+
recycles, manual feed data) runs in < 1 second for even large models.
1136+
20. **Separator liquid MW deviation** — When UniSim separators have
1137+
`has_water_product=False` (2-product), the NeqSim `Separator` includes
1138+
water in the liquid phase. This causes liquid MW to be lower than UniSim's
1139+
value (water dilutes the MW). Using `ThreePhaseSeparator` overcorrects by
1140+
removing ALL water. Expect 20-40% liquid MW deviation at low/medium
1141+
pressures. Gas MW and temperatures are much more accurate.
1142+
21. **Utility sub-flowsheets excluded** — In `full_mode=True`, sub-flowsheets
1143+
classified as "utility" (no shared streams with process flowsheet) are
1144+
excluded. This is correct for heating/cooling medium loops but may
1145+
miss utility systems that interact with process streams through
1146+
non-standard connections.
10931147

10941148
---
10951149

@@ -1232,6 +1286,7 @@ class UniSimModel:
12321286
| Case | File | Components | Operations | Converged | Notes |
12331287
|------|------|-----------|------------|-----------|-------|
12341288
| 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` |
1289+
| 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 |
12351290

12361291
### TUTOR1 Lessons Learned
12371292

@@ -1260,3 +1315,77 @@ Key findings that apply to any UniSim conversion:
12601315
ADJ-1 (adjuster) are not needed for mass balance; safe to skip.
12611316

12621317
6. **Heating Value spreadsheet**: Property-only calculations; safe to skip.
1318+
1319+
### R510 SG Condensation Model — Lessons Learned
1320+
1321+
The R510 SG Condensation model is a large-scale offshore processing model with
1322+
31 components (including lumped pseudo-components C10-C11* through C30P*, aromatics,
1323+
and glycols), 8 sub-flowsheets, 13+ recycle loops, and ~250 total operations.
1324+
This is the first full-mode verified conversion with sub-flowsheet classification.
1325+
1326+
**Model characteristics:**
1327+
- **Fluid package**: PR-LK (Peng-Robinson Lee-Kesler) with 31 components
1328+
- **Feed streams**: 3 feeds — "Reservoir oil" (MW=58.5, 1,950,684 kg/h),
1329+
"Formation water" (MW=18.0, 398,728 kg/h), "Res gas" (MW=19.6, 30,271 kg/h)
1330+
- **Sub-flowsheets**: 5 process (TPL1, TPL2, TPL5, TPL7, COL1) +
1331+
3 utility (TPL3, TPL4, TPL6)
1332+
- **Generated code**: ~2000 lines of Python with ProcessModel architecture
1333+
1334+
#### Comparison Results
1335+
1336+
**Isolated unit-by-unit** (each unit fed with correct UniSim inlet data):
1337+
- 97 GOOD (< 5% deviation), 9 WARN (5-15%), 30 BAD (> 15%), 66 SKIPPED
1338+
- **78% match rate** across 136 comparable stream properties
1339+
1340+
**Connected main-path model** (17 units, no recycles, error propagation):
1341+
- 11 OK, 1 WARN, 5 BAD — **71% match rate** in 0.5 seconds
1342+
- Temperature accuracy: excellent (< 0.3°C for most streams)
1343+
- Gas MW matching: good (< 5%)
1344+
- Liquid MW: moderate deviation due to water handling (see below)
1345+
1346+
#### Key Findings
1347+
1348+
1. **E300 fluid loading is essential for pseudo-components.** The `read(export_e300=True)`
1349+
option extracts Tc, Pc, ω, MW, and BIPs for all 31 components including lumped
1350+
pseudo-components (C10-C11*, C12-C13*, etc.). Without E300 BIPs, phase split
1351+
deviations exceed 100% for heavy components.
1352+
1353+
2. **Component name mapping for lumped pseudo-components.** UniSim uses names like
1354+
`C10-C11*`, `C12-C13*`, `C14-C15*`, `C16-C18*`, `C19-C20*`, `C21-C23*`,
1355+
`C24-C29*`, `C30P*`. These map 1:1 to the same names in the E300 file.
1356+
Do NOT assume individual components (C10, C11, C12, ...) — the model uses
1357+
lumped groups.
1358+
1359+
3. **Separator water handling (2-phase vs 3-phase).**
1360+
When UniSim's `sep3op` has `has_water_product: False` (all separators in R510),
1361+
use NeqSim `Separator` (2-phase), NOT `ThreePhaseSeparator`. The 2-phase Separator
1362+
gives combined oil+water liquid matching UniSim's 2-product behavior.
1363+
`ThreePhaseSeparator` removes ALL water, overcorrecting liquid MW
1364+
(e.g., 201.9 vs target 98.2, while 2-phase gives 66.2 — closer overall).
1365+
1366+
4. **Compressor efficiency extraction fails for some models.** All 6 compressors
1367+
in R510 returned `None` for `AdiabaticEfficiency` from COM. The 75% isentropic
1368+
default causes 10-32°C outlet temperature deviations. To improve accuracy,
1369+
back-calculate efficiency from UniSim inlet/outlet data.
1370+
1371+
5. **Recycle convergence.** Even with `setTolerance(1e6)` on all 13 recycles,
1372+
the full ProcessModel with 8 areas still times out. Root cause is likely the
1373+
combination of Adjusters, absorber columns, and multi-area iteration.
1374+
**Workaround**: Test individual process areas or connected sub-paths first
1375+
(the 17-unit connected model runs in 0.5s). Build up to full model incrementally.
1376+
1377+
6. **JSON key format.** The extracted JSON uses `pressure_bara` (not `pressure_kPa`)
1378+
and `mass_flow_kgh` (not `mass_flow_kg_h`). When writing comparison scripts,
1379+
use these exact keys.
1380+
1381+
7. **Sub-flowsheet mapping uses positional order.** When the JSON has sub-flowsheet
1382+
operations that cannot be matched by name or stream overlap, the reader falls
1383+
back to positional matching based on the original JSON order. This handles
1384+
cases where sub-flowsheet operation names are generic (e.g., `MIX-100` appears
1385+
in both main and sub-flowsheet).
1386+
1387+
8. **Temperature matching is the strongest comparison metric.** Temperatures
1388+
showed < 0.3°C deviation across the connected path. Gas-phase MW was within
1389+
5% for most equipment. Use temperature as the primary validation metric;
1390+
MW and flow deviations are often caused by liquid-phase water handling
1391+
rather than thermodynamic model errors.

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ ImpurityMonitor = jpype.JClass("neqsim.process.measurementdevice.ImpurityMonitor
693693
| `src/main/java/neqsim/process/processmodel/` | ProcessSystem, ProcessConnection, ProcessElementInterface, JsonProcessBuilder, SimulationResult |
694694
| `src/main/java/neqsim/process/automation/` | ProcessAutomation (string-addressable variable API), AutomationDiagnostics (fuzzy matching, auto-correction, physical validation, learning), SimulationVariable (INPUT/OUTPUT descriptor) |
695695
| `src/main/java/neqsim/process/processmodel/lifecycle/` | ProcessSystemState, ProcessModelState — JSON lifecycle snapshots, version comparison, compressed transfer |
696-
| `devtools/unisim_reader.py` | UniSim COM reader → NeqSim Python/notebook/EOT/JSON (UniSimReader, UniSimToNeqSim, UniSimComparator). 45+ op types, port-specific forward refs, auto-recycle wiring. **Default E300 fluid export**: `read(export_e300=True)` extracts Tc, Pc, omega, MW, BIPs from COM and writes E300 files for all fluid packages. `build_and_run()` auto-loads E300 fluids via `EclipseFluidReadWrite.read()` and `ProcessSystem.fromJsonAndRun(json, fluid)`. Verified with TUTOR1.usc (11/13 streams match). |
696+
| `devtools/unisim_reader.py` | UniSim COM reader → NeqSim Python/notebook/EOT/JSON (UniSimReader, UniSimToNeqSim, UniSimComparator). 45+ op types, port-specific forward refs, auto-recycle wiring. **Default E300 fluid export**: `read(export_e300=True)` extracts Tc, Pc, omega, MW, BIPs from COM and writes E300 files for all fluid packages. `build_and_run()` auto-loads E300 fluids via `EclipseFluidReadWrite.read()` and `ProcessSystem.fromJsonAndRun(json, fluid)`. **Full mode default**: `full_mode=True` (all 4 methods) auto-classifies sub-flowsheets as process/utility, includes only process SFs in ProcessModel. Verified with TUTOR1.usc (11/13 streams match) and R510 SG Condensation (31 comp, 250 ops, 8 SFs: 78% isolated match, 71% connected). |
697697
| `devtools/test_unisim_outputs.py` | 14 tests for all UniSim converter output modes (no COM needed — synthetic models) |
698698
| `examples/notebooks/tutor1_gas_processing.ipynb` | End-to-end UniSim→NeqSim verification: TUTOR1 gas processing (7 comp, PR EOS, 13 ops). Reference for conversion workflows. |
699699
| `src/main/java/neqsim/process/mechanicaldesign/subsea/` | Well & SURF design, cost estimation |

0 commit comments

Comments
 (0)