← Digital Garden
Part 2 of 3 · Black Oil PVT from Scratch

PR3 EOS Engine in Python: Implementation and Verification

29 May 2026

Part 1 established the characterised fluid — 12 components with Tc, Pc, ω, BIPs and volume shifts computed from MW and SG using Kesler-Lee correlations, all verified against commercial software exactly.

This part builds the PR3 EOS engine and computes the untuned bubble point pressure. The implementation follows Peneloux (1982) and Peng-Robinson (1976) exactly. All results are verified against the commercial software untuned output of 200.84 bar at 107°C.

Three implementation details are not explicitly documented in the software manual or standard references. Each is identified and verified here.

PR3 Pure-Component Parameters

The Peng-Robinson EOS describes fluid pressure as a function of molar volume:

P = RT / (V − b) − a(T) / [V(V + b) + b(V − b)]

Two parameters per component at reservoir temperature T = 380.15 K:

Attractive parameter ai:

ai = ΩA · (R²Tc² / Pc) · αi αi = [1 + κi(1 − √Tr,i)]²

Covolume bi:

bi = ΩB · RTc / Pc

with ΩA = 0.45724, ΩB = 0.077796, R = 83.14472 cm³·bar/(mol·K).

PR3 kappa — the third parameter:

Standard PR2 uses a quadratic κ that becomes unreliable for heavy components. PR3 switches to a cubic formula at ω > 0.491:

κ = 0.37464 + 1.54226·ω − 0.26992·ω² (ω ≤ 0.491) κ = 0.379642 + 1.48503·ω − 0.164423·ω² + 0.016666·ω³ (ω > 0.491)

For this fluid the cubic formula activates for C16+ (ω = 1.374). This is the primary reason PR3 is used for heavy oils — PR2 underestimates attraction forces for large acentric factors.

Table 1 — Pure-component PR3 parameters at T = 380.15 K
Component Tr κ α ai (cm⁶·bar/mol²) bi (cm³/mol)
N₂3.0120.4360.4626.84×10⁵24.05
CO₂1.2480.7080.8413.34×10⁶26.68
C11.9940.3950.7011.75×10⁶26.78
C21.2450.5240.8825.33×10⁶40.45
C31.0280.6030.9831.00×10⁷56.34
iC40.9320.6501.0461.51×10⁷72.37
nC40.8940.6741.0751.62×10⁷72.44
iC50.8260.7111.1342.24×10⁷87.87
nC50.8100.7451.1552.39×10⁷90.13
C60.7490.8121.2303.33×10⁷109.05
C7-C150.5931.0611.5478.78×10⁷181.44
C16+0.3832.1533.3161.49×10⁹925.84

Mixing Rules

Van der Waals mixing rules with BIPs from Part 1:

amix = Σᵢ Σⱼ zᵢzⱼ √(aᵢaⱼ) · (1 − kij) bmix = Σᵢ zᵢbᵢ

The Peneloux volume shifts from Part 1 enter as:

ci = si · bi cmix = Σᵢ zᵢci

At P = 213 bar, T = 380.15 K, the dimensionless mixture parameters are:

A = amix·P / (RT)² = 18.353 Braw = bmix·P / RT = 1.402 Beff = (bmix − cmix)·P / RT = 0.920

Implementation Detail 1 — The Cubic Solver Uses Braw

The PR cubic in Z:

Z³ + (B−1)·Z² + (A − 2B − 3B²)·Z + (B³ + B² − A·B) = 0

Per the Peneloux (1982) formulation, the cubic is solved with Braw, the unshifted covolume parameter. The volume shift is not incorporated into the cubic coefficients.

At P = 213 bar, Braw = 1.402. This gives a single real root Zraw = 1.624 — physically correct for a compressed liquid above its bubble point. As pressure decreases toward the bubble point, Braw drops and two roots appear.

The distinction between Braw and Beff matters practically: using Beff in the cubic gives incorrect root locations and therefore incorrect fugacity coefficients.

Implementation Detail 2 — Reported Z and Volume Are Shifted

The commercial software does not report Zraw. It reports the Peneloux-corrected quantities:

Zreported = Zraw − cmix·P / RT Vreported = Vraw − cmix

This is verified by back-calculation from the reported Z = 1.0793 at bubble point:

Zraw = 1.0793 + (71.6 × 200.8) / (83.14 × 380.15) = 1.534

Which is exactly the root of the PR cubic at P = 200.8 bar with Braw. The density computed from Vreported matches to 0.3%.

