Skip to the content.

PVT Workflow: From Lab Data to Tuned Fluid Model

This document describes the complete workflow for generating and tuning fluid models from laboratory PVT data in NeqSim.

Typical Laboratory PVT Report Data

A standard PVT laboratory report contains the following sections. Below are example data tables showing the format typically received from labs like Core Lab, Schlumberger, or Intertek.

Sample Information

Well Name:           A-1
Sample Type:         Bottom Hole Sample (BHS)
Sampling Depth:      2850 m TVD
Sampling Date:       2024-06-15
Reservoir Pressure:  285 bara
Reservoir Temp:      98 °C (371.15 K)
Saturation Pressure: 248 bara (Bubble Point)

Compositional Analysis (Mole %)

Component Mol % MW (g/mol) Density (g/cm³)
N₂ 0.34 28.01 -
CO₂ 3.53 44.01 -
H₂S 0.00 34.08 -
C₁ 70.78 16.04 -
C₂ 8.94 30.07 -
C₃ 5.05 44.10 -
i-C₄ 0.85 58.12 -
n-C₄ 1.68 58.12 -
i-C₅ 0.62 72.15 -
n-C₅ 0.79 72.15 -
C₆ 0.83 86.18 0.664
C₇ 1.06 92.2 0.7324
C₈ 1.06 104.6 0.7602
C₉ 0.79 119.1 0.7677
C₁₀ 0.57 133.0 0.790
C₁₁ 0.38 147.0 0.795
C₁₂+ 2.73 263.0 0.854
Total 100.00    

Constant Composition Expansion (CCE) at 98°C

Pressure (bara) Relative Volume Y-Factor Oil Density (kg/m³) Oil Viscosity (cP)
350 0.9521 - 612.3 0.285
300 0.9712 - 625.1 0.312
280 0.9845 - 632.8 0.328
248 (Psat) 1.0000 - 645.2 0.352
220 1.0523 2.145 658.4 0.385
200 1.1124 2.287 670.1 0.412
180 1.1892 2.456 682.5 0.445
150 1.3245 2.712 698.2 0.498
120 1.5421 3.024 715.8 0.562
100 1.7856 3.312 728.4 0.615

Differential Liberation (DLE) at 98°C

Pressure (bara) Rs (Sm³/Sm³) Bo (m³/Sm³) Oil Density (kg/m³) Oil Viscosity (cP) Gas Z-factor Gas Gravity
248 (Psat) 152.3 1.4521 645.2 0.352 - -
220 138.5 1.4012 658.4 0.385 0.862 0.745
200 125.2 1.3654 670.1 0.412 0.851 0.768
180 112.8 1.3285 682.5 0.445 0.838 0.792
150 94.5 1.2756 698.2 0.498 0.815 0.825
120 75.2 1.2198 715.8 0.562 0.792 0.861
100 61.8 1.1812 728.4 0.615 0.775 0.892
80 48.2 1.1425 742.1 0.685 0.758 0.928
50 28.5 1.0912 762.5 0.812 0.732 0.975
20 8.5 1.0385 785.2 1.025 0.712 1.045
1.01 (STO) 0.0 1.0000 825.4 2.850 - -

Constant Volume Depletion (CVD) at 98°C (for Gas Condensates)

Pressure (bara) Liquid Dropout (%) Z-factor Cumulative Gas Produced (%) Liquid Density (kg/m³)
285 (Pdew) 0.0 0.892 0.0 -
260 4.2 0.875 8.5 612.5
230 8.5 0.856 18.2 628.4
200 12.8 0.838 28.5 645.2
170 15.2 0.821 39.8 658.7
140 14.5 0.805 51.2 672.1
110 12.1 0.792 62.8 685.4
80 8.8 0.781 74.5 698.2
50 5.2 0.772 86.2 712.5

Multi-Stage Separator Test

Test Conditions:

Stage Pressure (bara) Temperature (°C)
1 (HP Sep) 45.0 45.0
2 (LP Sep) 8.0 35.0
3 (Stock Tank) 1.01 15.0

