Skip to the content.

Build a producing-well model from reservoir inflow, wellbore hydraulics, and surface equipment. Keep bottom-hole pressure distinct from wellhead pressure, and specify whether a rate is gas standard volume, stock-tank liquid volume, or actual volume at process conditions.

Table of Contents

Overview

Class Package Role
WellFlow neqsim.process.equipment.reservoir Inflow performance from reservoir to flowing bottom hole
SimpleReservoir neqsim.process.equipment.reservoir Lumped reservoir fluid inventory and producers/injectors
WellSystem neqsim.process.equipment.reservoir Integrated IPR/VLP operating-point workflow
TubingPerformance neqsim.process.equipment.reservoir Tubing correlations and temperature models
PipeBeggsAndBrills neqsim.process.equipment.pipeline Pressure drop along an inclined pipe or tubing
ThrottlingValve neqsim.process.equipment.valve Pressure reduction and valve/choke calculations
ESPPump neqsim.process.equipment.pump Electrical submersible pump equipment

Each Java block is a complete Java 8 program. Save it using its public class name and run with NeqSim and its dependencies on the classpath. Enable assertions with java -ea to verify the stated results. ControllersAndWellsDocumentationTest compiles and runs these exact blocks.

Well Types

Use WellFlow for an inflow relationship and add separate tubing and surface equipment as needed. Its outlet represents the flowing bottom hole, not the wellhead. For measured wellhead boundary conditions, a Stream is sufficient, as in the production-manifold example.

The formerly documented SimpleWell, GasLiftWell, ESPWell, and ChokeValve classes do not exist. Choose the equipment above or an explicit well-network model instead.

IPR Curves

Layered-well copying and saved definitions

WellFlow.copy() and a containing ProcessSystem.copy() preserve the ordered production layers or injection zones, including their streams, pressures, indices, calculated rates, target flags, fracture pressures and barrier stress contrasts. The copy owns independent mutable layers and fluids. Stream aliases within a single serialized process graph are preserved; for example, a reservoir stream that is also a layer stream remains the same object inside that copied process, not the original. Separately copying individual wells does not preserve shared reservoir ownership across those independent copies. Keep the reservoir owner explicit in lifecycle studies.

Older saved objects did not contain the transient layer list. Non-layered legacy objects can be read with an empty, extensible list. A legacy layered object cannot recover omitted zones: reading it now fails with a rebuild-from-inputs diagnostic instead of returning a well whose layer list is null. Recreate those definitions from the original model inputs and save them again. The existing serialVersionUID is retained; this is not a general cross-release serialization guarantee.

WellFlowCopyTest checks pre-run and post-run copies, analytical legacy gas rates, zone allocation, fracture flags, stream aliasing and independent pressure changes. The canonical field-network acceptance suite additionally re-solves a copied live injector, checks mass closure and restored flow, and rejects a lowered fracture limit. These are software/replay checks, not qualification of injectivity or fracture physics. The legacy multi-zone injection path still uses squared-pressure differences for all fluids and assigns total flow in MSm3/day. Copying does not implement a linear liquid law, correct its existing rate-getter unit inconsistency, or validate that basis for water. Use the explicit opt-in below for a mass-basis linear liquid model; existing definitions are not automatically migrated.

Explicit liquid mass injectivity

WellFlow.addLiquidInjectionZone uses an explicitly supplied mass injectivity $J_m$ in kg/(s bar):

\[\dot m_i = J_{m,i}\max(p_{bh}-p_{r,i},0).\]

Both pressures are absolute bara. If a calibrated liquid-volume index $J_v$ is specified in m3/(day bar), convert it with a declared density at the same reference conditions: $J_m=\rho_{ref}J_v/86400$. Do not use the gas-standard MSm3/day conversion for a liquid volume. No density is assumed or fitted by this API. A constant coefficient assumes approximately constant viscosity/density and formation properties over its calibrated pressure/temperature range; phase screening alone does not establish that range. InflowPerformance remains the separate stock-tank-liquid production IPR owner; its nonnegative production drawdown cannot be reused as injection acceptance by reversing or silently rescaling its coefficients. The field-development InjectionWellModel also remains a separate rate-specified screen: it derives formation coefficients and required BHP, applies fracture clipping, and estimates tubing/pump requirements without composing live process streams. Reusing its coefficients here requires an explicit units/reference-condition audit; it is not a substitute for this pressure-to-mass boundary or for the canonical network hydraulics.