Table 2 — Raw vs shifted quantities at bubble point (200.84 bar)
QuantityRaw (unshifted)ShiftedCommercial software
Zliquid1.5341.0791.0793
Vliquid (cm³/mol)241.4169.8169.800
Density (kg/m³)516735732.7

Implementation Detail 3 — Volume Shift Does Not Enter Fugacity

The PR fugacity coefficient for component i:

ln φᵢ = (bᵢ/bmix)·(Z−1) − ln(Z−B) − A/(2√2·B) · (daᵢ/amix − bᵢ/bmix) · ln[(Z + (1+√2)·B) / (Z + (1−√2)·B)]

where dai = 2·Σⱼ zⱼaᵢⱼ.

No volume shift term appears. Z and B here are Zraw and Braw respectively. Peneloux (1982) proved that the shift cancels exactly in the fugacity ratio φᵢᴸ/φᵢᵛ — it therefore has no effect on K-values or bubble point pressure. This is why the untuned bubble point can be reproduced exactly without knowing the volume shifts.

Table 3 — Fugacity coefficients at 213 bar
Componentln(φᵢ)φᵢVolatility
N₂1.2473.480High
C10.5561.743High — primary driver of bubble point
CO₂−0.0700.932Moderate
C3−1.2840.277Low
C6−3.4020.033Very low
C7-C15−6.2980.002Negligible
C16+−23.776~0Anchors liquid phase

Bubble Point Solver

At bubble point the liquid composition equals the feed (xi = zi) and the incipient vapour satisfies:

Σᵢ Kᵢzᵢ = 1 Kᵢ = φᵢᴸ / φᵢᵛ

The solution procedure uses Wilson K-values as initial estimates, successive substitution to convergence, and Brent's method on the outer pressure loop.

Result

The residual scan confirms a sign change between 200 and 210 bar:

P (bar)Residual
150+0.149
170+0.078
190+0.024
200+0.002
210−0.018
230−0.051

Brent's method converges to:

Pbub = 200.84 bar
Table 4 — K-values: Python vs commercial software
ComponentziyiKi (Python)Ki (Reference)
N₂0.004100.011862.8922.8917
CO₂0.037990.050611.3321.3322
C10.399160.797331.9981.9975
C20.060720.062931.0361.0364
C30.054490.038300.7030.7029
C60.023420.004960.2120.2116
C7-C150.197740.008290.0420.0419
C16+0.15903~0~04.49×10⁻⁶

All K-values match the commercial software output to 4 significant figures.

Summary of Implementation Details

Three details not explicitly stated in the software manual, all consistent with Peneloux (1982):

DetailStandard assumptionCorrect implementation
B in cubic solverUse Beff (shifted)Use Braw — per Peneloux (1982)
Z and V reportedRaw PR valuesShifted: subtract cmix·P/RT
Volume shift in fugacityAppliedNot applied — cancels in φᴸ/φᵛ

The Untuned Gap

The untuned EOS gives 200.84 bar. The lab measurement is 213.1 bar — a gap of 12.3 bar (6.1%). This is expected for a default characterisation with no regression. In Part 3 we close this gap by adjusting the critical properties of the pseudocomponents to match the lab bubble point.

Data and Code

Full Jupyter notebooks: github.com/eskoantg/PVTi_cross_check

Volve dataset: equinor.com/energy/volve-data-sharing

See Part 1 for data sources and disclaimer.

▶ Nomenclature (click to expand)
SymbolDescriptionUnits
PPressurebar
TTemperatureK
RUniversal gas constant (83.14472)cm³·bar/(mol·K)
VMolar volumecm³/mol
ZCompressibility factor
aiPure-component attractive parametercm⁶·bar/mol²
biPure-component covolumecm³/mol
ciPeneloux volume shiftcm³/mol
siDimensionless shift parameter
αiTemperature correction function
κiPR kappa function
ωAcentric factor
TrReduced temperature = T/Tc
TcCritical temperatureK
PcCritical pressurebar
ΩAEOS constant (0.45724)
ΩBEOS constant (0.077796)
amixMixture attractive parametercm⁶·bar/mol²
bmixMixture covolumecm³/mol
cmixMixture volume shiftcm³/mol
ADimensionless mixture attractive parameter
BrawDimensionless unshifted covolume
BeffDimensionless shifted covolume
kijBinary interaction parameter
daiPartial derivative of amix w.r.t. compositioncm⁶·bar/mol²
ziOverall mole fraction
xiLiquid mole fraction
yiVapour mole fraction
KiK-value (equilibrium ratio)
φiFugacity coefficient