Skip to main content

neqsim-production-optimization

Production optimization, bottleneck analysis, decline curves and history matching (Arps, Duong), material-balance surveillance (OGIP/OOIP, drive indices, aquifer) and IOR/EOR screening with NeqSim. USE WHEN: optimizing rates, finding facility bottlenecks, forecasting profiles, fitting decline curves, estimating reserves, allocating gas lift, comparing production scenarios, or generating Eclipse/OPM VFPPROD lift-curve tables from a NeqSim pipe model.

Source facts

Repository
equinor/neqsim
Last source activity
October 23, 2026 at 01:40
Detected SKILL.md language
English
Stars
168
Forks
78

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
neqsim-production-optimization
description
Production optimization, bottleneck analysis, decline curves and history matching (Arps, Duong), material-balance surveillance (OGIP/OOIP, drive indices, aquifer) and IOR/EOR screening with NeqSim. USE WHEN: optimizing rates, finding facility bottlenecks, forecasting profiles, fitting decline curves, estimating reserves, allocating gas lift, comparing production scenarios, or generating Eclipse/OPM VFPPROD lift-curve tables from a NeqSim pipe model.
last_verified
2026-09-21
# 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 ```java ProductionProfile profile = new ProductionProfile(); // Method 1: Arps decline profile.setDeclineModel(ProductionProfile.DeclineModel.EXPONENTIAL); profile.setPeakRate(25000.0); // boe/d profile.setDeclineRate(0.15); // 15% per year (Di) profile.setPlateauDuration(3); // years at peak profile.setProjectLife(25); // total years double[] rates = profile.generateProfile(); // Method 2: Hyperbolic with b-factor profile.setDeclineModel(ProductionProfile.DeclineModel.HYPERBOLIC); profile.setHyperbolicB(0.5); // Method 3: Resource-constrained ProductionProfileGenerator gen = new ProductionProfileGenerator(); gen.setResourceVolume(100.0); // MMboe recoverable gen.setRecoveryFactor(0.55); gen.setPeakRate(25000.0); gen.setBuildUpYears(2); gen.setPlateauYears(5); gen.generate(); ``` ### Multi-Well Drill Schedule ```java FieldProductionScheduler scheduler = new FieldProductionScheduler(); scheduler.setNumberOfWells(8); scheduler.setDrillingInterval(6); // months between wells scheduler.setFirstOil(2027); scheduler.setWellProductivity(4000.0); // initial boe/d/well scheduler.setDeclineRate(0.12); // 12%/year per well scheduler.setFacilityCapacity(28000.0);// boe/d plateau constraint double[][] schedule = scheduler.generateSchedule(25); // schedule[year][0] = oil rate, [1] = gas rate, [2] = water rate ``` --- ## 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. ```java import java.util.Map; import neqsim.pvtsimulation.util.DeclineCurveAnalysis; // t[] in days, q[] in bbl/d (or Sm3/d, MMscf/d — unit-agnostic) Map<String, Double> fit = DeclineCurveAnalysis.fitArps(t, q); double qi = fit.get("qi"); // initial rate double di = fit.get("di"); // nominal decline (1/day) double b = fit.get("b"); // Arps exponent (grid-searched 0..1) double r2 = fit.get("rSquared"); // goodness of fit // EUR to an economic-limit rate straight from the fit double eur = DeclineCurveAnalysis.eurFromFit(fit, 50.0); // rate-unit * days // Windowed fit — exclude early transient / cleanup points (indices 0..2 here) 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: ```java // Forward double q = DeclineCurveAnalysis.rateDuong(q1, a, m, t); // rate at time t double gp = DeclineCurveAnalysis.cumulativeDuong(q1, a, m, t); // cumulative // Fit q1, a, m to a rate history (log-log q/Gp vs t straight line) Map<String, Double> duong = DeclineCurveAnalysis.fitDuong(t, q); // keys: "q1", "a", "m", "rSquared" ``` **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`) ```java import neqsim.pvtsimulation.reservoirproperties.materialbalance.GasMaterialBalance; // P/Z straight line → OGIP (supply Z, or let it compute Z from Sutton + Hall-Yarborough) GasMaterialBalance.Result r = GasMaterialBalance.fitVolumetric(pressure, z, gp); double ogip = r.getOgip(); // x-intercept where p/Z = 0 double piZi = r.getPiOverZi(); // initial p/Z intercept (bara) double r2 = r.getRSquared(); // Compute Z internally (Sutton pseudo-criticals + Hall-Yarborough) GasMaterialBalance.Result r2fit = GasMaterialBalance.fitVolumetric(pressure, gp, tempK, gasGravity); // Cole plot — aquifer diagnostic (flat = volumetric depletion, rising = water influx) double[][] cole = GasMaterialBalance.colePlot(pressure, z, gp, tempK); // [0]=Gp, [1]=F/Eg // Havlena-Odeh with a supplied cumulative water influx We GasMaterialBalance.Result rHO = GasMaterialBalance.fitHavlenaOdeh(pressure, z, gp, we, tempK); ``` ### Oil — Havlena-Odeh, gas-cap ratio, drive indices (`OilMaterialBalance`) ```java import neqsim.pvtsimulation.reservoirproperties.materialbalance.OilMaterialBalance; // Build the F / Eo / Eg / Efw terms from black-oil PVT, then regress: OilMaterialBalance.Result dep = OilMaterialBalance.fitDepletionDrive(f, eo); // OOIP (no gas cap / aquifer) OilMaterialBalance.Result gc = OilMaterialBalance.fitGasCapDrive(f, eo, eg); // OOIP + gas-cap ratio m OilMaterialBalance.Result wd = OilMaterialBalance.fitWaterDrive(f, eo, we, bw); // OOIP with known We // Pirson fractional drive indices {DDI, SDI, WDI, EDI} (sum ≈ 1) double[] di = OilMaterialBalance.driveIndices(n, m, eoTerm, egTerm, efwTerm, we, bw, fTerm); ``` ### Aquifer influx — Van Everdingen-Hurst / Carter-Tracy (`VanEverdingenHurstAquifer`) ```java import neqsim.pvtsimulation.reservoirproperties.materialbalance.VanEverdingenHurstAquifer; double u = VanEverdingenHurstAquifer.aquiferConstant(porosity, ct, thickness, radius, angleDeg); double[] we = VanEverdingenHurstAquifer.cumulativeInfluxCarterTracy(tD, deltaP, u, reD); // reservoir m3 String aqutab = VanEverdingenHurstAquifer.exportAqutab(tD, reD); // ECLIPSE AQUTAB include table ``` 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 ```java BottleneckAnalyzer analyzer = new BottleneckAnalyzer(processSystem); analyzer.setReservoirDecline(reservoirModel); analyzer.setRateRange(10000, 60000, 5000); // min, max, step (boe/d) Map<String, Double> bottlenecks = analyzer.findBottlenecks(); // Returns: {"HP Separator": 35000, "Gas Compressor": 28000, // "Export Pump": 40000, "Water Treatment": 45000} // Bottleneck = Gas Compressor at 28,000 boe/d ``` ### Facility Capacity Over Time ```java FacilityCapacity capacity = new FacilityCapacity(); capacity.setEquipment("HP Separator", 35000.0); // boe/d capacity.setEquipment("Gas Compressor", 28000.0); capacity.setEquipment("Export Pump", 40000.0); capacity.setEquipment("Water Treatment", 45000.0); // Apply to production profile double[] constrainedProfile = capacity.constrain(unconstrained); String bottleneck = capacity.getActiveBottleneck(year); ``` --- ## Production Allocation ### Multi-Field to Single Facility ```java ProductionAllocator allocator = new ProductionAllocator(); allocator.addField("Field A", fieldAProfile, 0.60); // priority weight allocator.addField("Field B", fieldBProfile, 0.30); allocator.addField("Field C", fieldCProfile, 0.10); allocator.setFacilityCapacity(50000.0); // boe/d Map<String, double[]> allocation = allocator.allocate(); // Returns constrained profiles per field ``` --- ## Network Optimization ### Multi-Well Gathering System ```java NetworkSolver network = new NetworkSolver("Production Network"); network.addWell(well1, 3.0); // well, flowline length (km) network.addWell(well2, 5.5); network.addWell(well3, 8.0); network.addWell(well4, 4.2); // Mode 1: Fixed manifold pressure → find well rates network.setSolutionMode(SolutionMode.FIXED_MANIFOLD_PRESSURE); network.setManifoldPressure(60.0); // bara NetworkResult result = network.solve(); for (String wellName : result.getWellNames()) { double rate = result.getWellRate(wellName); // Sm3/d double whp = result.getWellheadPressure(wellName); // bara } // Mode 2: Fixed total rate → find required manifold pressure network.setSolutionMode(SolutionMode.FIXED_TOTAL_RATE); network.setTargetTotalRate(50000.0); // Sm3/d 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: ```java LoopedPipeNetwork net = new LoopedPipeNetwork("Gathering"); net.setFluidTemplate(gas); net.setSolverType(LoopedPipeNetwork.SolverType.NEWTON_RAPHSON); net.setMaxIterations(500); net.setTolerance(500.0); // Wells with IPR net.addSourceNode("R1", 230.0, 0.0); net.addJunctionNode("MF1"); net.addWellIPR("R1", "MF1", "W1", 5e-13, true); // Artificial lift net.setGasLift("W1", 500.0); // kg/hr net.setESP("W2", 80.0, 0.55); // kW, efficiency // Water, sand, corrosion tracking net.setWaterCut("W1", 0.15); net.setSandRate("W1", 3.0); // kg/hr net.setCorrosiveGas("trunk", 0.035, 0.002); // CO2, H2S mol frac net.setCorrosionModel("trunk", "NORSOK"); // NORSOK M-506 // GHG emissions net.setCO2EmissionFactor(2.75); net.setMethaneSlipFactor(0.02); net.run(); // Post-run analysis 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](docs/process/equipment/production_well_networks.md) for full API documentation of all features. ### Lift curves / VFPPROD tables for Eclipse and OPM Flow > Authoritative guide: `neqsim-vfp-lift-curves` (`VfpTableBuilder`, tubing/flowline/full-system providers, validation). > The recipe below is the manual flowline-only route with `PipeBeggsAndBrills`; prefer the builder. A reservoir simulator wants `VFPPROD`: a 5-D table `BHP[flow][THP][WFR][GFR][ALQ]` in **standard surface volumes** (Sm3/d, METRIC) with explicit axis definitions. NeqSim gives you the hydraulics (`PipeBeggsAndBrills`) and the validated keyword writer (`EclipseVFPExporter`); the piece in between is a surface-rate recombination loop that you write yourself (verified 2026-09-21, 216 points in ~100 s at 20 pipe increments): ```python # 1. Wellstream at standard conditions -> separator gas/condensate basis. # read(file, True) adds a zero-mole water slot with kij 0.5 and VLLE on. # NEVER call setMixingRule() after read(): it wipes the E300 BIC block. base = EclipseFluidReadWrite.read(e300_path, True) std = base.clone(); std.setTemperature(288.15); std.setPressure(1.01325) ThermodynamicOperations(std).TPflash(); std.initProperties() names = [str(std.getComponent(i).getComponentName()) for i in range(std.getNumberOfComponents())] def basis(kind): # composition + mol per standard m3 ph = std.getPhase(kind) x = [float(ph.getComponent(i).getx()) for i in range(len(names))] return x, float(ph.getDensity("kg/m3")) / float(ph.getMolarMass())
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub