OLGA Multiphase Flow Simulator
OLGA (SLB) is a transient one-dimensional three-phase pipe-flow simulator. It is the industry reference for slug tracking, terrain slugging, shut-in and restart, blowdown, liquid surge to a receiving facility, hydrate and wax management, well clean-up and drilling hydraulics.
This skill is the execution and post-processing layer: how to find the installed OLGA, validate a case cheaply, launch the batch engine correctly, understand why it stopped, and read its results into Python. It does not teach you how to build a flow-assurance model from nothing, and it never replaces a qualified flow-assurance review.
OLGA is licensed commercial software. This skill assumes the user already has a valid installation and licence; it only drives what is installed locally.
When to Use
- A task must run an existing OLGA case and extract numbers from the results.
- An OLGA case needs a PVT table or a hydrate equilibrium curve generated from a NeqSim fluid, so that OLGA and NeqSim share one fluid and one hydrate boundary.
- A parametric or sensitivity sweep must be run reproducibly rather than by hand in the GUI.
- An OLGA run failed and the exit code, log or licence state must be diagnosed.
- OLGA results must be compared against NeqSim screening or pipeline models.
Do not use this skill to invent a case from scratch, to guess PVT tables, or to interpret a transient result without a flow-assurance engineer in the loop.
Inputs
case: path to the.genkey/.keykeyword file the batch engine reads. The GUI.opiproject is not an engine input — generate thegenkeyfrom it first.pvt_file: the.tabPVT table referenced byFILES PVTFILE=, resolved relative to the working directory. Generated from a NeqSim fluid (two- or three-phase) so OLGA and NeqSim share one fluid.hydrate_curve: optional hydrate equilibrium curve forHYDRATECHECK, also generated from the same NeqSim fluid.restart_file: optional.rswsnapshot for a restart (RESTART READFILE=ON).parameters: mapping of{KEYWORD: {ATTRIBUTE: value}}overrides applied byapply_parameters/set_parameter/write_variantfor sweeps, e.g.{"INTEGRATION": {"ENDTIME": "6 h"}}.out_dir: output directory for the run (-outDir); defaults to the case directory.nthreads: thread count for parallel execution (-nthreads).timeout: wall-clock limit in seconds after which the run is terminated.olga_home/olga_engine: optional explicit installation or engine path; otherwiseOLGA_HOME/OLGA_ENGINEor installation discovery is used.- Licence environment:
LM_LICENSE_FILEorSLBSLS_LICENSE_FILE, plus a reachable licence server when the licence is served rather than node-locked.
Outputs
installations:OlgaInstallationrecords fromfind_olga_installations()— version, root and batch-engine path for every OLGA found on the machine.rule_check:OlgaRunResultfrom the-exitRCinput-rule pass, withsucceededand the engine stdout.run_result:OlgaRunResultwithcase,out_dir,command,returncode,category,code_name,description,duration_s,stdout,timed_out, andoutputs(the.out,.tpl,.ppl,.plt,.h5files produced).summary()returns a JSON-ready dict forresults.json.trend:TrendDatafromread_tpl— time vector,time_unit, and oneOlgaVariableper catalog entry with its ownunit;final(name)gives the end-of-run value.profile:ProfileDatafromread_ppl— spatial profiles per variable and time step, withpositions(name)giving the matching abscissa in m.branches:OlgaBranchgeometry (xdistance along the branch,yelevation) read from the result header, needed to plot or orient a profile.exit_code_decoding:describe_exit_code(code)→(category, name, description), resolved offline from the embeddedEXIT_CODEStable.
Engineering Method
OLGA solves the transient one-dimensional two-fluid (three-phase) conservation equations along a discretized pipe network. This skill does not reimplement that physics — it drives the licensed engine and decodes what it produced:
- Discovery. Installations are located from
OLGA_HOME/OLGA_ENGINE, then from the standard install roots, and the newest version is used by default. TheOLGA-Spoint model (OLGAS_SLB_x64) is deliberately excluded: it is the steady-state model other hosts link against, not the transient engine. - Cheap validation before expensive runs. Every case is first run with
-exitRC, which parses keywords, units and topology in seconds. It does not open the PVT file and does not validateTRENDDATA/PROFILEDATAvariable names, so the result-fileCATALOGis read back afterwards to confirm every requested variable actually exists. - Execution. The engine is launched with the working directory set to the
case directory, so relative
FILES PVTFILE=references resolve, with the case file last on the command line as the engine requires. - Outcome classification. The process exit code is mapped through the table
transcribed from
exit_code_lookup.exeinto a category, name and first action — separating input errors (17, 20–22), licence failures (26), PVT problems (23/34) and numerical divergence (65–73), which need different responses. - Discretization.
discretize_routeturns a surveyed route into OLGA pipes and sections using a target section length with graded first-section lengths, a neighbour length-ratio limit, and refinement at local elevation minima where terrain slugging accumulates liquid. - Result decoding.
.tpland.pplshare one ASCII header, geometry block andCATALOG; the data block is consumed as a single number stream using the catalog counts (nsections + 1values for aBOUNDARY:variable,nsectionsfor aSECTION:one). Units are taken per variable from the catalog, because OLGA mixes systems in one file (PTin Pa,TMin °C).
The engine result is only as good as the case and its PVT table. This skill gives a reproducible, decodable run — it is not a flow-assurance method and does not replace a qualified flow-assurance review.
Installed Layout (Windows)
A standard installation looks like this — several versions can coexist:
C:\Program Files\Schlumberger\
├── Olga 2025.1.0\ <- the simulator
│ ├── OPGFramework.exe <- graphical front end
│ ├── opi.exe <- launcher associated with .opi case files
│ ├── OlgaExecutables\
│ │ ├── Olga-2025.1.0.exe <- the batch engine (what you run)
│ │ ├── OlgaOpc-2025.1.0.exe <- OPC server build
│ │ └── exit_code_lookup.exe <- decodes engine exit codes
│ ├── Data\OPG Files\ <- bundled sample-case library
│ ├── Tools\ (OLGAViewer, FluidDefTool, Multiflash, ProfileGenerator, Rocx, FEMThermViewer)
│ ├── Modules\RmoParameterStudy\ <- built-in parameter-study module (RMO)
│ └── Plugins\ (FEMTherm, O4W, PipelineEditor)
├── OLGA-S 2025.1.0\Win64\ <- OLGA-S steady-state point model (used by other hosts)
└── OLGA Namespace Explorer 7.3.x\
OLGA-S is not the transient simulator — it is the steady-state point model
that third-party process and nodal-analysis tools link against. The installer
exports its location as OLGAS_SLB_x64. Never point the batch runner at it.
User-level libraries live under %USERPROFILE%\Documents\Schlumberger\
(PipelineLibrary\*.pml, WellLibrary\*.dml); shared settings under
%ProgramData%\Schlumberger\OLGA.
File Types
| Extension | Role |
|---|---|
.opi |
OLGA GUI project/case file — opened by the GUI, not by the engine |
.genkey / .key |
Keyword input file the batch engine actually reads |
.tab |
PVT table referenced by FILES PVTFILE= |
.rsw |
Restart/snapshot file (RESTART READFILE=ON, FILE=...) |
.out |
Human-readable run log: input echo, mass-error blocks, stop reason |
.tpl |
Trend (time-series) results at the positions given by TRENDDATA |
.ppl |
Profile (spatial) results for the variables given by PROFILEDATA |
.plt |
Binary plot file for OLGA Viewer |
.h5 |
HDF5 result container |
.sil |
Event log that can be replayed with -play |
The GUI owns the .opi project and generates the .genkey the engine runs.
Automation therefore works on the generated .genkey, not on the .opi.
Batch Command Line (verified, OLGA 2025.1.0)
Olga-<version>.exe [options] casefile.[key|genkey]
The case file must be last. The options that matter for automation:
| Option | Effect |
|---|---|
-exitRC |
Run only the input rule checks, then exit. Use before every long run. |
-exitID |
Rule checks plus input-object initialization, then exit. |
-nthreads N |
Use N threads for parallel execution. |
-outDir DIR |
Write output files to DIR (default: the current directory). |
-noout -notpl -noppl -noplt -noopc |
Suppress the corresponding output. |
-log FILE / -consoleLog |
Write engine messages / console output to a log file. |
-restorePolicy N |
Override the case RESTOREPOLICY for a restart. |
-keep |
Keep intermediate files for debugging. |
-version all |
Print engine, rules-engine and flow-model build information. |
-help |
Print the full option list. |
A successful run ends with **** NORMAL STOP IN EXECUTION **** in the .out
file and console, and exit code 0.
What -exitRC does not check. The rule check validates keywords, units and
topology — nothing else. Verified on OLGA 2025.1:
- It never opens the PVT file, so a broken or missing
.tabstill passes. - It does not validate
PROFILEDATA/TRENDDATAvariable names at all. A deliberately invented name passes withRuleCheck: OK, exit 0 and no message, and is then silently dropped from the results. The only authority on which variables exist is theCATALOGblock of the.ppl/.tplafter a real run — read it back and confirm every variable you asked for is there. - It does not check that the physics is posed sensibly.
Treat a passing rule check as "the file parses", never as "the case is right".
Run with the working directory set to the case directory. OLGA resolves
relative references such as FILES PVTFILE=(./fluid.tab) against the current
directory, not against the case file location. OlgaRunner.run does this for you.
Exit Codes
OLGA reports the outcome through the process exit code, grouped by category. The
full table is embedded in runner.EXIT_CODES (transcribed from
exit_code_lookup.exe list), so it can be decoded offline. The ones you will
actually meet:
| Code | Name | Meaning and first action |
|---|---|---|
| 0 | OK |
Normal stop. |
| 17 | CMDLINE_FAILED |
Illegal command line — check flag spelling and that the case is last. |
| 20 | INIT_FAILED |
Initialization failed — read the .out header. |
| 21 | RESTART_FAILED |
Restart file missing or inconsistent with the case. |
| 22 | SSPP_FAILED |
Steady-state pre-processor failed — boundary conditions inconsistent. |
| 23 / 34 | FLUID_FAILED / PVT_FAIL |
PVT table missing, unreachable, or outside its P/T range. |
| 26 | LICENSE_FAIL |
Licence checkout failed — check LM_LICENSE_FILE and server reachability. |
| 35–40 | WAX_/PLUGIN_/ROCX_/SLUG_/TRACER_/PROCEQ_FAIL |
The named submodel failed. |
| 65–73 | PT_*, TM_*, H_* |
Pressure/temperature/enthalpy out of range or NaN — the solution diverged. |
| 97–101 | internal errors | Reproduce with -keep and escalate to the vendor. |
Codes 65–73 mean the physics or numerics failed, not the input. Reduce MAXDT,
check for a closed boundary, an unphysical source, or a MASSFLOW boundary that
cannot be met, and re-run with a shorter INTEGRATION ENDTIME to isolate the time
of failure.
Result File Format
Both .tpl and .ppl are ASCII with the same header, then a CATALOG of
variables, then numbers:
'OLGA 2025.1.0.24773'
TIME PLOT (or PROFILE PLOT)
INPUT FILE / PVT FILES / DATE / PROJECT / TITLE / AUTHOR
NETWORK
<nbranches>
GEOMETRY ' (M) '
BRANCH
'<BRANCH NAME>'
<nsections> <- boundary points = nsections + 1
<x coordinates> <y coordinates>
CATALOG
<nvariables>
PT 'BOUNDARY:' 'BRANCH:' 'PIPELINE' '(PA)' 'Pressure'
TIME SERIES ' (S) '
<numbers>
- In a
.tpl, each data row istimethen one scalar per catalog entry. - In a
.ppl, each time step is thetimevalue then one full profile per catalog entry:nsections + 1values for aBOUNDARY:variable andnsectionsvalues for aSECTION:variable of that variable's branch.
Values wrap across lines freely, so parse the data block as one number stream and
consume it with the counts above — that is what read_tpl / read_ppl do.
The header also carries each branch's geometry: the distance along the branch and
the elevation at every section boundary. OlgaBranch.x / .y expose them, and
ProfileData.positions(name) returns the abscissa that matches a given profile
(boundaries for a BOUNDARY: variable, section centres for a SECTION: one).
Without it a profile is just a list of numbers — you cannot plot it, and you
cannot tell which end is the wellhead. Check the sign of branch.y rather than
assuming index 0 is the inlet.
Units are per variable, and they are not one consistent system. In the same
file OLGA reports PT in PA but TM in C. Always read OlgaVariable.unit
and convert from that; assuming SI throughout turns a 16.8 °C fluid into
−256 °C.
Python Usage Pattern
from olga_multiphase_simulator import (
OlgaRunner, find_olga_installations, read_ppl, read_tpl, write_variant,
)
# 1. Which OLGA versions are installed?
for installation in find_olga_installations():
print(installation.version, installation.engine)
# 2. Cheap input validation first (seconds, not hours).
runner = OlgaRunner(default_nthreads=4) # newest version by default
check = runner.rule_check("case.genkey")
assert check.succeeded, check.stdout[-2000:]
# 3. Run, then decode the outcome.
result = runner.run("case.genkey", timeout=3600)
print(result.summary()) # JSON-ready, for results.json
if not result.succeeded:
print(result.category, result.code_name, result.description)
# 4. Post-process.
trend = read_tpl(result.outputs["tpl"])
print(trend.time_unit, trend.names())
print(trend.final("PT"), trend.variables[0].unit)
profile = read_ppl(result.outputs["ppl"])
pressure = profile.profile("PT", time_index=-1, branch="PIPELINE")
distance = profile.positions("PT", branch="PIPELINE") # matching abscissa, m
Enable output on a case that has none. TRENDDATA / PROFILEDATA inside the
network components is not enough — without the global keywords OLGA writes no
.tpl / .ppl at all:
from olga_multiphase_simulator import apply_parameters, ensure_statement
text = Path("case.genkey").read_text()
text = apply_parameters(text, {"INTEGRATION": {"ENDTIME": "120 s"}})
for statement in ("TREND DTPLOT=20 s", "PROFILE DTPLOT=60 s"):
text = ensure_statement(text, statement, after_keyword="INTEGRATION")
Parametric sweep — write a variant next to the original so its relative .tab
references still resolve, then run each variant into its own output directory:
for endtime in ("1 h", "6 h", "24 h"):
variant = write_variant(
"case.genkey",
f"case_{endtime.replace(' ', '')}.genkey",
{"INTEGRATION": {"ENDTIME": endtime}},
)
runner.run(variant, out_dir=f"runs/{variant.stem}")
genkey.get_parameter / set_parameter edit exactly one value and leave every
other byte untouched, handle \-continuation lines, skip ! comments, and
support repeated keywords through occurrence=. ensure_statement adds a
missing global statement. None of them validate physics — always rule_check
the variant before running it.
The bundled sample library under Data/OPG Files is a good end-to-end test, but
most samples ship only as .opi GUI projects. Well/Well-CleanUp_T includes a
generated .key, so it is the one case that runs in batch without opening the
GUI first.
For large designed experiments, OLGA also ships its own parameter-study module
(Modules/RmoParameterStudy), which is the right tool when the study must be
reproduced inside the GUI.
Authoring a Case From Scratch
A hand-written genkey must use OLGA's network structure. A flat list of
NODE / BRANCH / PIPE statements is rejected with "No network components
found in the input file". The minimum viable single-line case is:
CASE AUTHOR="...", TITLE="..."
OPTIONS TEMPERATURE=WALL, COMPOSITIONAL=OFF, STEADYSTATE=ON
FILES PVTFILE="fluid.tab"
INTEGRATION ENDTIME=12 h, STARTTIME=0 s, MINDT=0.001 s, MAXDT=10 s, DTSTART=0.001 s
OUTPUT DTOUT=12 h
TREND DTPLOT=1800 s
PROFILE DTPLOT=1800 s
MATERIAL LABEL="STEEL", TYPE=SOLID, CAPACITY=500 J/kg-C, CONDUCTIVITY=45 W/m-C, DENSITY=7850 kg/m3
WALL LABEL="PIPEWALL", THICKNESS=(0.02) m, MATERIAL=("STEEL")
NETWORKCOMPONENT TYPE=FLOWPATH, TAG=FLOWPATH_1
PARAMETERS LABEL="EXPORTLINE"
BRANCH FLUID="NewFluid"
GEOMETRY LABEL="ROUTE", XSTART=0 m, YSTART=-307 m, ZSTART=0 m
PIPE LABEL=PIPE_01, ROUGHNESS=4.5e-05 m, DIAMETER=0.355 m, WALL="PIPEWALL", \
NSEGMENT=5, LSEGMENT=(738.45, 738.45, 738.45, 738.45, 738.45) m, \
XEND=3692.26 m, YEND=-294.8 m, ZEND=0 m
HEATTRANSFER LABEL="SEABED", PIPE=ALL, HOUTEROPTION=HGIVEN, \
HAMBIENT=3.2 W/m2-C, TAMBIENT=4 C, HMININNERWALL=10 W/m2-C
SOURCE LABEL="FEED", PIPE=PIPE_01, SECTION=1, TIME=0 s, \
MASSFLOW=101.1 kg/s, TEMPERATURE=40 C, GASFRACTION=1 -
INITIALCONDITIONS PRESSURE=70 bara, TEMPERATURE=40 C, VOIDFRACTION=1 -
OUTPUTDATA VARIABLE=(PT, TM, HOL, ID, USG, USL)
PROFILEDATA VARIABLE=(PT, TM, HOL, ID, USG, USL, ROG, ROHL)
TRENDDATA PIPE=PIPE_01, SECTION=5, VARIABLE=(PT, TM, HOL, USG, USL)
ENDNETWORKCOMPONENT
NETWORKCOMPONENT TYPE=NODE, TAG=NODE_INLET
PARAMETERS LABEL="INLET", TYPE=CLOSED, X=0 m, Y=-307 m, Z=0 m
ENDNETWORKCOMPONENT
NETWORKCOMPONENT TYPE=NODE, TAG=NODE_OUTLET
PARAMETERS LABEL="OUTLET", TYPE=PRESSURE, X=73845 m, Y=-300 m, Z=0 m, \
PRESSURE=70 bara, TEMPERATURE=4 C, GASFRACTION=1 -, FLUID="NewFluid"
ENDNETWORKCOMPONENT
CONNECTION TERMINALS = (NODE_INLET FLOWTERM_1, FLOWPATH_1 INLET)
CONNECTION TERMINALS = (NODE_OUTLET FLOWTERM_1, FLOWPATH_1 OUTLET)
ENDCASE
Rules learned the hard way (each one was a rules-engine rejection):
- Keyword names are singular on
WALL:THICKNESS=,MATERIAL=— notTHICKNESSES/MATERIALS.MATERIALneedsTYPE=SOLIDandW/m-C. SOURCEhas noFLUID=key. The fluid comes from theBRANCH FLUID=label.SOURCErequiresTIME=, even for a steady source: omitting it givesSOURCE: Key TIME must be given.UseTIME=0 s.HEATTRANSFERis mandatory on a flowpath. Without it the rules engine rejects the whole branch with<TAG> Missing required keywords. HEATTRANSFER, even for a short isothermal line.HOUTEROPTION=HGIVENwith a nominalHAMBIENT/TAMBIENTis enough when heat loss is irrelevant.PIPEneeds an explicitLENGTH=. GivingNSEGMENT=nwith a scalarLSEGMENTproduces two errors at once —PIPE: Key LENGTH must be given.andNumber of items mismatch between keys LSEGMENT (1) and NSEGMENT (n)— becauseLSEGMENTis a list with one entry per segment. Either giveLENGTH=withNSEGMENT=and let OLGA divide, or give the fullLSEGMENT=(...)list.INTEGRATIONhas noNSLUGkey in 2025.1, and noNSAVEkey either (1019: Key not found. (NSAVE for INTEGRATION in CASELEVEL CASELEVEL)).- The inner keyword of a
NETWORKCOMPONENT TYPE=NODEisPARAMETERS, notNODE. WritingNODE TYPE=PRESSURE, ...gives the unhelpfulUnknown keyword type NODE for NODE. - Global statements (
OPTIONS,FILES,INTEGRATION,OUTPUT,TREND,PROFILE,MATERIAL,WALL) go before the network components;CONNECTIONstatements go after them. - Iterate against
-exitRC. The rules engine names the offending key and its owner, e.g.1019: Key not found. (FLUID for SOURCE EXPORTFEED in FLOWPATH EXPORTLINE). Treat it as the authority rather than guessing from documentation. It reports several errors per pass, so fix them in batches.
Posing the boundary conditions
OLGA cannot fix inlet pressure, inlet temperature and flow rate at the same
boundary the way a steady-state marching model such as PipeBeggsAndBrills can.
To reproduce a NeqSim case that specifies all three, use a SOURCE for mass flow
and temperature plus a TYPE=PRESSURE outlet node, then iterate the outlet
pressure until OLGA's computed inlet pressure matches the NeqSim specification.
A secant iteration converges in three or four runs. Do not compare an OLGA run
against a NeqSim run at a different pressure level: gas friction scales roughly
as G²/ρ, so the pressure level materially changes the answer.
The SOURCE phase split is an input, and it is easy to get wrong.
GASFRACTION on a SOURCE is a mass fraction and it overrides the table
equilibrium. Writing GASFRACTION=1 feeds OLGA dry gas. If the compositional
model flashes two phases at the inlet, the two cases are then not comparable —
a rich gas at 200 bara / 40 °C can be 99.5 % gas by mole but only 97.4 % by mass,
so GASFRACTION=1 silently deletes 2.6 % of the mass as condensate. Always take
the gas mass fraction from a NeqSim flash at the source conditions:
fluid.setTemperature(313.15); fluid.setPressure(200.0)
ThermodynamicOperations(fluid).TPflash(); fluid.initProperties()
total = sum(fluid.getPhase(i).getNumberOfMolesInPhase()
* fluid.getPhase(i).getMolarMass()
for i in range(fluid.getNumberOfPhases()))
gas_mass_fraction = (fluid.getPhase(0).getNumberOfMolesInPhase()
* fluid.getPhase(0).getMolarMass()) / total
Resolve the gas phase by type (fluid.getPhaseOfType("gas")) rather than by
index whenever the fluid may contain water — a flash returns only the phases that
exist, so index 0 is not reliably the gas.
Three-phase sources: GASFRACTION and WATERFRACTION are on different bases.
This is the single most expensive input error in this skill. When a SOURCE
carries both keys:
WATERFRACTIONis the aqueous mass fraction of the total stream;GASFRACTIONis the gas mass fraction of the hydrocarbon part only, i.e. of what is left after the water is removed — not of the total.
Feeding the total-stream gas mass fraction alongside a WATERFRACTION runs
without any error or warning and produces a plausible-looking result. On a wet
gas line with 80.3 % water by mass, writing the total-basis 0.19147 next to
WATERFRACTION=0.80333 gave GAS 19.37 / OIL 81.79 kg/s where the fluid should
have delivered 98.49 / 2.67 — a 30× condensate error that still converged.
# masses from a NeqSim TPflash at the source conditions
w_water = m_aqueous / m_total # -> WATERFRACTION
w_gas_hc = m_gas / (m_gas + m_oil) # -> GASFRACTION (HC basis)
Always read the split back from the .out file before believing any
three-phase number. OLGA echoes what it actually injected in a
MASS SOURCE INFORMATION: block giving GAS / OIL / WATER in kg/s per source;
check those three numbers against the NeqSim flash to within a per cent. A run
that passed -exitRC and exited 0 proves nothing about the phase split.
Proving the two models see the same input
Before reporting any OLGA-versus-other-simulator comparison, verify each item below with a number rather than by inspection of the input files:
| Input | How to prove it |
|---|---|
| Composition | Same source file (hash it), and the PVT table reproduces a direct flash density at the inlet, mid-line and arrival states |
| Mass rate | SOURCE MASSFLOW equals the other model's mass rate, not just the same volumetric rate — a standard-volume rate depends on the molar mass |
| Inlet phase split | GASFRACTION equals the flashed gas mass fraction (of the hydrocarbons only when WATERFRACTION is also set) |
| Injected phase rates | The MASS SOURCE INFORMATION: block in the .out file matches the NeqSim flash in kg/s for gas, oil and water |
| Diameter and roughness | The set of DIAMETER= and ROUGHNESS= values in the genkey is a single value matching the other model |
| Length | Sum of pipe lengths equals the other model's total, including elevation |
| Pressure level | Inlet pressures agree after the outlet-pressure iteration |
| Ambient | TAMBIENT and the U-to-HAMBIENT mapping stated explicitly |
A 0.01 % density agreement between the table and a direct flash is the check that turns "same fluid file" into "same fluid".
Generating the PVT table from NeqSim
generator = jneqsim.thermodynamicoperations.propertygenerator \
.OLGApropertyTableGeneratorKeywordFormat(fluid)
generator.setFluidLabel("NewFluid") # must equal BRANCH FLUID=
generator.setPressureRange(5.0, 260.0, 41) # bara
generator.setTemperatureRange(233.15, 353.15, 31) # K
generator.run()
generator.writeOLGAinpFile("linnorm.tab")
OLGApropertyTableGeneratorKeywordFormatemitsPHASE = TWOand works for any fluid, with or without a water component.OLGApropertyTableGeneratorWaterKeywordFormatemitsPHASE = THREEand requires awatercomponent. It is the only water generator that works end to end with the plainrun()+writeOLGAinpFile()sequence;OLGApropertyTableGeneratorWaterEventhrowsNullPointerExceptiononbubPLOGunlesscalcPhaseEnvelope()is called first, and itsrun()then throwsIsNaNException: molarVolumeAnalytical - compressibility factor is NaN.setFluidLabelmust match the genkey. The label written into the table has to equal theBRANCH FLUID=(and outlet-nodeFLUID=) string, or OLGA cannot bind the table to the flowpath.- For a
PHASE = THREEtable setOPTIONS COMPOSITIONAL=OFF. The water profile variables areHOLWT(water holdup) andHOLHL(hydrocarbon-liquid holdup);UHLandUWTare not validPROFILEDATAnames and are silently dropped with a warning. - Cover the whole P/T range the line will visit, including Joule-Thomson cooling
at the arrival end; an out-of-table state gives exit 23/34 (or 68
TM_BELOWwhen an iteration walks off the cold edge). - Requires NeqSim with the 2026-08 generator fixes. Older builds ignored the
filenameargument (writing to a hardcoded path and swallowing the error), wroteBUBBLEPRESSURESandBUBBLETEMPERATURESwith different lengths, and — the defect that made them unusable beyond a always-two-phase fluid — indexed phases by array position (0 = gas, 1 = oil, 2 = water) assuming all three existed. Because a flash returns only the phases that are present, every single-phase grid node wrote zero for the absent phase and OLGA refused the file withERROR IN THE INPUT FILE: OIL DENSITY IS ZERO AT: PRES.=... AND TEMP.=.... Dry gas, dead oil and dense-phase CO₂ all failed to load, and a two-phase fluid failed at any grid corner outside its envelope. Current builds resolve phases by type, mark absent phases, and nearest-neighbour fill in grid-index space; mass fractions (RS,RSW) are deliberately left at zero where the phase is absent.
A generated table is only validated when OLGA has actually loaded it.
-exitRC does not read the PVT file, so a rule check passing says nothing
about the table. The two-stage harness that works:
- Scan the written file for
NaN,Infand zero density/viscosity columns. - Run a throwaway minimal case — one flowpath, a
SOURCE, aTYPE=PRESSUREoutlet node, a few minutes ofENDTIME— against the table and require exit 0 plusNORMAL STOP IN EXECUTION.
Exercise the fluid types the study will actually meet: dry gas, gas condensate, black oil, dead oil, CO₂-rich, three-phase, gas+water without oil, oil+water without gas, and a fluid with no water component at all.
Designing the P/T grid
The grid is the model. OLGA interpolates it, so a state the grid does not bracket
is either an extrapolation or a hard stop (exit 23/34 FLUID_FAILED/PVT_FAIL,
or 65–73 when an iteration walks off an edge).
- Bracket the whole excursion, not the design point. Include Joule-Thomson cooling at the arrival end, the seabed temperature, and any blowdown or shut-in the case will simulate. A line entering at 200 bara / 40 °C can arrive at 60 bara / −11 °C at high rate.
- Give the pressure axis headroom above the inlet. When the arrival boundary
is iterated to match a target inlet, intermediate iterates overshoot; a table
topping out at the design inlet aborts with
PRESSURE ABOVE TABLE VALUES. - Use an asymmetric grid when testing a generator. A square nP × nT grid hides transposed-index bugs.
- A 41 × 31 grid over 5–260 bara and 233–353 K is a workable starting point for a subsea gas export line.
Hydrate input to OLGA
OLGA does not compute hydrate thermodynamics. It interpolates a tabulated equilibrium curve that the case supplies, or falls back to the Hammerschmidt correlation — a crude inhibitor shift. If the NeqSim side of a study uses a rigorous CPA hydrate model and OLGA is left on its own default, the two halves of the study disagree about where the hydrate boundary is, and the disagreement is invisible in both sets of output.
Export the curve from the same fluid that produced the PVT table
(neqsim.thermodynamicoperations.propertygenerator.OLGAhydrateCurveGenerator):
gen = jneqsim.thermodynamicoperations.propertygenerator \
.OLGAhydrateCurveGenerator(fluid) # fluid must contain water
gen.setCurveLabel("HYD")
gen.setPressureRange(10.0, 200.0, 21) # bara, linear spacing
gen.run()
gen.writeOLGAinpFile("hydrate_curve.inp")
print(gen.getHydrateCheckKeyword()) # line to paste into the flowpath
It works on a clone, so the caller's fluid keeps its state, and it drops
pressures where the hydrate flash does not converge rather than writing a zero —
a zero temperature would silently move the boundary instead of failing. Check
len(getCurvePressures()) against the number requested; a curve that lost points
has a chord across the gap.
The generated block goes at library level, before the first
NETWORKCOMPONENT, and the flowpath refers to it by label. Verified against the
OLGA 2025.1 rules engine (-exitRC → RuleCheck: OK):
HYDRATECURVE LABEL="HYD", PRESSURE=(10,30,60,100,150,200) bara, \
TEMPERATURE=(4.5,12.1,17.3,20.6,23.1,24.9) C
NETWORKCOMPONENT TYPE=FLOWPATH, TAG=FLOWPATH_1
...
HYDRATECHECK HYDRATECURVE="HYD"
ENDNETWORKCOMPONENT
The output variable is DTHYD, and its sign is the opposite of a margin.
It is a SECTION variable in C, described in the catalog as "Difference
between hydrate and section temperature", and it is T_hydrate(P) − T_fluid:
| section | P, bara | TM, °C |
curve T_hyd, °C |
DTHYD, °C |
meaning |
|---|---|---|---|---|---|
| inlet | 200.1 | 40.0 | 24.9 | −15.1 | 15.1 K of margin, safe |
| mid | 161.6 | 22.6 | 23.5 | +1.0 | 1.0 K inside the hydrate region |
| arrival | 121.5 | 8.4 | 21.7 | +13.4 | 13.4 K of subcooling, hydrate risk |
So positive DTHYD is subcooling into the hydrate region and negative is the
safe margin — reading it as a margin inverts every conclusion. Those numbers
also reproduce a linear interpolation of the supplied curve to about 0.1 K, which
is the proof that OLGA is interpolating the exported curve and not modelling
hydrates itself.
Because the interpolation is linear, curve resolution is an accuracy choice, and the error concentrates where the curve is steepest — the low-pressure end. Measured against a 39-point reference for a wet gas over 10–200 bara:
| curve points | max error over 10–200 bara | max error within a 120–200 bara band |
|---|---|---|
| 4 | 4.11 K | – |
| 6 | 2.55 K | 0.05 K |
| 10 | 1.31 K | – |
| 20 | 0.48 K | 0.01 K |
| 30 | 0.17 K | – |
A steady high-pressure line is nearly straight on this curve, so six points are enough. A case that depressurises — blowdown, shut-in, restart, or a rate sweep that walks the arrival down — spends its time in the steep part, where four points cost 4 K of hydrate temperature. Span the pressures the case will actually visit and use at least 20 points whenever the range extends below about 50 bara.
Two further rules:
- Inhibited curves must be exported, not assumed. OLGA's Hammerschmidt fallback is a correlation shift; a MEG or methanol curve from the NeqSim fluid with the inhibitor present is the whole point of exporting.
- State the curve on every hydrate result. The label, the pressure range, the
point count and the fluid it came from. A
DTHYDnumber without them is not reproducible.
Discretising the geometry
OLGA's batch engine does not discretise. It consumes a PIPE list that already
carries NSEGMENT and LSEGMENT. OLGA's own "discretize geometry" lives in the
Geometry editor and Tools/ProfileGenerator/ProfileGeneratorTool.exe, both of
which are GUI-only — the tool has no command line and simply opens a window if you
pass it arguments. So an automated workflow has to produce the section list itself.
Use discretize_route rather than hand-picking NSEGMENT:
from olga_multiphase_simulator.geometry import discretize_route
mesh = discretize_route(
kp, elevation, # route waypoints, m
target_section_length=500.0,
boundary_section_length=100.0, # grade toward inlet and outlet
max_adjacent_ratio=1.5, # limit the jump between neighbours
refine_low_points=True, # refine where liquid accumulates
)
print(mesh.summary())
genkey_pipes = mesh.to_genkey(diameter_m=0.355, roughness_m=4.5e-5,
wall_label="PIPEWALL")
summary() returns the numbers to record with the run: total_sections,
min_section_length_m, max_section_length_m and max_adjacent_ratio. The
ratio is enforced across pipe boundaries as well as inside a pipe, because a step
change in section length is a numerical error source, and pipe lengths follow the
route including elevation change, not the horizontal distance.
A uniform "N sections per leg" mesh is the usual shortcut and the usual mistake: on a survey-derived route the legs are unequal, so a fixed count produces a section-length jump at every leg boundary. Always report the mesh statistics, and demonstrate grid independence by halving the target section length and confirming the answer moves less than the accuracy being claimed.
Working With NeqSim
OLGA and NeqSim are complementary; use each for what it is good at.
| Question | Tool |
|---|---|
| Transient liquid surge, terrain slugging, shut-in/restart, blowdown dynamics | OLGA |
| Steady-state ΔP and holdup on a gas-dominated line | NeqSim TwoFluidPipe (mechanistic; ΔP within ~3 % of OLGA) |
| Steady-state ΔP screening, single-phase or moderately liquid-loaded | NeqSim PipeBeggsAndBrills (single-phase within 0.1–0.3 % of OLGA) |
| Line pack, rate ramp, shut-in on a gas-dominated line | NeqSim TwoFluidPipe.runTransient (within 0.13 % of OLGA on a line-pack step) |
| Transport delay / arrival lag inside a flowsheet | NeqSim PipeBeggsAndBrills.runTransient — a relaxation lag only, no mass storage |
| Valve slam, surge | NeqSim WaterHammerPipe |
| Phase envelope, hydrate curve, wax appearance temperature, fluid properties | NeqSim thermodynamics |
| Topside process response to an arriving slug | NeqSim ProcessSystem / runTransient |
Choose the NeqSim model against the liquid loading, not by habit. Beggs &
Brill is calibrated for no-slip liquid fractions down to about 0.01–0.02; below
that its two-phase friction multiplier is extrapolated and ΔP is over-predicted
by 30–60 % on a large-bore high-pressure gas line. Above that it is a reasonable
conservative bound. TwoFluidPipe is mechanistic and matches OLGA on ΔP for
gas-dominated flow, but its holdup runs 2–4× OLGA and its transient is not usable
for liquid-rich lines. The authority on which NeqSim model applies, and on its
current measured accuracy and open defects, is the neqsim-flow-assurance
skill — read it before quoting a NeqSim pipeline number.
Typical composition: NeqSim characterises the fluid and produces the hydrate and
wax envelopes, OLGA transports it transiently, and the OLGA arrival trends
(PT, TM, liquid rates) become the boundary condition for the NeqSim topside
model. Screen first with the community flow-regime and slug skills; escalate to
OLGA only when the transient actually matters.
When the two models disagree, audit the correlation term by term
A headline gap in ΔP or holdup is not evidence of a bug in either code, and it is not evidence of a modelling limitation either — until the intermediate terms have been compared. Do this before writing either conclusion:
- Reimplement the steady-state correlation from the published equations in a few dozen lines of Python, driven from the same flashed NeqSim fluid object so composition and properties cannot differ.
- Run the simulator on a short, single-increment pipe (1 m, one segment). A long segment lets the pressure change within the increment shift the state at which the correlation is evaluated; a 10% holdup deviation seen over a 100 m segment vanished entirely at 1 m.
- Compare every intermediate, not just the answer: no-slip liquid fraction, Froude number, the regime-boundary numbers, the horizontal holdup, the velocity number, the inclination factor, the two-phase friction multiplier, the no-slip friction factor, then ΔP.
- Sweep the inclination as well as the horizontal case. Terms that only appear in the inclination correction are invisible in a horizontal test, and they are raised to powers as high as 3.8, so a small input error becomes a large output error.
This procedure found four real defects in PipeBeggsAndBrills that a
horizontal end-to-end comparison had hidden: a pipe angle converted from
degrees to radians twice, a distributed-regime boundary tested against the
wrong limit, gravity counted twice in the liquid velocity number, and a
volume-corrected density mixed with an uncorrected one inside a single formula.
Two residual differences are expected and are not defects:
- Beggs & Brill was calibrated on small-bore air/water loops, for no-slip liquid fractions down to roughly 0.01–0.02. A large-bore high-pressure gas line carrying a few mass per cent of condensate runs below that range, so the two-phase friction multiplier is being extrapolated. On a 74 km 14-inch line at a mean no-slip liquid fraction of 0.009 the multiplier reached 1.42, which accounted for the entire difference against the transient two-fluid model: removing it brought the correlation from 124 bar to 87 bar against OLGA's 78 bar and a single-phase Darcy check of 84 bar.
- A mechanistic two-fluid code computes an interfacial friction close to single-phase in this regime, because the condensate rides as a thin film rather than loading the gas core.
Do not reach for the Payne et al. (1979) holdup correction to explain such a
gap without checking the numbers. The Beggs & Brill S factor is monotonically
increasing in y = λ_L / H_L² over
…(truncated)