Results:

Property Stage 1 Stage 2 Stage 3 Total
GOR (Sm³/Sm³) 95.2 18.5 8.2 121.9
Gas Gravity 0.752 0.985 1.245 0.812
Oil Density (kg/m³) 712.5 758.2 825.4 -
Oil Viscosity (cP) 0.52 0.85 2.45 -

Stock Tank Oil Properties:

Viscosity Data (Separate Measurements)

Pressure (bara) Temperature (°C) Oil Viscosity (cP) Gas Viscosity (cP)
285 98 0.285 0.0185
248 98 0.352 0.0178
200 98 0.412 0.0165
150 98 0.498 0.0148
100 98 0.615 0.0132
248 80 0.425 0.0168
248 60 0.585 0.0155

Swelling Test (CO₂ Injection)

Cumulative CO₂ Injected (mol%) Saturation Pressure (bara) Relative Swelling Factor Oil Density (kg/m³)
0.0 248.0 1.000 645.2
5.0 268.5 1.025 638.4
10.0 292.1 1.052 630.1
15.0 318.4 1.082 620.5
20.0 348.2 1.115 608.2
25.0 382.5 1.152 594.8
30.0 421.8 1.195 578.5

Overview

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Lab PVT Data  │───▶│  Initial Fluid  │───▶│  EOS Regression │───▶│  Export Model   │
│   (CCE,DLE,CVD) │    │  Characterization│    │  (Parameter Fit)│    │  (E300, CSV)    │
└─────────────────┘    └─────────────────┘    └─────────────────┘    └─────────────────┘

Step 1: Create Initial Fluid from Composition

Start with laboratory-reported composition and characterize heavy fractions:

import neqsim.thermo.system.SystemSrkEos;
import neqsim.thermo.system.SystemInterface;

// Create fluid at reservoir conditions
SystemInterface fluid = new SystemSrkEos(373.15, 250.0);  // T(K), P(bar)

// Add defined components (from lab composition)
fluid.addComponent("nitrogen", 0.34);
fluid.addComponent("CO2", 3.53);
fluid.addComponent("methane", 70.78);
fluid.addComponent("ethane", 8.94);
fluid.addComponent("propane", 5.05);
fluid.addComponent("i-butane", 0.85);
fluid.addComponent("n-butane", 1.68);
fluid.addComponent("i-pentane", 0.62);
fluid.addComponent("n-pentane", 0.79);
fluid.addComponent("n-hexane", 0.83);

// Add C7+ fractions (TBP cuts from lab)
fluid.addTBPfraction("C7", 1.06, 92.2/1000.0, 0.7324);   // name, mole%, MW(kg/mol), SG
fluid.addTBPfraction("C8", 1.06, 104.6/1000.0, 0.7602);
fluid.addTBPfraction("C9", 0.79, 119.1/1000.0, 0.7677);
fluid.addTBPfraction("C10", 0.57, 133.0/1000.0, 0.79);

// Add plus fraction
fluid.addPlusFraction("C20+", 2.11, 381.0/1000.0, 0.88);

// Characterize plus fraction into pseudo-components
fluid.getCharacterization().characterisePlusFraction();

// Initialize database and set mixing rule
fluid.createDatabase(true);
fluid.setMixingRule(2);  // Classic mixing rule

Step 2: Add Laboratory PVT Data

Import experimental data from CCE, DLE, CVD, and separator tests:

import neqsim.pvtsimulation.regression.PVTRegression;
import neqsim.pvtsimulation.regression.RegressionParameter;

PVTRegression regression = new PVTRegression(fluid);

// CCE (Constant Composition Expansion) data
double[] ccePressures = {400, 350, 300, 280, 260, 240, 220, 200, 180};  // bara
double[] cceRelVol = {0.95, 0.97, 0.99, 1.00, 1.02, 1.05, 1.10, 1.18, 1.30};
double cceTemperature = 373.15;  // K
regression.addCCEData(ccePressures, cceRelVol, cceTemperature);

