| name | neqsim-production-optimization |
| description | Production optimization, bottleneck analysis, decline modeling, decline-curve history matching (Arps + Duong), reservoir material balance surveillance (OGIP/OOIP, drive indices, aquifer influx), and IOR/EOR screening with NeqSim. USE WHEN: optimizing production rates, identifying facility bottlenecks, forecasting production profiles, fitting decline curves to production history, estimating reserves from pressure/production data, analyzing gas lift allocation, evaluating IOR/EOR options, or running multi-scenario production comparisons. |
| last_verified | 2026-07-11 |
NeqSim Production Optimization Skill
Reference for production forecasting, optimization, bottleneck analysis, and
improved recovery using NeqSim's field development and process simulation tools.
Production Profile Modeling
Decline Curve Types
| Model | Formula | Use Case |
|---|
| Exponential | $q(t) = q_i e^{-Dt}$ | Gas wells, constant decline |
| Hyperbolic | $q(t) = q_i (1 + bDt)^{-1/b}$ | Oil wells, b = 0.3-0.8 |
| Harmonic | $q(t) = q_i / (1 + Dt)$ | Hyperbolic with b = 1 |
| Plateau + exponential | Build-up → plateau → decline | Constrained by facility |
Production Profile Generation
ProductionProfile profile = new ProductionProfile();
profile.setDeclineModel(ProductionProfile.DeclineModel.EXPONENTIAL);
profile.setPeakRate(25000.0);
profile.setDeclineRate(0.15);
profile.setPlateauDuration(3);
profile.setProjectLife(25);
double[] rates = profile.generateProfile();
profile.setDeclineModel(ProductionProfile.DeclineModel.HYPERBOLIC);
profile.setHyperbolicB(0.5);
ProductionProfileGenerator gen = new ProductionProfileGenerator();
gen.setResourceVolume(100.0);
gen.setRecoveryFactor(0.55);
gen.setPeakRate(25000.0);
gen.setBuildUpYears(2);
gen.setPlateauYears(5);
gen.generate();
Multi-Well Drill Schedule
FieldProductionScheduler scheduler = new FieldProductionScheduler();
scheduler.setNumberOfWells(8);
scheduler.setDrillingInterval(6);
scheduler.setFirstOil(2027);
scheduler.setWellProductivity(4000.0);
scheduler.setDeclineRate(0.12);
scheduler.setFacilityCapacity(28000.0);
double[][] schedule = scheduler.generateSchedule(25);
Decline Curve Fitting & History Matching (DeclineCurveAnalysis)
neqsim.pvtsimulation.util.DeclineCurveAnalysis is a static, unit-agnostic
Arps + Duong decline toolkit. Besides the forward rate(...),
cumulativeProduction(...), eur(...) and forecast(...) methods it now
least-squares fits decline parameters to a measured rate-time history — the
inverse (surveillance) direction used to estimate remaining reserves and EUR
directly from production data. Times are in days; rates keep whatever consistent
unit you supply.
import java.util.Map;
import neqsim.pvtsimulation.util.DeclineCurveAnalysis;
Map<String, Double> fit = DeclineCurveAnalysis.fitArps(t, q);
double qi = fit.get("qi");
double di = fit.get("di");
double b = fit.get("b");
double r2 = fit.get("rSquared");
double eur = DeclineCurveAnalysis.eurFromFit(fit, 50.0);
Map<String, Double> fitW = DeclineCurveAnalysis.fitArps(t, q, 3, t.length - 1);
The fitArps overloads grid-search the exponent b over [0, 1] (coarse then
fine), analytically solving qi and di for each candidate by linearising in
rate space (ln q vs t for b≈0; q^-b vs t otherwise). Use the windowed
overload to fit only the established boundary-dominated decline.
Duong (2011) — tight / unconventional wells
For fracture-dominated tight-gas and shale wells the Arps model over-estimates
reserves. The Duong (2011) model is included:
double q = DeclineCurveAnalysis.rateDuong(q1, a, m, t);
double gp = DeclineCurveAnalysis.cumulativeDuong(q1, a, m, t);
Map<String, Double> duong = DeclineCurveAnalysis.fitDuong(t, q);
Model selection: fit both fitArps and fitDuong, compare rSquared, and
prefer Duong when the well is in transient linear (fracture-dominated) flow.
Reservoir Material Balance & Surveillance (inverse tank models)
neqsim.pvtsimulation.reservoirproperties.materialbalance regresses original
hydrocarbon in place, drive mechanism and aquifer support directly from a
measured pressure-vs-cumulative-production history. These "inverse" tank models
complement the forward depletion model (SimpleReservoir) — use them for
reserves surveillance and drive diagnosis. Pressures are in bara, temperatures in
Kelvin; cumulative volumes keep any consistent surface unit and the returned
in-place volume is in the same unit.
Gas — P/Z line, Cole plot, Havlena-Odeh (GasMaterialBalance)
import neqsim.pvtsimulation.reservoirproperties.materialbalance.GasMaterialBalance;
GasMaterialBalance.Result r = GasMaterialBalance.fitVolumetric(pressure, z, gp);
double ogip = r.getOgip();
double piZi = r.getPiOverZi();
double r2 = r.getRSquared();
GasMaterialBalance.Result r2fit = GasMaterialBalance.fitVolumetric(pressure, gp, tempK, gasGravity);
double[][] cole = GasMaterialBalance.colePlot(pressure, z, gp, tempK);
GasMaterialBalance.Result rHO = GasMaterialBalance.fitHavlenaOdeh(pressure, z, gp, we, tempK);
Oil — Havlena-Odeh, gas-cap ratio, drive indices (OilMaterialBalance)
import neqsim.pvtsimulation.reservoirproperties.materialbalance.OilMaterialBalance;
OilMaterialBalance.Result dep = OilMaterialBalance.fitDepletionDrive(f, eo);
OilMaterialBalance.Result gc = OilMaterialBalance.fitGasCapDrive(f, eo, eg);
OilMaterialBalance.Result wd = OilMaterialBalance.fitWaterDrive(f, eo, we, bw);
double[] di = OilMaterialBalance.driveIndices(n, m, eoTerm, egTerm, efwTerm, we, bw, fTerm);
Aquifer influx — Van Everdingen-Hurst / Carter-Tracy (VanEverdingenHurstAquifer)
import neqsim.pvtsimulation.reservoirproperties.materialbalance.VanEverdingenHurstAquifer;
double u = VanEverdingenHurstAquifer.aquiferConstant(porosity, ct, thickness, radius, angleDeg);
double[] we = VanEverdingenHurstAquifer.cumulativeInfluxCarterTracy(tD, deltaP, u, reD);
String aqutab = VanEverdingenHurstAquifer.exportAqutab(tD, reD);
The We array feeds the aquifer term of GasMaterialBalance.fitHavlenaOdeh /
OilMaterialBalance.fitWaterDrive. Use reD = Double.POSITIVE_INFINITY for an
infinite-acting aquifer or a finite reD for a bounded one.
Workflow: (1) diagnose drive with the Cole plot / drive indices, (2) if
water drive, build We with Carter-Tracy, (3) regress OGIP/OOIP with the
matching fit* method, (4) cross-check reserves against the DeclineCurveAnalysis
EUR.
Facility Bottleneck Analysis
Identifying Constraints
BottleneckAnalyzer analyzer = new BottleneckAnalyzer(processSystem);
analyzer.setReservoirDecline(reservoirModel);
analyzer.setRateRange(10000, 60000, 5000);
Map<String, Double> bottlenecks = analyzer.findBottlenecks();
Facility Capacity Over Time
FacilityCapacity capacity = new FacilityCapacity();
capacity.setEquipment("HP Separator", 35000.0);
capacity.setEquipment("Gas Compressor", 28000.0);
capacity.setEquipment("Export Pump", 40000.0);
capacity.setEquipment("Water Treatment", 45000.0);
double[] constrainedProfile = capacity.constrain(unconstrained);
String bottleneck = capacity.getActiveBottleneck(year);
Production Allocation
Multi-Field to Single Facility
ProductionAllocator allocator = new ProductionAllocator();
allocator.addField("Field A", fieldAProfile, 0.60);
allocator.addField("Field B", fieldBProfile, 0.30);
allocator.addField("Field C", fieldCProfile, 0.10);
allocator.setFacilityCapacity(50000.0);
Map<String, double[]> allocation = allocator.allocate();
Network Optimization
Multi-Well Gathering System
NetworkSolver network = new NetworkSolver("Production Network");
network.addWell(well1, 3.0);
network.addWell(well2, 5.5);
network.addWell(well3, 8.0);
network.addWell(well4, 4.2);
network.setSolutionMode(SolutionMode.FIXED_MANIFOLD_PRESSURE);
network.setManifoldPressure(60.0);
NetworkResult result = network.solve();
for (String wellName : result.getWellNames()) {
double rate = result.getWellRate(wellName);
double whp = result.getWellheadPressure(wellName);
}
network.setSolutionMode(SolutionMode.FIXED_TOTAL_RATE);
network.setTargetTotalRate(50000.0);
result = network.solve();
double requiredManifoldP = result.getManifoldPressure();
LoopedPipeNetwork (Advanced)
Full NR-GGA production network solver with IPR, chokes, tubing VLP, Beggs-Brill
multiphase, compressors, artificial lift, water/sand/corrosion/emissions:
LoopedPipeNetwork net = new LoopedPipeNetwork("Gathering");
net.setFluidTemplate(gas);
net.setSolverType(LoopedPipeNetwork.SolverType.NEWTON_RAPHSON);
net.setMaxIterations(500);
net.setTolerance(500.0);
net.addSourceNode("R1", 230.0, 0.0);
net.addJunctionNode("MF1");
net.addWellIPR("R1", "MF1", "W1", 5e-13, true);
net.setGasLift("W1", 500.0);
net.setESP("W2", 80.0, 0.55);
net.setWaterCut("W1", 0.15);
net.setSandRate("W1", 3.0);
net.setCorrosiveGas("trunk", 0.035, 0.002);
net.setCorrosionModel("trunk", "NORSOK");
net.setCO2EmissionFactor(2.75);
net.setMethaneSlipFactor(0.02);
net.run();
Map<String, double[]> sand = net.calculateSandTransport();
Map<String, double[]> corr = net.calculateCorrosion();
Map<String, double[]> em = net.calculateEmissions();
double annualCO2 = net.getAnnualCO2EmissionsTonnes();
See production_well_networks.md
for full API documentation of all features.
Gas Lift Optimization
Single Well
GasLiftCalculator glCalc = new GasLiftCalculator();
glCalc.setWellDepth(3000.0);
glCalc.setReservoirPressure(250.0);
glCalc.setProductionRate(5000.0);
glCalc.setGLR(500.0);
glCalc.setInjectionPressure(150.0);
double optimalGLR = glCalc.calculateOptimalGLR();
double gasRate = glCalc.calculateInjectionRate();
Multi-Well Allocation
GasLiftOptimizer optimizer = new GasLiftOptimizer();
optimizer.addWell("P1", well1, glCalc1);
optimizer.addWell("P2", well2, glCalc2);
optimizer.addWell("P3", well3, glCalc3);
optimizer.setTotalGasAvailable(500000.0);
Map<String, Double> allocation = optimizer.optimize();
double totalOilGain = optimizer.getTotalOilGain();
Choke + Lift-Gas Co-Optimization with Facility Constraints (strupe/øke lists)
When the decision is which wells to choke back or open up under several shared
facility ceilings at once (gas handling, produced-water/PWRI, and the lift-gas budget) —
the classic offshore "strupe/øke liste" — use the choke-and-gas-lift allocation stack in
neqsim.process.fielddevelopment.integrated. It co-optimizes choke opening and
lift-gas per well, honours discrete on/off locks (sand, lost comms, life extension) and
per-well gas ceilings, and emits an operator-ranked action list.
GasLiftPerformanceCurve curve =
GasLiftPerformanceCurve.fromWellSystem(wellSystem, 60000.0, peakOil, 150000.0);
ChokeableGasLiftWell w = new ChokeableGasLiftWell("S-24", curve)
.setMaxChokeFraction(0.60)
.setCurrentChokeFraction(0.0)
.setGor(750.0).setWaterCut(0.35)
.setGasHandlingLimit(750_000.0);
ChokeAndGasLiftAllocationOptimizer opt = new ChokeAndGasLiftAllocationOptimizer()
.addWell(w)
.setLiftGasBudget(totalLiftGasSm3d)
.setGasHandlingLimit(maxGasSm3d)
.setWaterHandlingLimit(maxWaterSm3d)
.setObjective(ChokeAndGasLiftAllocationOptimizer.Objective.OIL);
ChokeAndGasLiftAllocationOptimizer.AllocationResult r = opt.optimize();
String json = r.toJson();
StrupeOkeReport report = StrupeOkeReport.build(Arrays.asList(w ), r);
System.out.println(report.toTable());
Screening-grade: choke is a linear deliverability scale, the facility relief is a greedy
"choke back the least valuable barrels first" search. Use LoopedPipeNetwork for a
rigorous coupled network solve. Distinct from GasLiftOptimizer (lift gas + compression
only) and ReservoirToMarketOptimizer (choke + one throughput cap only).
Scenario Analysis
Multi-Scenario Comparison
ScenarioAnalyzer scenarios = new ScenarioAnalyzer();
scenarios.addScenario("Base", baseEngine);
CashFlowEngine highPrice = baseEngine.clone();
highPrice.setOilPrice(90.0);
scenarios.addScenario("High Price", highPrice);
CashFlowEngine lowRecovery = baseEngine.clone();
lowRecovery.setRecoveryFactor(0.45);
scenarios.addScenario("Low Recovery", lowRecovery);
CashFlowEngine accelerated = baseEngine.clone();
accelerated.setDrillingInterval(3);
scenarios.addScenario("Accelerated", accelerated);
Map<String, CashFlowResult> results = scenarios.runAll();
scenarios.generateComparisonTable();
IOR/EOR Screening Considerations
Water Injection (Voidage Replacement)
InjectionStrategy waterInj = InjectionStrategy.waterInjection(1.0);
InjectionResult result = waterInj.calculateInjection(
reservoir, oilRate, gasRate, waterRate
);
double requiredRate = result.waterInjectionRate;
double achievedVRR = result.achievedVRR;
Gas Injection
InjectionStrategy gasInj = InjectionStrategy.gasInjection(0.8);
Key IOR/EOR Methods (Screening Criteria)
| Method | Viscosity Limit | Depth Limit | API Gravity | Recovery Boost |
|---|
| Water flood | < 150 cP | Any | > 15° | 5-30% OOIP |
| WAG | < 10 cP | > 1500 m | 25-50° | 5-15% OOIP |
| Polymer | 10-150 cP | < 3000 m | > 15° | 5-15% OOIP |
| Steam (SAGD) | > 200 cP | < 1500 m | 7-20° | 20-50% OOIP |
| CO2 flooding | < 12 cP | > 600 m | > 25° | 8-20% OOIP |
| Surfactant | < 35 cP | < 3000 m | > 20° | 5-15% OOIP |
Emissions Tracking
EmissionsTracker emissions = new EmissionsTracker();
emissions.setProcessSystem(processSystem);
emissions.setFuelType("natural_gas");
emissions.setProductionRate(25000.0);
double co2Tonnes = emissions.calculateAnnualCO2();
double intensity = emissions.getCO2Intensity();
Detailed Emissions
DetailedEmissionsCalculator detailedCalc = new DetailedEmissionsCalculator();
detailedCalc.setGasTurbinePower(25.0);
detailedCalc.setFlareRate(5000.0);
detailedCalc.setFugitiveRate(0.001);
Map<String, Double> breakdown = detailedCalc.calculate();
Energy Efficiency
EnergyEfficiencyCalculator efficiency = new EnergyEfficiencyCalculator();
efficiency.setProcessSystem(processSystem);
efficiency.setExportRate(25000.0);
double specificPower = efficiency.getSpecificPower();
double energyEfficiency = efficiency.getEfficiency();
Map<String, Double> consumers = efficiency.getPowerBreakdown();
Late Life & Decommissioning
Late Life Options
| Strategy | NeqSim Support | Key Considerations |
|---|
| Infill drilling | New WellSystem + network rebalance | Marginal well economics |
| Water shut-off | Adjust WellSystem water cut | Intervention cost vs benefit |
| Gas lift optimization | GasLiftOptimizer | Declining reservoir pressure |
| Choke + lift under gas/water/lift limits (strupe/øke list) | ChokeAndGasLiftAllocationOptimizer + StrupeOkeReport | Multi-constraint fleet, on/off locks |
| Tie-back satellite | TiebackAnalyzer | Host capacity utilization |
| EOR (CO2, polymer) | InjectionStrategy + EOS | Fluid compatibility |
| Cessation of production | DecommissioningEstimator | Regulatory requirements |
Decommissioning
DecommissioningEstimator decom = new DecommissioningEstimator();
decom.setNumberOfWells(6);
decom.setWellAbandonment(true);
decom.setSubseaRemoval(true);
decom.setPlatformRemoval(false);
decom.setPipelineDecommissioning(true);
decom.setWaterDepth(350.0);
decom.setRegion("Norway");
double abex = decom.estimate();
Map<String, Double> breakdown = decom.getBreakdown();
Common Optimization Pitfalls
| Pitfall | Impact | Prevention |
|---|
| Optimizing wells independently | Sub-optimal network, back-pressure effects | Always use NetworkSolver for coupled optimization |
| Ignoring facility constraints | Unrealistic production profile | Apply FacilityCapacity constraints to profile |
| Static gas lift allocation | Missed oil as reservoir depletes | Re-optimize gas lift periodically as BHP declines |
| Ignoring water cut increase | Overstated revenue, understated OPEX | Model watercut trajectory, include water treatment costs |
| Over-producing from best wells | Premature water/gas coning | Balanced withdrawal per reservoir zone |
| Ignoring backpressure coupling | Wrong wellhead pressures | Network solver captures well-to-well interactions |