All zones in a liquid mass-injection well must use this new builder. Adding a legacy layer/zone to it, or a liquid zone to a populated legacy well, fails before adding a zone. isLiquidMassInjection() exposes the selection. Existing addInjectionZone, production models, serialized definitions and legacy rate-getter values are unchanged. There is no automatic conversion of old squared-pressure coefficients. Recreate the well definition with calibrated mass coefficients and rebind it to the existing field node; rebuilding the field topology or changing edge hydraulics is unnecessary. New liquid definitions must not be loaded into older NeqSim versions that do not understand this mass-basis flag. Only forward reading of existing legacy definitions is supported here; Java serialization is not a backward interchange format. Keep original model inputs and the producing software version with saved models.

The inlet stream supplies injected composition and temperature, not the reservoir-zone stream. The inlet is cloned and checked at BHP for one liquid phase before assigning the summed mass rate to the existing outlet. Gas, multiphase and solid states are rejected. Zone streams remain boundary metadata and are not consumed or mixed into the injected fluid. Coefficients/pressures/limits are revalidated on every solve, including direct mutations of the existing public layer fields. Shut-in zones have zero rate; crossflow is not modeled. Fracture limits remain reported acceptance limits, not rate caps, and the existing network coupler rejects an exceeded limit.

For this opt-in model, getLayerFlowRates and getOutOfZoneRate accept only kg/sec, kg/s, kg/hr and kg/day. ReservoirLayer.calculatedRate is kg/s; allocation and target-zone efficiency are mass fractions. Read results after a successful solve; a rejected solve invalidates calculated zone rates. Allocation/efficiency/out-of-zone queries reject unavailable rate evidence instead of reporting a plausible zero. Do not apply legacy volume labels to these values. runTransient is allowed only in steady/quasi-steady mode; this does not implement transient reservoir, thermal, fracture or multiphase injectivity physics.

The complete example below is compiled and executed by ControllersAndWellsDocumentationTest. Its two coefficients give 0.5 + 0.2 = 0.7 kg/s at 150 bara. They are synthetic inputs, not field calibration.

import neqsim.process.equipment.reservoir.WellFlow;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

public class LiquidMassInjectionExample {
  public static void main(String[] args) {
    SystemSrkEos water = new SystemSrkEos(298.15, 180.0);
    water.addComponent("water", 1.0);
    water.setMixingRule("classic");
    Stream feed = new Stream("water supply", water);
    feed.setFlowRate(1.0, "kg/sec");
    feed.run();
    WellFlow injector = new WellFlow("liquid injector");
    injector.setInletStream(feed);
    injector.addLiquidInjectionZone("target", feed.clone(), 100.0, 0.01, 170.0);
    injector.addLiquidInjectionZone("secondary", feed.clone(), 110.0, 0.005, 160.0);
    injector.setTargetZone("target");
    injector.setOutletPressure(150.0, "bara");
    injector.run();
    double[] rates = injector.getLayerFlowRates("kg/sec");
    assert Math.abs(rates[0] - 0.5) < 1.0e-12;
    assert Math.abs(rates[1] - 0.2) < 1.0e-12;
    assert Math.abs(injector.getOutletStream().getFlowRate("kg/sec") - 0.7) < 1.0e-10;
    assert !injector.getZoneFractureRisk()[0];
    assert !injector.getZoneFractureRisk()[1];
  }
}

WellFlowLiquidInjectionTest independently checks the constant-property Darcy mass coefficient $\rho kA/(\mu L)\,10^5$: with density 1000 kg/m3, permeability 1e-12 m2, area 10 m2, viscosity 0.001 Pa s and length 100 m it is 0.01 kg/(s bar). It also checks nearby BHPs, unit conversion, partial/full shut-in and recovery, fracture reporting, invalid inputs, phase rejection, legacy compatibility and copies. This is an analytical software check of the stated linear approximation, not laboratory/field injectivity, fracture-pressure or operating-envelope qualification.

For pressure-dependent ordinary-water injection, use RadialDarcyWaterInjectivity.iapwsPureWater(...) and attach the immutable law with WellFlow.addRadialDarcyWaterInjectionZone(...). The same radial-Darcy implementation is used by the field-development injection screen and the live well boundary. It evaluates viscosity and reservoir/standard specific-volume ratio at each zone’s current reservoir pressure, reports the applied PVT evidence, and returns Sm3/day. WellFlow uses the law’s IF97 density at 288.15 K and 1.01325 bara to convert that result explicitly to kg/s for the canonical network. Changing ReservoirLayer pressure and solving again refreshes the evidence without rebuilding the well, topology or hydraulic edges.

