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
- Well Types
- IPR Curves
- Wellbore Hydraulics
- Choke Modeling
- Nodal Analysis
- Gas Lift
- ESP
- Usage Examples
- GOR and Water Cut
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):
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
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.