// DLE (Differential Liberation) data
double[] dlePressures = {280, 240, 200, 160, 120, 80, 40, 1.01325};  // bara
double[] dleRs = {150, 130, 110, 85, 60, 35, 15, 0};  // Sm³/Sm³
double[] dleBo = {1.45, 1.40, 1.35, 1.28, 1.20, 1.12, 1.06, 1.02};  // m³/Sm³
double[] dleOilDensity = {650, 680, 710, 740, 770, 800, 830, 850};  // kg/m³
double dleTemperature = 373.15;  // K
regression.addDLEData(dlePressures, dleRs, dleBo, dleOilDensity, dleTemperature);

// CVD (Constant Volume Depletion) data - for gas condensates
double[] cvdPressures = {350, 300, 250, 200, 150, 100};  // bara
double[] cvdLiquidDropout = {0, 5, 12, 18, 15, 10};  // volume %
double[] cvdZFactor = {0.85, 0.82, 0.80, 0.78, 0.77, 0.76};
double cvdTemperature = 373.15;  // K
regression.addCVDData(cvdPressures, cvdLiquidDropout, cvdZFactor, cvdTemperature);

// Separator test data
regression.addSeparatorData(
    125.0,    // GOR (Sm³/Sm³)
    1.35,     // Bo
    35.0,     // API gravity
    50.0,     // separator pressure (bar)
    313.15,   // separator temperature (K)
    373.15    // reservoir temperature (K)
);

Step 3: Configure Regression Parameters

Select which EOS parameters to tune:

// Binary Interaction Parameters (BIPs)
regression.addRegressionParameter(RegressionParameter.BIP_METHANE_C7PLUS);
regression.addRegressionParameter(RegressionParameter.BIP_C2C6_C7PLUS);
regression.addRegressionParameter(RegressionParameter.BIP_CO2_HC);

// Critical property multipliers for C7+ pseudo-components
regression.addRegressionParameter(RegressionParameter.TC_MULTIPLIER_C7PLUS);
regression.addRegressionParameter(RegressionParameter.PC_MULTIPLIER_C7PLUS);
regression.addRegressionParameter(RegressionParameter.OMEGA_MULTIPLIER_C7PLUS);

// Volume shift for density matching
regression.addRegressionParameter(RegressionParameter.VOLUME_SHIFT_C7PLUS);

// Viscosity parameters (if tuning viscosity)
regression.addRegressionParameter(RegressionParameter.VISCOSITY_LBC_MULTIPLIER);
regression.addRegressionParameter(RegressionParameter.VISCOSITY_PEDERSEN_ALPHA);

// Custom bounds (optional)
regression.addRegressionParameter(
    RegressionParameter.BIP_METHANE_C7PLUS, 
    0.0,    // lower bound
    0.15,   // upper bound
    0.05    // initial guess
);

// Set experiment weights (optional)
regression.setExperimentWeight(ExperimentType.CCE, 1.0);
regression.setExperimentWeight(ExperimentType.DLE, 1.5);  // Higher weight for DLE
regression.setExperimentWeight(ExperimentType.SEPARATOR, 1.0);

// Optimization settings
regression.setMaxIterations(200);
regression.setTolerance(1e-8);
regression.setVerbose(true);

Step 4: Run Regression

Execute the optimization:

import neqsim.pvtsimulation.regression.RegressionResult;

RegressionResult result = regression.runRegression();

// Get the tuned fluid
SystemInterface tunedFluid = result.getTunedFluid();

// Check results
System.out.println("Final chi-square: " + result.getChiSquare());
System.out.println("Optimized parameters:");
double[] params = result.getOptimizedParameters();
for (int i = 0; i < params.length; i++) {
    System.out.println("  Parameter " + i + ": " + params[i]);
}

Step 5: Validate and Compare with Lab Data

Generate comparison reports:

import neqsim.pvtsimulation.util.PVTReportGenerator;
import neqsim.pvtsimulation.simulation.*;

// Run PVT simulations with tuned fluid
ConstantMassExpansion cce = new ConstantMassExpansion(tunedFluid);
cce.setTemperature(373.15);
cce.setPressures(ccePressures);
cce.runCalc();

DifferentialLiberation dle = new DifferentialLiberation(tunedFluid);
dle.setTemperature(373.15);
dle.setPressures(dlePressures);
dle.runCalc();

// Create report generator
PVTReportGenerator report = new PVTReportGenerator(tunedFluid);
report.setProjectInfo("Field X Development", "Well A-1 Sample");
report.setReservoirConditions(250.0, 100.0);  // P(bar), T(°C)

// Add simulation results
report.addCCE(cce);
report.addDLE(dle);

// Add lab data for comparison
for (int i = 0; i < ccePressures.length; i++) {
    report.addLabCCEData(ccePressures[i], "RelVol", cceRelVol[i], "");
}
for (int i = 0; i < dlePressures.length; i++) {
    report.addLabDLEData(dlePressures[i], "Bo", dleBo[i], "m³/Sm³");
    report.addLabDLEData(dlePressures[i], "Rs", dleRs[i], "Sm³/Sm³");
}

// Generate comparison with statistics
String comparison = report.generateLabComparison();
System.out.println(comparison);
// Output includes AAD (Average Absolute Deviation) and ARE (Average Relative Error)

// Generate full Markdown report
String fullReport = report.generateMarkdownReport();

Step 6: Export to Reservoir Simulator

Export the tuned fluid model to Eclipse E300 format:

import neqsim.blackoil.io.EclipseEOSExporter;
import java.nio.file.Path;

// Simple export with default settings
EclipseEOSExporter.toFile(tunedFluid, Path.of("PVT_TUNED.INC"));

// Export with custom configuration
EclipseEOSExporter.ExportConfig config = new EclipseEOSExporter.ExportConfig()
    .setUnits(EclipseEOSExporter.Units.FIELD)      // METRIC or FIELD
    .setReferenceTemperature(373.15)               // Reservoir temp (K)
    .setIncludePVTO(true)                          // Live oil table
    .setIncludePVTG(true)                          // Wet gas table
    .setIncludePVTW(true)                          // Water properties
    .setIncludeDensity(true)                       // Stock tank densities
    .setComment("Tuned to Well A-1 PVT data");

EclipseEOSExporter.toFile(tunedFluid, Path.of("PVT_FIELD.INC"), config);

// Or get as string for inspection
String eclipseContent = EclipseEOSExporter.toString(tunedFluid, config);
System.out.println(eclipseContent);

Generated Eclipse Keywords

The exporter produces standard Eclipse keywords:

Unit Systems

Unit System Pressure Density GOR/CGR Viscosity
METRIC bar kg/m³ Sm³/Sm³ mPa·s
FIELD psia lb/ft³ scf/stb cp

Export Compositional EOS to E300 Format

In addition to black-oil PVT tables, you can export the full compositional EOS model to Eclipse E300 format. This preserves all component properties and binary interaction coefficients:

import neqsim.thermo.util.readwrite.EclipseFluidReadWrite;

// Export tuned fluid to E300 compositional format
EclipseFluidReadWrite.write(tunedFluid, "TUNED_FLUID.e300", 100.0);  // reservoir temp in °C

// Or get as string for inspection
String e300Content = EclipseFluidReadWrite.toE300String(tunedFluid, 100.0);
System.out.println(e300Content);

The E300 compositional file includes:

Keyword Description
NCOMPS Number of components
EOS Equation of state (SRK, PR)
RTEMP Reservoir temperature
CNAMES Component names
TCRIT Critical temperatures (K)
PCRIT Critical pressures (bar)
ACF Acentric factors
MW Molecular weights (g/mol)
TBOIL Normal boiling points (K)
VCRIT Critical volumes (m³/kmol)
SSHIFT Volume translation parameters
PARACHOR Parachor values for IFT
ZI Mole fractions
BIC Binary interaction coefficients