The live builder deliberately accepts only IAPWS_IF97_PURE_WATER. The inlet must contain ordinary water only and remain one liquid phase at BHP. Configured-constant PVT remains available to screening calculations, but lacks an unambiguous standard density and is therefore rejected at this mass boundary. Salinity, dissolved gas, relative permeability, formation damage, fracture growth and reservoir transients remain outside the law. The analytical tests and shared-header acceptance establish software units, state refresh and mass conservation; they are not independent field or laboratory qualification.

Single-layer gas inflow

For the single-layer gas production-index model:

\[q = PI\left(P_r^2-P_{wf}^2\right)\]

WellFlow.setWellProductionIndex uses MSm3/day/bar², with M meaning one million. Both pressures are absolute, in bara. WellSystem.setProductionIndex accepts a unit string and commonly uses Sm3/day/bar²; the numerical PI values differ by a factor of one million.

import neqsim.process.equipment.reservoir.WellFlow;
import neqsim.process.equipment.stream.Stream;
import neqsim.thermo.system.SystemSrkEos;

public class GasWellInflowExample {
  public static void main(String[] args) {
    SystemSrkEos fluid = new SystemSrkEos(358.15, 250.0); // K, bara
    fluid.addComponent("methane", 0.95);
    fluid.addComponent("ethane", 0.05);
    fluid.setMixingRule("classic");
    Stream reservoir = new Stream("reservoir boundary", fluid);
    reservoir.setFlowRate(0.50, "MSm3/day");
    reservoir.run();

    WellFlow inflow = new WellFlow("gas-well IPR");
    inflow.setInletStream(reservoir);
    inflow.setWellProductionIndex(1.0e-4); // MSm3/day/bar2
    inflow.run(); // Default: calculate flowing bottom-hole pressure from inlet rate.

    double expectedBhp = Math.sqrt(250.0 * 250.0 - 0.50 / 1.0e-4);
    assert Math.abs(inflow.getOutletStream().getPressure("bara") - expectedBhp) < 1.0e-8;
    assert Math.abs(inflow.getOutletStream().getFlowRate("Sm3/day") - 500000.0) < 1.0e-4;

    // Alternative boundary condition: solve the produced rate at 230 bara BHP.
    inflow.setOutletPressure(230.0, "bara");
    inflow.solveFlowFromOutletPressure(true);
    inflow.run();
    double expectedRate = 1.0e-4 * (250.0 * 250.0 - 230.0 * 230.0);
    assert Math.abs(inflow.getOutletStream().getFlowRate("MSm3/day") - expectedRate) < 1.0e-10;
    assert Math.abs(inflow.getOutletStream().getPressure("bara") - 230.0) < 1.0e-8;
  }
}

Expected results are approximately 239.79 bara for the first calculation and 0.96 MSm3/day for the second. In flow-solving mode, consume the calculated outlet rate; the inlet stream is a reservoir boundary and its original rate is not automatically updated.

Other Inflow Relationships

WellFlow also offers Vogel, Fetkovich, non-Darcy backpressure, table-driven, and liquid-inflow relationships. Select them through the corresponding parameter methods rather than a string setIPRModel call. The legacy Vogel, Fetkovich, backpressure, and table branches use MSm3/day internally. Do not pass a stock-tank oil rate in Sm3/day to those branches without reconciling the model’s rate definition.

For oil wells, InflowPerformance with WellFlow.setInflowPerformance and setLiquidRate provides a dedicated stock-tank liquid-rate basis in Sm3/day. A Vogel relation has the form

\[q = q_{max}\left[1-0.2\frac{P_{wf}}{P_r}-0.8\left(\frac{P_{wf}}{P_r}\right)^2\right].\]

Its rate basis and applicability must match the well test used to fit the curve. See the Well Simulation Guide and Integrated Production Modelling for the available inflow models and calibration workflows.

Wellbore Hydraulics

PipeBeggsAndBrills uses length, elevation change, diameter, and wall roughness in metres. Positive elevation represents upward flow. Its outlet is the wellhead when its inlet is the bottom-hole stream. The next example explicitly includes 3000 m of vertical tubing.

