Skip to the content.

NeqSim can reproduce common pressure-volume-temperature (PVT) laboratory experiments with an equation-of-state fluid. Use measured composition and characterized heavy fractions as the starting point, then compare simulated and measured values at the same temperature and pressure schedule.

Units and naming

The simulation classes do not all use the same setter signatures. Apply these conventions explicitly:

Quantity Convention
EOS constructor temperature K
EOS constructor pressure bara
addTBPfraction and addPlusFraction molar mass kg/mol
TBP/plus-fraction density Specific gravity (numerically g/cm³); kg/m³ inputs are auto-detected and converted
BasePVTsimulation.setTemperature(value, unit) Use "C" or "K" explicitly
Pressure arrays bara
SeparatorTest.setSeparatorConditions temperature array K
ViscositySim.setTemperaturesAndPressures temperature array K
ViscositySim viscosity outputs Pa·s; multiply by 1000 for cP

addTBPfraction and addPlusFraction preserve + in a pseudo-component label when they append the internal _PC suffix, for example C20+_PC. However, the default Pedersen plus-fraction characterization parses a terminal carbon number from that label. Use a numeric label such as C20, not C20+, before calling characterisePlusFraction().

Supported experiments

Experiment Class Main results
Constant mass expansion (CCE/CME) ConstantMassExpansion Relative volume, liquid volume as percent of saturation volume, gas Z-factor, Y-function
Constant volume depletion (CVD) ConstantVolumeDepletion Saturation pressure, relative volume, liquid volume as percent of saturation volume, cumulative mole-percent depletion
Differential liberation (DL) DifferentialLiberation Saturation pressure, Bo, Bg, Rs, oil density, gas Z-factor
Legacy separator test SeparatorTest Per-stage GOR and oil formation-volume factor arrays
Swelling test SwellingTest Pressure and relative-oil-volume arrays
Pressure/temperature viscosity grid ViscositySim Gas, oil, and aqueous viscosity arrays in Pa·s

SwellingTest does not calculate minimum miscibility pressure (MMP). Determine MMP with a dedicated slim-tube or multi-contact workflow; do not infer it from the swelling-factor curve alone.

Repeated saturation-temperature tuning

SaturationTemperature defaults to a descending 30–1200 K search on a 10 K grid. This selects the uppermost resolved two-phase-to-single-phase crossing, including retrograde and disconnected phase regions, independently of the initial temperature. The result is refined to a bracket width of 1e-5 K. Regions narrower than the coarse grid can be missed; this is not a complete phase-envelope calculation.

For repeated dew-point fitting, explicitly supply bounds that contain the desired upper boundary throughout the pressure and composition range being fitted:

SaturationTemperature simulation = new SaturationTemperature(fluid);
simulation.setTemperatureSearchBounds(260.0, 320.0); // K; establish these for your fluid
simulation.run();
double saturationTemperatureK = simulation.getSaturationTemperature();

This scans the same 10 K grid inside the supplied interval, including both endpoints, and refines its uppermost crossing with full multiphase TP flashes. It re-evaluates the current fluid on every call, so composition and pressure changes do not reuse stale phase classifications. The bounds remain fixed between calls. The 24-component UMR-PRU regression for issue #3795 covers heavy-component factors from 0.5 to 1.5 at 52.1 bara and limits each bounded call to 30 flashes.

If the upper endpoint is multiphase or the interval has no resolved crossing, the calculation falls back to the global search. If that search finds no boundary, the legacy result is 1200 K; it must not be interpreted as a converged saturation point. The caller’s multiphase-check setting is restored even when a flash throws.

Bounds are a caller-supplied physical constraint, not an automatic global guarantee. A local crossing and a single-phase upper endpoint cannot rule out a separate two-phase region above the bounds. Establish a suitable interval using global scans or phase-envelope calculations across the tuning range. Use clearTemperatureSearchBounds() to return to the global search when the interval is no longer justified. Existing callers retain the global behavior; they must opt in to bounded searching to obtain the reduced flash count.

Executable constant-mass-expansion example

This complete Java example uses SI molar masses for every characterized fraction. The pressure schedule and returned arrays share the same index.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import neqsim.pvtsimulation.simulation.ConstantMassExpansion;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemSrkEos;

public final class ConstantMassExpansionExample {
  private static final Logger logger =
      LogManager.getLogger(ConstantMassExpansionExample.class);

  private ConstantMassExpansionExample() {}