Read E300 File Back into NeqSim

// Read the E300 file back into a NeqSim fluid
SystemInterface importedFluid = EclipseFluidReadWrite.read("TUNED_FLUID.e300");

// Set conditions and run flash
importedFluid.setPressure(200.0, "bara");
importedFluid.setTemperature(100.0, "C");
ThermodynamicOperations ops = new ThermodynamicOperations(importedFluid);
ops.TPflash();

Step 7: Export CSV for Other Applications

Generate CSV files for spreadsheet analysis or other simulators:

// CCE data
String cceCSV = report.generateCCECSV();

// DLE data
String dleCSV = report.generateDLECSV();

// CVD data  
String cvdCSV = report.generateCVDCSV();

// Viscosity data
String viscCSV = report.generateViscosityCSV();

// Density data
String densCSV = report.generateDensityCSV();

// Swelling test
String swellCSV = report.generateSwellingCSV();

// GOR data
String gorCSV = report.generateGORCSV();

// MMP data
String mmpCSV = report.generateMMPCSV();

Available Regression Parameters

Parameter Description Typical Bounds
BIP_METHANE_C7PLUS BIP between CH₄ and C7+ 0.0 - 0.10
BIP_C2C6_C7PLUS BIP between C2-C6 and C7+ 0.0 - 0.05
BIP_CO2_HC BIP between CO₂ and hydrocarbons 0.08 - 0.18
BIP_N2_HC BIP between N₂ and hydrocarbons 0.02 - 0.12
VOLUME_SHIFT_C7PLUS Volume shift multiplier for C7+ 0.8 - 1.2
TC_MULTIPLIER_C7PLUS Critical temperature multiplier 0.95 - 1.05
PC_MULTIPLIER_C7PLUS Critical pressure multiplier 0.95 - 1.05
OMEGA_MULTIPLIER_C7PLUS Acentric factor multiplier 0.90 - 1.10
PLUS_MOLAR_MASS_MULTIPLIER Plus fraction MW adjustment 0.90 - 1.10
GAMMA_ALPHA Whitson gamma distribution shape 0.5 - 4.0
GAMMA_ETA Whitson gamma min MW (η) 75.0 - 95.0
VISCOSITY_LBC_MULTIPLIER LBC viscosity correlation factor 0.8 - 1.5
VISCOSITY_PEDERSEN_ALPHA Pedersen viscosity parameter 0.5 - 2.0

Separator Optimization

Find optimal separator conditions:

import neqsim.pvtsimulation.simulation.MultiStageSeparatorTest;

MultiStageSeparatorTest sepTest = new MultiStageSeparatorTest(tunedFluid);
sepTest.setReservoirConditions(250.0, 373.15);  // P(bar), T(K)

// Add separator stages
sepTest.addSeparatorStage(50.0, 40.0, "HP Separator");   // P(bar), T(°C)
sepTest.addSeparatorStage(10.0, 30.0, "LP Separator");
sepTest.addSeparatorStage(1.01325, 15.0, "Stock Tank");

// Run simulation
sepTest.run();

// Optimize first stage pressure/temperature
MultiStageSeparatorTest.OptimizationResult optResult = 
    sepTest.optimizeFirstStageSeparator(
        5.0, 80.0, 16,    // pressure: min, max, steps
        20.0, 60.0, 9     // temperature: min, max, steps
    );

System.out.println("Optimal P: " + optResult.getOptimalPressure() + " bara");
System.out.println("Optimal T: " + optResult.getOptimalTemperature() + " °C");
System.out.println("Max Recovery: " + optResult.getMaximumOilRecovery());
System.out.println("GOR at optimum: " + optResult.getGorAtOptimum() + " Sm³/Sm³");

Complete Example

See PVTRegressionTest.java for working examples.