TubingPerformance supplies additional correlations and temperature profiles. In an integrated WellSystem, selecting setPressureDropCorrelation alone does not activate its full VLP solver; select the matching setVLPSolverMode as described in the well guide. Check the operating-point convergence flag and the physical pressure residual before accepting an integrated solution.

Choke Modeling

Use ThrottlingValve for a specified downstream pressure. This performs an isenthalpic pressure reduction unless configured otherwise. Specifying only outlet pressure does not determine a unique choke bean size or establish whether the required flow is within a calibrated choke’s capacity. See Valves for sizing, valve travel, and choking behavior.

Nodal Analysis

An operating point must satisfy both the IPR and tubing hydraulics. This example varies the production rate until the calculated wellhead pressure equals a 170 bara target. It brackets the root, checks the final pressure residual, and only then runs the surface choke to 30 bara. The bracket and target are specific to this example; recheck them for another fluid or well.

import neqsim.process.equipment.pipeline.PipeBeggsAndBrills;
import neqsim.process.equipment.reservoir.WellFlow;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.valve.ThrottlingValve;
import neqsim.thermo.system.SystemSrkEos;

public class GasWellNodalExample {
  private static double wellheadPressure(double rate, Stream reservoir,
      WellFlow inflow, PipeBeggsAndBrills tubing) {
    reservoir.setFlowRate(rate, "MSm3/day");
    reservoir.run();
    inflow.run();
    tubing.run();
    return tubing.getOutletStream().getPressure("bara");
  }

  public static void main(String[] args) {
    SystemSrkEos fluid = new SystemSrkEos(358.15, 250.0);
    fluid.addComponent("methane", 0.95);
    fluid.addComponent("ethane", 0.05);
    fluid.setMixingRule("classic");
    Stream reservoir = new Stream("reservoir boundary", fluid);
    reservoir.setFlowRate(0.10, "MSm3/day");
    WellFlow inflow = new WellFlow("well IPR");
    inflow.setInletStream(reservoir);
    inflow.setWellProductionIndex(1.0e-5); // MSm3/day/bar2
    PipeBeggsAndBrills tubing = new PipeBeggsAndBrills("tubing", inflow.getOutletStream());
    tubing.setLength(3000.0);
    tubing.setElevation(3000.0);
    tubing.setDiameter(0.10);
    tubing.setPipeWallRoughness(5.0e-5);
    tubing.setNumberOfIncrements(20);

    double target = 170.0; // bara at the wellhead
    double low = 0.01; // MSm3/day
    double high = 0.40;
    double lowPressure = wellheadPressure(low, reservoir, inflow, tubing);
    double highPressure = wellheadPressure(high, reservoir, inflow, tubing);
    if (!(lowPressure > target && highPressure < target)) {
      throw new IllegalStateException("Rate bracket does not enclose the wellhead-pressure target");
    }
    for (int i = 0; i < 30; i++) {
      double rate = 0.5 * (low + high);
      double pressure = wellheadPressure(rate, reservoir, inflow, tubing);
      if (pressure > target) {
        low = rate;
      } else {
        high = rate;
      }
    }
    double operatingRate = 0.5 * (low + high);
    double wellhead = wellheadPressure(operatingRate, reservoir, inflow, tubing);
    double bhp = inflow.getOutletStream().getPressure("bara");
    assert operatingRate > 0.01 && operatingRate < 0.40;
    assert Math.abs(wellhead - target) < 1.0e-4;
    assert bhp > wellhead && bhp < 250.0;
    assert Math.abs(operatingRate - 1.0e-5 * (250.0 * 250.0 - bhp * bhp)) < 1.0e-9;

    ThrottlingValve choke = new ThrottlingValve("wellhead choke", tubing.getOutletStream());
    choke.setOutletPressure(30.0, "bara");
    choke.run();
    double inletMass = tubing.getOutletStream().getFlowRate("kg/hr");
    double outletMass = choke.getOutletStream().getFlowRate("kg/hr");
    assert Math.abs(choke.getOutletStream().getPressure("bara") - 30.0) < 1.0e-8;
    assert Math.abs(outletMass - inletMass) < inletMass * 1.0e-8;
    double inletEnthalpy = tubing.getOutletStream().getFluid().getEnthalpy();
    double outletEnthalpy = choke.getOutletStream().getFluid().getEnthalpy();
    assert Math.abs(outletEnthalpy - inletEnthalpy) < Math.max(Math.abs(inletEnthalpy), 1.0) * 1.0e-5;
  }
}