  public static void main(String[] args) {
    SystemInterface fluid = new SystemSrkEos(370.65, 350.0);
    fluid.addComponent("nitrogen", 0.39);
    fluid.addComponent("CO2", 0.30);
    fluid.addComponent("methane", 40.20);
    fluid.addComponent("ethane", 7.61);
    fluid.addComponent("propane", 7.95);
    fluid.addComponent("i-butane", 1.19);
    fluid.addComponent("n-butane", 4.08);
    fluid.addComponent("i-pentane", 1.39);
    fluid.addComponent("n-pentane", 2.15);
    fluid.addComponent("n-hexane", 2.79);
    fluid.addTBPfraction("C7", 4.28, 95.0 / 1000.0, 0.729);
    fluid.addTBPfraction("C8", 4.31, 106.0 / 1000.0, 0.749);
    fluid.addTBPfraction("C9", 3.08, 121.0 / 1000.0, 0.770);
    fluid.addTBPfraction("C10", 2.47, 135.0 / 1000.0, 0.786);
    fluid.addTBPfraction("C11", 1.91, 148.0 / 1000.0, 0.792);
    fluid.addTBPfraction("C12", 1.69, 161.0 / 1000.0, 0.804);
    fluid.addTBPfraction("C13", 1.59, 175.0 / 1000.0, 0.819);
    fluid.addTBPfraction("C14", 1.22, 196.0 / 1000.0, 0.833);
    fluid.addTBPfraction("C15", 1.25, 206.0 / 1000.0, 0.836);
    fluid.addTBPfraction("C16", 1.00, 225.0 / 1000.0, 0.843);
    fluid.addTBPfraction("C17", 0.99, 236.0 / 1000.0, 0.840);
    fluid.addTBPfraction("C18", 0.92, 245.0 / 1000.0, 0.846);
    fluid.addTBPfraction("C19", 0.60, 265.0 / 1000.0, 0.857);
    fluid.addPlusFraction("C20", 6.64, 453.0 / 1000.0, 0.918);
    fluid.getCharacterization().getLumpingModel().setNumberOfPseudoComponents(12);
    fluid.getCharacterization().characterisePlusFraction();
    fluid.setMixingRule("classic");

    double[] pressuresBara = {
      351.4, 323.2, 301.5, 275.9, 250.1, 226.1, 205.9, 197.3,
      189.3, 183.3, 165.0, 131.2, 108.3, 85.3, 55.6
    };

    ConstantMassExpansion cce = new ConstantMassExpansion(fluid);
    cce.setTemperature(97.5, "C");
    cce.setPressures(pressuresBara);
    cce.runCalc();

    double[] relativeVolume = cce.getRelativeVolume();
    double[] liquidRelativeVolume = cce.getLiquidRelativeVolume();
    double[] gasZ = cce.getZgas();
    double[] yFunction = cce.getYfactor();

    for (int i = 0; i < pressuresBara.length; i++) {
      logger.info("{} bara: Vrel={}, Vliq={} %, Zgas={}, Y={}", pressuresBara[i],
          relativeVolume[i], liquidRelativeVolume[i], gasZ[i], yFunction[i]);
    }
    logger.info("Saturation pressure: {} bara", cce.getSaturationPressure());
  }
}

The focused documentation regression executes this example and checks representative finite values and array lengths. Treat calculated values as model results, not laboratory measurements.

Current API map

Constant volume depletion

Configure ConstantVolumeDepletion with setTemperature, setPressures, and runCalc. Read getSaturationPressure, getRelativeVolume, getLiquidRelativeVolume, getCummulativeMolePercDepleted, getZmix, and getZgas. The spelling getCummulativeMolePercDepleted is the current compatibility API. getLiquidRelativeVolume returns percent of saturation volume, not a unitless fraction.

Differential liberation

Configure DifferentialLiberation with setTemperature, setPressures, and runCalc. Read getSaturationPressure, getBo, getBg, getRs, getOilDensity, getZgas, and getRelGasGravity. Result arrays align with the pressure schedule.

Separator tests

The legacy SeparatorTest accepts all stages in one call: setSeparatorConditions(temperaturesK, pressuresBara). After runCalc, read the per-stage arrays from getGOR and getBofactor. For named stages, reservoir-condition handling, stock-tank results, and cumulative GOR, prefer MultiStageSeparatorTest; see the PVT simulation overview.

Swelling tests

Set the injection fluid with setInjectionGas, supply cumulative injected-gas mole percentages with setCummulativeMolePercentGasInjected, and call runCalc. Read the pressure and relative-oil-volume arrays from getPressures and getRelativeOilVolume.

Viscosity grids

ViscositySim.setTemperaturesAndPressures(temperaturesK, pressuresBara) defines paired states. After runCalc, use getGasViscosity, getOilViscosity, and getAqueousViscosity. These arrays are in Pa·s.

Calibration workflow

Calibrate a fluid only against traceable measurements and keep a separate validation set. A practical sequence is:

  1. Verify composition, component names, molar-mass units, densities, and test conditions.
  2. Characterize the plus fraction and freeze the chosen lumping scheme.
  3. Compare untuned saturation pressure and volumetric trends with measurements.
  4. Adjust only physically justified characterization or interaction parameters.
  5. Re-run every experiment and report residuals, parameter bounds, and validation results.

There is no universal one-call tuning recipe for all PVT experiments. Regression choices depend on the fluid, available measurements, and intended prediction range.

See also