WellSim Manual
Nodal analysis, minimum connected reserves, and production forecasting for oil & gas wells — a zero-dependency web port of the field-proven M. El-Ashry Excel toolset, with the macros replaced by deterministic solvers and every formula pinned against the original workbook cells.
What’s built
| Module | Oil | Gas |
|---|---|---|
| 1 — Well model (nodal analysis) | Natural ✓ · Gas lift ✓ · ESP full stack ✓ (68-pump database, matching, design proof) · Water Well tab ✓ (producer + injector) | Natural ✓ |
| 2 — Reserve estimate | ✓ Havlena–Odeh MB · static-gauge MB · reservoir limit | ✓ p/Z prod data · p/Z from SITHP · reservoir limit · p/Z from memory gauges |
| 3 — Forecast | ✓ Tarner · Walsh generalized-MB (Rv) — saturation-tracking MB + nodal | ✓ p/Z tank + nodal, history overlay |
Project-wide principles: Darcy IPR is the default and dominant J for both fluids (the test J calibrates a matched permeability at your judged skin); two pressure records (Pri immutable anchor, Pr working); explicit physics only — Brill & Beggs Z everywhere, no goal-seek; well-head temperature always calculated (Ramey); gas impurities are inputs wherever gas properties appear.
Run & test
node src/server/server.js # serves http://localhost:3355 node --test # 261 tests node scripts/validation-sweep.mjs # 43 module-vs-workbook sensitivity cases
The server uses only Node built-ins; the UI is plain HTML/JS (Plotly for charts). Documentation lives in the repo: README.md, docs/user-guide.md, docs/equations.md, docs/deploy.md. A standalone portable build (WellSim.exe, no install, cases saved beside the exe) is produced from this same source tree by build.ps1.
UI conventions
- Selection modules: where the workbook had parallel sheets, one radio selection is active and the rest hide (lift type, IPR basis, module, reserve pressure-source).
- Side tables beside each chart hold the plotted numbers with a Copy button (tab-separated, pastes into Excel).
- Your inputs survive a refresh. Everything typed is autosaved in the browser and restored on the next load, and the first solve runs on the restored case rather than the demo. Reset (header) forgets it and reloads the demo well. The store is per-browser and never leaves your machine — Save as / Open remain the way to move a case between machines. WellSim is also an installable PWA: the UI, charts and manual are cached so it opens offline, while calculations are never cached, since a stale reservoir answer is worse than none.
- Charts size themselves to the space they are in. Each is measured after it draws and corrected: the canvas to its container (so it never laps the table beside it), the title to the canvas (shrunk, then wrapped rather than made unreadable), and the legend clear of the x-axis title. They re-fit on font load, page load, resize and rotate, so a reload settles straight into its final layout. Result rows follow the active selection — the ESP Sensitivity view replaces the model-match charts and leaving it restores them; rows belonging to another lift are cleared rather than left showing the previous run.
- There is no current-rate input. The well model SOLVES for the rate — the operating point is the IPR/VLP intersection — so a typed rate would either be ignored or quietly contradict the answer. Rates are entered only where they are genuine data: the calibration test rate, the prod-data table, and the ESP/gas-lift matches.
- Production tables accept clipboard paste (button or Ctrl+V in a cell); column order
Date, FTHP, rate, CGR|GOR, WGR|WC, Pwf; header lines auto-skip; up to 200 rows. - Dates:
17-Nov-14,17-Nov-2014 13:00:00,05/03/2014 06:30, ISO, or a day serial. Impossible dates are rejected with the row number — nothing silently rolls over. dt = days from the first row; time-of-day honoured. - Header bar: the Oil Well | Water Well | Gas Well tabs, then Save as · Open · Export · Print / PDF · Help. A server-case link appears only when the deployment reports a supported case store.
- Save as / Open: the whole case — every input, selection and production table across all three tabs — saves to a JSON file and restores in one click; program-filled grey cells stay program-filled.
- Export: downloads either a complete, restorable WellSim JSON case or spreadsheet-ready CSV containing case metadata, labelled inputs, units, selections and production rows. CSV is for analysis and is not a restorable case.
- Print / PDF: prints the results only — charts, tables, summary cards and banners on a clean white page (inputs, navigation and buttons hide); use the browser dialog’s “Save as PDF” for a shareable report.
- Server cases: the legacy JSON company store is disabled by default while the secure organization/membership store is built. A typed company name is not proof of membership. When explicitly enabled for controlled migration, registration is invite-only. The portable program uses the same panel for its local
cases/folder. - Visitor use stays complete: every calculation, browser autosave, Save as, Open, Export and Print/PDF workflow works without an account.
- Mobile: below 640 px the layout stacks and the action button stays pinned.
Oil — Well model
Lift types: Natural flow · Gas lift · ESP (one active).
Defaults
| Group | Fields (default) |
|---|---|
| Well & flow | FTHP 700 psi · WC 50% · GOR 5000 scf/stb · ID 2.992 in · roughness 0.00006 |
| Trajectory | top perf 2810 mAH · kick-off 1910 m · deviation 7° |
| Fluids & PVT | 46 API · γg 0.842 · Rsi 700 scf/stb · Tres 201°F · μ tubing 6 cp · water SG 1.05 · Pb blank = calc |
| Heat transfer | soil 90°F · U 3 BTU/hr·ft²·°F · OD 3.5 in · Cp 0.51 |
| IPR (Darcy) | Pri 3550 · K 50 mD · H 42.653 ft · Re 1640.5 · Rw 0.5104 · skin 0 |
| Test | 2100 stb/d @ 700 psi · Pwf blank = get Pwf |
| Gas lift | injection 2490.92 mTVD · sweep to 2 MMscf/d in 10 steps |
| ESP | pump 2993.08 mAH (= 2985 mTVD on the demo trajectory) · ΔP 1325.16 psi (manual) · tubing gas blank = formation · the ESP demo case sets the pump at 2985 mAH (back on natural/gas lift) |
Workflow
- Solve well — IPR + VLP, operating point (rate, Pwf, calculated WHT), AOF, well-head PQ curve with WHT on the second axis.
- Calibrate from test — get Pwf marches the test Pwf from FTHP if blank; the Jones/test J is computed Pri-anchored; you judge skin (guidance table by drilling/completion method) and the program back-solves the matched K so J(Darcy) = J(test).
- Sensitivities — VLP parameter sets (blank = base) and future pressures (0.75/0.5/0.25 × the current Pr, refilled as you edit Pr or Pri unless you have typed your own) with the future-J chain. The VLP columns follow the fluid and the ACTIVE LIFT TYPE: oil natural
FTHP · GOR · W.C · tubing ID; oil gas lift+ injection gas; oil ESP+ frequency (Hz); water naturalFTHP · tubing ID; water gas lift+ injection gas; water ESP+ frequency; water injectorinjection THP · injected-water temperature · tubing ID. Typed values survive a lift switch. The chart carries the current-Pr IPR (heavy teal) — the curve every solution is solved against — then the future-pressure family. Pri is not drawn: on a depleted well it is history, and a second high curve only competes with the one the answers belong to (the pressure AXIS still tops out at Pri, so runs stay comparable). Each set’s solved NODE is marked with a diamond, and a set that changes the water cut also gets its own thin IPR, because oil rate = gross × (1−W.C) gives it a different curve in rate terms. Every node is therefore a visible intersection of two drawn curves, and the numbers match the nodal-solution table beneath the chart, which every module carries. - Sensitivity pressure axis — the IPR/VLP sensitivity chart tops its pressure axis at Pri on oil, water and gas wells: the inflow side can never exceed the initial reservoir pressure, and the VLP tail that does (high rates the well could never reach) would otherwise stretch the scale and squash the families. The injector keeps an auto axis — injection deliberately pushes ABOVE Pri, so capping it would clip the very curves it exists to show.
- Pump @ solution point — wherever an ESP node is solved (the model match AND every sensitivity case) the pump’s full state is reported in one identical parameter set: pump · stages · frequency (with the curve’s reference frequency) · wear · head total and per stage · ΔP · rate at the pump before and after separation · composite gradient · free gas at intake · separator efficiency · thrust with its down/BEP/up window · hydraulic power · ΔP convergence. In the sensitivity view the sets sit side by side, one column each, so the trade-off reads straight across.
- Every sensitivity set is solved — a table under the chart gives each VLP set its own NODAL SOLUTION against the current IPR (rate, Pwf, WHP, calculated WHT). On ESP wells it also carries the frequency, solved ΔP, head, intake/discharge and thrust per set, and two more charts appear: the pump curve per sensitivity (each set at its own frequency, node starred, over the shared thrust envelope) and the traverse per sensitivity (each set’s top-down march and IPR back-calc with its ΔP step at pump depth). Oil and water alike.
- ESP frequency sensitivity — with a pump selected (oil or water — both tabs read the same 68-pump database) the coupled ΔP is re-solved per rate at each frequency (affinity-scaled curve, intake state and all); with Manual ΔP the quoted ΔP is scaled by the affinity law (f/f₀)². Beyond the pump’s flow range the head is zero, so the frequency curves legitimately merge.
- Injector sensitivities — the injector has its own family: available BHIP vs rate per set against the injectivity line at future pressures. Injection water is incompressible with a fixed viscosity, so its temperature moves the BOTTOMHOLE TEMPERATURE, not the pressure — the BHT families are drawn on the second axis.
- Gas-lift performance — injection sweep, optimum, incremental dQ/dInj.
- ESP — the full pump stack: 68-pump database in the background (WD/WG/WE/FLEX/ESP-B) with add-new-pump (custom per-stage curve — its thrust-marker rows are coloured: row 4 blue = down-thrust limit, row 6 gold = BEP, row 8 red = up-thrust limit, the fixed rows the physics reads) and Manual ΔP fallback; affinity scaling 30–60 Hz with the down-thrust/BEP/up-thrust envelope; intake free-gas block with the default-ON gas separator (95%, “Separator required” above 10% free gas, tubing GLR cut by the BE74 fraction); the coupled solve closes ΔP→march→intake→head→ΔP and matches the bottom-up traverse (Pwf from the IPR at CONSTANT Pres) to the top-down one at the intake — PI is the IPR match factor. Match stages (first run, new pump, wear 0) solves the installed stage count with a DESIGN PROOF: all stage-dependent march pressures must stay above the design floor (input, default 300 psi) — the minimum binds at the pump intake, and a match that would starve it is capped at the stage count where intake = floor, and the anchor Pwf is written grey into the test-Pwf cell (the workbook macro’s C22 — overwrite it to anchor the match on a measured value); Match wear (actual Pint/Pdis) applies the wear factor only — the implied PI and matched K are reported as QC, never written to your inputs (on the demo’s actual gauges the QC recovers the workbook’s PI 2.7 as 2.698). Two ESP views: Model match with the FINAL charts (IPR vs the coupled ESP-VLP nodal plot, wellhead PQ & WHT, PumpCurve with the results block beside, and the Traverse on its own row with measured markers — also shown in Manual-ΔP mode, where the input ΔP appears as the highlighted step at pump depth); and Sensitivity — future-Pres cases at 0.9 / 0.8 / 0.7 × Pr (shallower than the 0.75/0.5/0.25 used elsewhere, because a pump still has to lift at modest depletion), each FULLY solved: the Darcy future J, then a complete coupled ESP solve. The chart draws the current-Pr IPR (heavy teal), each case’s IPR and the coupled ESP VLP — one curve, since pump, tubing and THP do not change with Pr — so every solved node is a visible crossing marked by a diamond, with the pressure axis topped at Pri as elsewhere. The table lists the ESP state AT that node — rate, Pwf, Pint, Pdis, ΔP, head, Qg@pump, gradient, free gas %, WHT and thrust — which is how you see the pump working harder and the intake falling as the reservoir depletes. ESP cases also appear in the main VLP/IPR sensitivity block (frequency column, coupled solve per set), but that block solves at the CURRENT Pr only.
Water — Well model
Well type: Producer | Injector · producer lift types: Natural flow · Gas lift · ESP.
The oil-tab structure without the Reserve/Forecast modules (lift types, get-Pwf calibration and sensitivities included), running the same modified-Griffith march at its limiting case: API fixed at 10° (SG = 1.000 exactly), w.c. 100 %, GOR = Rsi = 0, Pb = 0. Rates are gross water (bbl/d); the IPR is built in as the pure linear Darcy form (no Vogel curvature) with the Darcy J on water properties (μ = 0.5 cp, Bw = 1); future-pressure IPR sensitivities keep J constant. Defaults: FTHP 200 psi (producer) · 2000 bbl/d · Pri 4800 psi. Head-dominated: it flows naturally only when Pr beats THP + the static water column (≈ 0.446 psi/ft at SG 1.05) — otherwise “no-intersection” is the physics, not an error.
Defaults
| Group | Fields (default) |
|---|---|
| Well & flow | FTHP 200 psi (producer) · injection THP 2000 psi (injector) · ID 2.992 in · roughness 0.00006 |
| Trajectory | top perf 2810 mAH · kick-off 1910 m · deviation 7° |
| Fluids | water SG 1.05 · lift-gas SG 0.842 · Tres 201°F · injection water temp 90°F |
| Heat transfer | soil 90°F · U 3 BTU/hr·ft²·°F · OD 3.5 in · Cp 0.51 |
| IPR | basis Darcy | User PI · Pri 4800 · K 50 mD · H 42.653 ft · Re 1640.5 · Rw 0.5104 · skin 0 · fracture gradient 0.7 psi/ft (injector parting check; blank = off) |
| Test | 2000 bbl/d @ 200 psi · Pwf blank = get Pwf |
| ESP | pump 2993.08 mAH · 145 stages @ 50 Hz · wear 0 · no separator (water is gas-free) · Manual ΔP 1325.16 psi |
Oil — Reserve estimate
Prod data & macro
Per-row Pwf (input-or-march) → Pr by composite-Vogel inversion iterated with J_2 → Havlena–Odeh MB. Headline N = AVERAGE(F/Eo); slope of F vs Eo as cross-check; F–Eo crossplot + N vs Np charts.
Static Pres history
Measured memory-gauge pressures typed directly (Date | Pres) — no IPR/VLP anywhere. Np/Gp from the prod-data cumulative. The strongest data when surveys exist.
Reservoir limit
m = −slope(Pwf, t), Ct = Cg·Sg+Co·So+Cw·Sw+Cf, STOIP = q̄/(Ct·m). Defaults Sg 0.1 / So 0.8 / Sw 0.15. Early lower bound.
Gas — Well model
Defaults
| Group | Fields (default) |
|---|---|
| Well & flow | FTHP 1625 psi · CGR 57.436 · WGR 3.846 stb/MMscf · ID 2.992 in · base roughness 0.0021 in |
| Trajectory | top perf 3013 mAH · kick-off 690 m · deviation 23.65° |
| Fluids & PVT | cond. 48.7 API · γg 0.763 · N₂ 1.2% · CO₂ 3% · H₂S 2 ppm · Tres 232°F · μcond 2 cp · σ 30 dyn/cm |
| Reservoir | Pri 3800 psi · Pr blank = Pri |
| IPR (Darcy) | K 5 mD · H 80 ft · Re 1640.5 · Rw 0.5104 · skin 0 |
| Multi-rate test | 2440/5.192 · 2000/10.002 · 1625/14.137 (psi / MMscf/d), Pwf blank = get Pwf |
IPR basis (one active): Darcy Pr² (default, dominant) or C & n (grey = fitted from the test). Calibration fits C&n, computes the test J, and matches Darcy K at your judged skin. Impurities feed the sour pseudo-critical route everywhere.
The reserve survey tables — oil memory-gauge, gas SITHP and gas memory-gauge — take Paste from clipboard, Import CSV, +10 rows and Clear, and grow to the data just like prod_data. Clipboard cells are read in the table’s own input-column order; a first line starting with a letter is treated as a header and skipped (a 17-Nov-14 date is not).
Gas — Reserve estimate
Prod data & macro
Per-row Pwf (input-or-march) → Pr = √(1000q/J + Pwf²) → explicit Z → Gp trapezoid → p/Z vs Gp line → minimum connected GIIP.
Pres from SITHP
Static shut-in surveys; Pres by the static gas march (gas-head-only stations, geothermal T, per-station Z — 1.2% vs the workbook case). Gp from prod cumulative; no IPR/VLP.
Reservoir limit
GIIP = q̄/(Ct·m)/1000. Defaults Sg 0.85 / So 0 / Sw 0.15; Cg grey = from two Bg points; slim table + Pwf-decline chart.
Pres from memory gauges
Measured static Pr — no march, no IPR: the gauge reading IS the datum. Inputs Date | Pr; calculated z | Gp | p/Z. Each reading must be a stabilized build-up corrected to datum. Defaults are the pressures selection 2 computes, so the two routes cross-check (both 120.19 Bscf).
Oil — Forecast (Tarner | Walsh)
Saturation-tracking material-balance forecast with a Method selector (one active). N and the start state (date, Np, Pres) are grey input-or-calculated, chained off the Reserve module — the date defaults to the last prod-data date, and both methods book an anchor row at that date so the curve begins there and joins the history. The forecast stream continues the well as last measured: Forecast THP, Forecast W.C and Forecast GOR are grey input-or-calculated, defaulting to the last prod row and overridable. All three are wellbore conditions held for the whole run — the GOR feeds the lift march every step, so a well producing free gas keeps that gas helping it flow, while the charted F GOR stays the MB reservoir GOR; both methods share the same inputs and the same Pwf source selection: nodal at forecast THP (VLP-coupled, default) or fixed minimum Pwf. Per step, both solve the same two residuals deterministically: pressure closes the gas balance (Gp by GOR trapezoid = Gp from MB), saturation closes the MB saturation; rel-perm from the sheet’s Kro(So)/Krg(Sg) polynomials; Brent/alternation to convergence instead of GoalSeek loops, the rate coupled to the trial pressure. PVT (Rs/Bo/Bg/μ) is evaluated at the trial pressure inside the solve — the training-material policy (“all PVT data evaluated at the assumed reservoir pressure”).
Tarner (default)
The classic method (workbook Tarner sheet): standard MB, So = (1−Swi)(N−Np)Bo/(N·Boi(1−ctΔp)), GOR = Rs + (Krg/Kro)(μoBo/μgBg), rate from the mobility PI J1 = 0.00708Kh/(ln(Re/Rw)−0.75+S).
Walsh (generalized MB, Rv)
Walsh’s generalized material balance (PETSOC 95-01-07) with the volatilized oil-gas ratio Rv(P): (Bg−Rv·Bo) terms in both the Gp and So equations, Foo-corrected GOR, and the CONSTANT calibrated PI on total mobility (qt = J·λt·Δp, qo = qt·λo/(λt·Bo)). Rv from the workbook’s tuned polynomial. Quirks preserved: μg = μo (the sheet’s Gas-Visc column reads the oil-viscosity cell); Bg in cf/scf.
Defaults: step 30 d · THP, W.C and GOR from the last prod row (300 psi / 60% / 5000 scf/stb on the demo well) · min Pwf 500 · Swi 0.15 · Cw 2.63e-6 · Cf 3.25e-6 · abandonment 50 stb/d · max 60 steps. Validation: 7 cell-pinned tests reproduce the Walsh sheet’s first solved row to 9+ digits; replaying the sheet’s Pwf column tracks its 64-step trajectory within ≈1% on pressure (EUR −2.2%, attributable to the sheet reading PVT from its parallel Tarner column). Chart: history + forecast (rate, GOR, Pres) on a calendar axis, table below.
Gas — Forecast
p/Z tank coupled to the nodal model, chained off the end of history: start date / start Gp / start Pres are grey input-or-calculated (last prod date, cumulative, minimum solved Pres). Defaults: step 30 d · FTHP 300 psi · plateau 12 MMscf/d · abandonment 1 MMscf/d · max 60 steps · GIIP & pᵢ/Zᵢ grey = from the reserve fit.
Each step: Pr from the p/Z line (first step anchors on start Pres) → Darcy IPR at Pr → nodal operating point at forecast FTHP → q = min(op, plateau) → Gp advances. Terminates on abandonment / depletion / dead well / max steps, reporting EUR and recovery %. The chart overlays history + forecast on a calendar-date axis; the results table sits below it.
Constants & conversions
| Constant | Value |
|---|---|
| scf → m³ | 0.02831684639 |
| bbl → m³ | 0.158987304 |
| Water density | 998.9926968 kg/m³ ≡ 62.36509524 lb/ft³ |
| psi → Pa | 1 / 0.000145037738 |
| π (trajectory cosine) | 22/7 (Excel legacy, preserved) |
| π (areas) | 3.14 |
| Pb calibration constant | 2.1045604254721 |
Oil PVT (Standing-metric family)
With TC = (T−32)·5/9, ρₒ = 141.5/(131.5+API)·998.9926968 kg/m³, rs in m³/m³.
Pb [psi] = 125·[ (582·rsi/γg)^0.83 · 10^(0.00164·Tc − 1768/ρo) − 1.4 ] / 14.5 · 2.1045604254721 Rs(P<Pb) : rs = (γg/582) · [ (8e-6·P_Pa + 1.4) · 10^(1768/ρo − 0.00164·Tc) ]^(1/0.83); Rs = Rsi above Pb Bo_sat = 0.9759 + 12e-5 · (177·rs·√(γg/ρo) + 2.25·Tc + 40)^1.2 Bo(P>Pb) = e^(3e-6·(Pb−P)) · Bo_sat(Rsi) μ_od (Glaso) = 3.141e10 · T_F^(−3.444) · (log10 API)^a , a = 10.313·log10(T_F) − 36.447 Beggs–Robinson : A = 10.715·(Rs+100)^−0.515 , B = 5.44·(Rs+150)^−0.338 , μo = A·μod^B Vasquez–Beggs P>Pb: μ = μob·(P/Pb)^m , m = 2.6·P^1.187·10^(−3.9e-5·P − 5)
Gas PVT
Sweet pcrits (oil family): Tpc = 169 + 314·γg Ppc = 708.75 − 57.7·γg Sour route (gas wells) : γhc = (γg − 0.9672·yN2 − 1.5195·yCO2 − 1.1762·yH2S)/(1 − Σy) Tc_hc = 187 + 330·γhc − 71.5·γhc² Pc_hc = 706 + 51.7·γhc − 11.1·γhc² Kay mixing (+227.3/547.6/672.4 T; +493/1071/1306 P) + Wichert–Aziz ε correction
Brill & Beggs Z (explicit — the ONLY Z model): A = 1.39·(Tpr−0.92)^0.5 − 0.36·Tpr − 0.101 B = (0.62−0.23·Tpr)·Ppr + (0.066/(Tpr−0.86) − 0.037)·Ppr² + 0.32/10^(9(Tpr−1))·Ppr⁶ C = 0.132 − 0.32·log10(Tpr) D = 10^(0.3106 − 0.49·Tpr + 0.1824·Tpr²) Z = A + (1−A)/e^B + C·Ppr^D
μg = μ_base(γg,T)/Tpr · exp( Dempsey poly16(Ppr,Tpr) ) (a0 = −2.462 … a11 = 0.00441, sheet column V) ρg = 28.97·γg·P / (Z·10.73·(T+460)) [lb/ft³] b (march) = 0.0283·Z·(T+460)/(P+14.5) Bg (res. limit) = 0.00504·5.61·(T+460)·Z/P [cf/scf] Bg (oil MB) = 0.0283·Z·(T+460)/(P+14.5)/5.615 [bbl/scf]
Quirk preserved: the oil family anchors Tpr at station 1; the gas march uses local Tpr.
Trajectory, temperatures & marches
TVD = kickoff + (AH − kickoff)·cos(θ·(22/7)/180)
— also places the ESP pump: its setting depth is entered as MEASURED
depth (mAH) and converted to TVD on this trajectory
Ramey WHT (always calculated):
K10 = (OD_ft·3.14·U)/((w/24)·Cp) K11 = e^(−K10·L₁) (L₁ = FIRST station spacing — sheet quirk)
T_shelf(x) = T_soil + (T_res−T_soil)·TVD(x)/TVD_tot
bottom-up: T(i) = T_shelf(i) + (T(i+1) − T_shelf(i))·K11
Chen friction (Fanning): 1/√f = −4·log10[ ε/3.7065 − (5.0452/Re)·log10( ε^1.1098/2.8257 + (7.149/Re)^0.8981 ) ] dP_fric/dx = (1/144)·f·w²/(7.413e10·d_ft⁵·ρ_mix)·matchFriction Head acts on ΔTVD, friction on ΔAH — explicit Euler, 25–30 stations, no iteration.
Oil march (modified Griffith) — one march for natural / gas lift / ESP:
E_L = 1 − 0.38·(1 + v_m/0.8 − √disc) (Griffith, author's 0.38) Ashry head factor (applied to the hydrostatic term): f_WC = 7.27117418e-6·WC² − 1.19359442e-3·WC + 1.00062546 GOR≤400 : f_GOR = −6.59987105e-7·GOR² + 3.56896045e-4·GOR + 1.02495053 400–5000 : f_GOR = 1.97914840e-8·GOR² − 1.61983769e-4·GOR + 1.09560265 (capped at 5000) F_head = (f_GOR·f_WC)^0.82
Segments: natural 29 stations; gas lift 15+14 around the injection point; ESP 26 + pump node + 2 (ΔP at the pump, separated tubing gas above, back-march for intake with properties at max(P, 100)).
Gas march (modified Gray) — Gray holdup with condensate + water loading, effective-roughness friction (base 0.0021 in), local Tpr. Bit-parity pins: BHP!D51 = 3598.66511095252 @ 1e-8; three real get Pwf points (3414.02 / 2913.97 / 2647.82 psi).
IPR
Oil — composite Vogel (q_gross; q_oil = q_gross·(1−WC/100)): Pr>Pb, Pwf≥Pb : q = J·(Pr − Pwf) Pr>Pb, Pwf<Pb : q = J·(Pr − Pb) + (J·Pb/1.8)·(1 − 0.2·Pwf/Pb − 0.8·(Pwf/Pb)²) Pr≤Pb : q = (J·Pr/1.8)·(1 − 0.2·Pwf/Pr − 0.8·(Pwf/Pr)²) Darcy J (dominant): J = 0.00708·K·h / [ μo·Bo·(ln(Re/Rw) − 0.75 + S) ] Calibrate: user judges skin → closed-form matched K so J_Darcy = J_test (Pri-anchored) User PI basis: type J + Pres directly (ESP workbook route, "Iput PI"); K becomes the DERIVED cell — same closed-form match against the typed J, grey = program-filled. Geometry optional. Lift-type switch loads that workbook’s demo case on a pristine form. Future J (J_2x): μo·Bo re-evaluated at the future Pr, with Beggs–Robinson A/B at the CURRENT-Pr Rs (sheet quirk). Pins: J_21–23 = 5.27146844059 / 5.8313895807703 / 6.78094220552862
Gas: Darcy Pr² : q [MMscf/d] = J·(Pr² − Pwf²)/1000 J_test = 1000·q/(Pri² − Pwf²) From K : J = 703e-6·K·h / [ μg·Z·(T+460)·(ln(0.472·Re/Rw) + S) ] C & n : q = C·(Pr² − Pwf²)^n (log-log least squares) Pr from test: Pr = √(1000·q/J + Pwf²) | Pr = √((q/C)^(1/n) + Pwf²)
Multi-layer (optional): per-layer rates at common Pwf with crossflow warnings; equivalent record gas-exact (J_t = ΣJᵢ, Pr₀ = √(ΣJᵢPrᵢ²/ΣJᵢ)); oil Pr-average solves ΣQᵢ = 0.
Nodal analysis
Operating point: Brent root of R(q) = VLP(q) − IPR(q), highest-rate crossing (stable branch) Well-head curve: WHP(q) = Pwf_IPR(q) − Pwf_VLP(q) + FTHP, with calculated WHT per rate get_Pwf: any blank test/production Pwf is marched from its FTHP at its own rate and ratios
Reserves
GAS — p/Z (selections 1 & 2): Pr per row (closed form) → Z → Gp trapezoid → least-squares p/Z vs Gp GIIP [Bscf] = −(p/Z)ᵢ / slope (minimum connected) SITHP statics: gas-head-only station march, geothermal T, per-station Z GAS & OIL — reservoir limit (selection 3): m = −slope(Pwf vs t) over ALL rows Cg = (Bg₁ − Bg₂)/Bg₁/(P₂ − P₁) (first & last rows; input-or-calculated) Ct = Cg·Sg + Co·So + Cw·Sw + Cf GIIP [Bscf] = q̄/(Ct·m)/1000 STOIP [MMstb] = q̄/(Ct·m)/1e6
OIL — Havlena–Odeh (selections 1 & 2, solution-gas drive → minimum STOIIP): Pr per row: composite-Vogel inversion fixed-point iterated with J_2 (macro loops 5×) Np, Gp trapezoids (produced gas: Rsi above Pb, row GOR below); Rp = Gp/Np F = Np·( Bo + (Rp − Rs)·Bg ) Eo = Bo + (Rsi − Rs)·Bg − Boi Headline N = AVERAGE(F/Eo) over rows with Np > 0 (sheet AD1) Cross-check N = SLOPE(F vs Eo) incl. the exact (0,0) anchor (sheet V1) Selection 2 evaluates the same MB at MEASURED gauge pressures — no IPR/VLP.
Forecast (gas)
start: date = last prod date, Gp = cumulative, Pres = MIN solved Pres (all overridable)
per Δt: (p/Z)_target = (p/Z)ᵢ·(1 − Gp/GIIP)
Pr = start Pres (first step) | Brent-invert P/Z(P) = target
Darcy IPR at Pr → nodal op at forecast FTHP → q = min(q_op, plateau)
Gp += q·Δt/1000
stops: abandonment | depleted | died | max-steps → EUR, recovery %
Forecast (oil — Tarner & Walsh)
Both march the macro's GoalSeek PAIR per step, alternated to convergence:
AE: Gp by the producing-GOR trapezoid over Np = Gp from the MB → solve P (Brent)
AF: the assumed So = So from the MB → solve So (substitution)
Pwf frozen for the step; PVT at the TRIAL pressure; 25 passes, |ΔP|<1e-6, |ΔSo|<1e-10
shared: Kro(So), Krg(Sg) = the sheet 6th-order polynomials (Tarner!F16/G16), verbatim
Sg = max(0, 1 − Swi − So); So clamped to [0.05, 1 − Swi]
ct = (Cw·Swi + Cf)/(1 − Swi)·(Pri − P) Swi .15, Cw 2.63e-6, Cf 3.25e-6 /psi
Np += qo·Δt/1e6; Gp += (GOR_prev + GOR_new)/2 · ΔNp
Pwf 'vlp' = nodal vs the REAL oil march at forecast FTHP, floored at minPwf
'fixed' = constant minPwf (the sheet's active behaviour)
an anchor row at the start date (Δt = 0) joins the forecast to its history
Tarner — solution-gas MB, MOBILITY Darcy PI (no μ·B; mobility enters through λ):
J1 = 0.00708·K·h/(ln(Re/Rw) − 0.75 + S) λt = Kro/μo + Krg/μg λo = Kro/μo
qo = J1·(λo/Bo)·(P − Pwf)
So = (1 − Swi)·(N − Np)·Bo / (N·Boi·(1 − ct)) (Tarner!Y)
Gp = N·(Rsi − Rs) − (N·Boi − (N − Np)·Bo)/Bg + N·Boi·ct/Bg + Np·Rs (Tarner!O)
GOR = Rs + (Krg/Kro)·(μo·Bo)/(μg·Bg) (Tarner!P)
Bg in bbl/scf; μg at reservoir T, sweet pseudo-criticals, LOCAL Tpr
Walsh — generalized (volatile-oil) MB with Rv, on the CONSTANT calibrated PI J:
Rv(P) = the sheet 6th-order polynomial (Walsh!AH) / 1000 [bbl/scf]
qt = J·λt·(P − Pwf); qo = qt·λo/(λt·Bo) J = Walsh!$I$13 = J_2
Gp = (N·(Boi·(1 − Rv·Rs) − (Bg − Rv·Bo)·Rsi − (Bo − Rs·Bg)) + Np·Bo − Np·Rs·Bg)
/ (Bo·Rv − Bg) + (N·Boi/Bg)·ct/(1 − Swi) (Walsh!O)
So = (1 − Swi)·((1 − Np/N)·Bo·Bg − Boi·Rv·Bo)
/ (Boi·(Bg − Rv·Bo)·(1 − ct)) (Walsh!Y)
Foo = 1/(1 + (Bo·Krg·μo·Rv)/(Bg·Kro·μg)) (Walsh!AI)
GOR = Foo·(Rs + (Krg/Kro)·(μo·Bo)/(μg·Bg)) (Walsh!P)
QUIRKS KEPT: μg = μo (Walsh!W reads the OIL-visc cell BHP!AQ)
Bg in cf/scf, NOT bbl/scf — the Walsh MB is written in those units
start Gp = Np₀·Rsi (Tarner starts at 0)
stops: qo < abandon (50 stb/d) | Np ≥ .999·N | P ≤ max(1.02·minPwf, 120) | dead | max-steps
→ EUR, recovery %
LIMIT: neither MB carries a water term — the Forecast W.C affects LIFT only, never
the balance. On a high-water-cut well that is a real limit of the method.
Documented deviations from the workbooks
- Z factor: the sheets GoalSeek Hall–Yarborough (left unconverged in places); WellSim is explicit Brill & Beggs. Station parity pinned at 1e-9 with sheet Z injected; march drift bands: natural <2%, gas-lift <5.5%, ESP <3–5%; gas march bit-exact.
- Solvers: Brent replaces GoalSeek and the forecast’s quadratic-LSQ intersection — same intent, exact crossing.
- Forecast plateau Pwf: reported at the produced (constrained) rate; the sheet’s unconstrained-intersection quirk is not replicated.
- Pressure update: exact p/Z inversion instead of the one-step-lag Z estimate.
- Oil reservoir-limit Cg anchors: first & last rows (sheet pinned rows 5 & 14) — near-immaterial for oil Ct.
- WHT: always calculated (Ramey); the input-THT path was removed by design decision.
- Rounding artifacts preserved where parity needs them: π = 22/7 trajectory cosine; the
rouhgscartifact available as a test override. - Oil-forecast PVT evaluation point: Rs/Bo/Bg/μ are evaluated at the TRIAL pressure inside each step’s solve, for both methods. The saved Tarner sheet froze PVT at initial values; the training material directs otherwise, and the Walsh Gp residual moves with pressure almost entirely through the PVT (Bg, Rs, Rv) — freezing it leaves no root.
- Walsh PVT table pressure: the sheet reads its PVT table at the parallel TARNER sheet’s pressures (a shared-table convenience of the workbook); WellSim reads it at Walsh’s own solved pressure.
Troubleshooting
| Symptom | Cause & fix |
|---|---|
| “unparseable date row N” | Fix that row; nothing was computed. Use d-MMM-yy, dd/mm/yyyy hh:mm:ss, ISO, or a day number. |
| “no depletion signal” | The fitted p/Z (or MB Eo) is not declining — more data spread needed, or the volume genuinely is not closing. |
| Huge oil MB N | Undersaturated path + window too long. Use the early-production window (days–weeks) or trust the static-gauge selection. |
| Gas march error: CGR + WGR > 0 | Gray’s liquid terms divide by liquid rate — use a small CGR (0.1 stb/MMscf) for essentially dry gas. |
| Reserve/forecast uses the wrong well | Both always read the current Well-model inputs — calibrate the well model first. |
Contact
Questions, bug reports, or a workbook you would like ported — write to [email protected].