Gas Lift

Include an injection-gas stream and its mass and energy balance when modeling gas lift through a flowsheet. GasLiftPerformanceCurve, GasLiftNetworkOptimizer, and ChokeableGasLiftWell provide performance-curve and allocation workflows in neqsim.process.fielddevelopment.integrated. See Integrated Production Modelling and Production Well Networks. A lift-gas rate by itself does not define the injection depth, gas composition, or a complete wellbore model.

ESP

Use neqsim.process.equipment.pump.ESPPump between explicit inlet and outlet wellbore sections. Pump inlet pressure, free-gas fraction, pump performance, efficiency, and downstream hydraulics determine the operating point. See the ESP Pump Tutorial for the equipment API and multiphase considerations.

Usage Examples

Example: Production System

When wellhead pressure and mass rate are given, represent each well as a boundary stream. This example combines two such streams through individual pressure-reduction valves and a manifold, then separates gas, oil, and water at 30 bara. It does not calculate reservoir deliverability or depletion.

import neqsim.process.equipment.mixer.Mixer;
import neqsim.process.equipment.separator.ThreePhaseSeparator;
import neqsim.process.equipment.stream.Stream;
import neqsim.process.equipment.valve.ThrottlingValve;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.thermo.system.SystemPrEos;

public class ProductionManifoldExample {
  public static void main(String[] args) {
    SystemPrEos fluid = new SystemPrEos(323.15, 50.0);
    fluid.addComponent("methane", 0.40);
    fluid.addComponent("n-heptane", 0.40);
    fluid.addComponent("water", 0.20);
    fluid.setMixingRule("classic");
    fluid.setMultiPhaseCheck(true);
    Stream well1 = new Stream("well 1 boundary", fluid);
    well1.setFlowRate(10000.0, "kg/hr");
    Stream well2 = new Stream("well 2 boundary", fluid.clone());
    well2.setPressure(45.0, "bara");
    well2.setFlowRate(8000.0, "kg/hr");

    ThrottlingValve choke1 = new ThrottlingValve("choke 1", well1);
    choke1.setOutletPressure(30.0, "bara");
    ThrottlingValve choke2 = new ThrottlingValve("choke 2", well2);
    choke2.setOutletPressure(30.0, "bara");
    Mixer manifold = new Mixer("production manifold");
    manifold.addStream(choke1.getOutletStream());
    manifold.addStream(choke2.getOutletStream());
    ThreePhaseSeparator separator = new ThreePhaseSeparator("production separator", manifold.getOutletStream());

    ProcessSystem process = new ProcessSystem("two-well production manifold");
    process.add(well1);
    process.add(well2);
    process.add(choke1);
    process.add(choke2);
    process.add(manifold);
    process.add(separator);
    process.run();

    double gasMass = separator.getGasOutStream().getFlowRate("kg/hr");
    double oilMass = separator.getOilOutStream().getFlowRate("kg/hr");
    double waterMass = separator.getWaterOutStream().getFlowRate("kg/hr");
    assert gasMass > 0.0 && oilMass > 0.0 && waterMass > 0.0;
    assert Math.abs(gasMass + oilMass + waterMass - 18000.0) < 18000.0 * 1.0e-7;
    assert Math.abs(separator.getGasOutStream().getPressure("bara") - 30.0) < 1.0e-8;

    double oilVolume = separator.getOilOutStream().getFlowRate("m3/hr");
    double waterVolume = separator.getWaterOutStream().getFlowRate("m3/hr");
    double waterCut = waterVolume / (oilVolume + waterVolume);
    assert waterCut > 0.0 && waterCut < 1.0;
  }
}

GOR and Water Cut

The water-cut calculation above uses actual liquid volumes at separator conditions. Report that basis with the result. A stock-tank water cut requires conditioning the liquids to the specified stock-tank conditions first.

Stock-tank GOR is the gas standard volume divided by the stabilized oil liquid volume on its specified reference basis. Calling getFlowRate("Sm3/day") on an oil stream does not perform stock-tank separation or account for the gas liberated during stabilization. Model the relevant separation stages and use the PVT Laboratory Tests guide for separator-test and differential-liberation workflows.