# Rod pump geometry

The mechanics of a downhole sucker-rod pump: plunger travel off a crank, the fluid load and displacement from the field formulas, the travel at which each ball valve lifts, and the dynamometer card that falls out of the two.

> For the complete index, see [llms.txt](https://robocn.dev/llms.txt). A Markdown version of any page is available by appending `.md` to its URL or by sending an `Accept: text/markdown` header.

## Install

```bash
bunx --bun shadcn@latest add https://robocn.dev/r/rodpump-geometry.json
```

Registry item: `rodpump-geometry` · [`https://robocn.dev/r/rodpump-geometry.json`](https://robocn.dev/r/rodpump-geometry.json)

## Notes

- One compression model produces the whole diagnostic family. The travelling valve opens where the gas trapped below the plunger reaches discharge pressure, or on contact with the liquid, whichever comes first — so `gas` is a Boyle curve, `pound` is the same function with no gas to compress, and both fall back onto `full` as the barrel fills. They are not three drawn shapes.
- The two balls are one mechanism, not two. They are the two ends of a single volume — the barrel between the standing valve and the plunger — and `chamberPressure` is what holds each of them down or lets it go. Below intake pressure the standing valve lifts; above discharge the travelling valve does; in between **both are down** and the plunger is only changing the pressure. They can never both pass, because one chamber cannot be under the formation and over the discharge column at once. Which way the plunger is going decides which way that pressure is moving, so the whole valve sequence is two facts and not a schedule.
- A ball is not a switch either. Once the pressure has let it go, `valveFlow` is the plunger's own rate through it and `ballLift` rides it up its cage on that flow, letting it settle back as the stroke slows — so it comes off its seat, floats, and beds again over real travel. A plunger falling through a void moves nothing, so its ball stays down through the whole fall and slams to the cage the moment it meets liquid. That is a fluid pound, and it falls out of the same chamber the card does.
- The standing valve's delay is the mirror of that compression: the same gas has to expand back to intake pressure before formation fluid will come in, which is zero travel for a barrel that fills and a measurable fraction of the stroke for a gassy one.
- No wave equation. The surface card is not propagated down the rod string — no stretch, damping, inertia, buoyancy, friction, gas solubility, temperature or slippage rate. `TRANSFER` is one constant standing in for the stretch that rounds a real card's corners, and this is the downhole card, which is the one a pump failure is read from anyway.
- Pure functions over plain objects: no React, no three.js, no dependencies.

## API

| name | type | default | description |
| --- | --- | --- | --- |
| `solveRodPump(cycle, condition?, geometry?)` | `(cycle: number, condition?: PumpCondition, geometry?: PumpGeometry) => RodPumpPose` | — | The whole pump at one point in its cycle: travel, direction, load, both valves, the liquid in the barrel, and the loads and rates in field units. One call, one frame of drawing. |
| `plungerTravel(cycle)` | `(cycle: number) => number` | — | 0 with the plunger on bottom, 1 on top. Hung off a crank, so it dwells at both ends and runs fastest through the middle. |
| `strokeDirection(cycle)` | `(cycle: number) => 1 | -1` | — | Up over the first half of the cycle, down over the second. |
| `cycleForTravel(travel, near?)` | `(travel: number, near?: number) => number` | — | The cycle nearest `near` that puts the plunger at `travel`. Every travel happens twice a cycle; picking the nearer branch is what lets a person drag the plunger and have it turn over at the ends instead of reversing. |
| `fluidLoad(geometry?)` | `(geometry?: PumpGeometry) => number` | — | Fo = 0.34 · D² · G · L, pounds: the plunger area times the head of the column it lifts. |
| `pumpDisplacement(geometry?)` | `(geometry?: PumpGeometry) => { displacement: number; production: number }` | — | PD = 0.1166 · D² · S · N, barrels a day, and what this fillage actually lifts. |
| `chamberPressure(cycle, condition?, geometry?)` | `(cycle: number, condition?: PumpCondition, geometry?: PumpGeometry) => number` | — | Pressure in the barrel between the standing valve and the plunger, 0 at pump intake and 1 at discharge. **The one number the pump turns on**: the load on the rods is what is left of the differential across the plunger, and both balls are held down or let go by it. Going up the plunger expands whatever gas the clearance kept; coming down it compresses what is trapped above the liquid, until it meets liquid, which does not compress at all. |
| `pumpState(cycle, condition?, geometry?)` | `(cycle: number, condition?: PumpCondition, geometry?: PumpGeometry) => PumpState` | — | Which of the card's four sides the pump is on: `filling`, `discharging`, or the two transfers — `picking-up` and `releasing` — where **both balls are down** and the plunger is doing nothing but change the pressure. Instants in a pump that fills; a fifth of the stroke in a gassy one. |
| `pumpRegime(condition, geometry?)` | `(condition: PumpCondition, geometry?: PumpGeometry) => PumpRegime` | — | What a condition does to the pump, as numbers: fillage, intake ratio, how much of the unfilled barrel is compressible gas rather than void, valve slip, backflow, tagging. The six cards are six points in one model rather than six drawn shapes. |
| `tvOpenTravel(regime) / svOpenTravel(regime)` | `(regime: PumpRegime) => number` | — | The travel at which the chamber first reaches discharge and first falls back to intake — so where each ball lifts. For a barrel that fills, the travelling valve opens at the top of the stroke (the card's square corner) and the standing valve at the bottom; a gassy one discharges late *and* fills late. |
| `pumpLoad(cycle, condition?, geometry?)` | `(cycle: number, condition?: PumpCondition, geometry?: PumpGeometry) => number` | — | Load on the plunger as a fraction of Fo: the card's ordinate, where `plungerTravel` is its abscissa. |
| `valveFlow(cycle, condition?, geometry?)` | `(cycle: number, condition?: PumpCondition, geometry?: PumpGeometry) => { travelling: number; standing: number }` | — | Flow through each valve as a fraction of the plunger's peak rate: `plungerSpeed` gated by the valve, and gated by the same travel the card is built from, so the flow, the load and the valve can never disagree. A plunger falling through a void moves nothing. |
| `ballLift(flow)` | `(flow: number) => number` | — | Where a ball rides, 0 on its seat to 1 against its cage. Not a switch: the stream past it carries it up until the drag balances its own submerged weight, and it settles back as the flow dies at the end of the stroke. Linear in flow, because the annular area past the ball opens as it lifts. |
| `plungerSpeed(cycle)` | `(cycle: number) => number` | — | Plunger velocity normalised to ±1 — the derivative of the travel, so zero at both ends of the stroke and fastest through the middle. Whatever the pump is moving, it is moving it at this rate. |
| `pumpValves(cycle, condition?, geometry?)` | `(cycle: number, condition?: PumpCondition, geometry?: PumpGeometry) => PumpValves` | — | Both ball lifts, both valve flows, and how much liquid the barrel is holding. |
| `volumetricEfficiency(regime)` | `(regime: PumpRegime) => number` | — | How much of the swept volume reaches surface: fillage, less slip past the travelling valve, less backflow through the standing valve. A pump can stroke perfectly and still deliver half of what it displaces. |
| `pumpCard(condition?, geometry?, steps?)` | `(condition?: PumpCondition, geometry?: PumpGeometry, steps?: number) => PumpCard` | — | The closed card and its peak load, sampled over one cycle — so the running dot cannot leave the card it is drawn on. |
| `defaultPumpGeometry` | `PumpGeometry` | — | A 1.75 in plunger on an 86 in stroke lifting 0.9 gravity fluid 4200 ft at 8 spm: about 3900 lb of fluid load, and 246 bpd of displacement — what the barrel sweeps, not what the well makes. |
| `PumpCondition` | `"full" | "gas" | "pound" | "tv-leak" | "sv-leak" | "tagging"` | — | The diagnostic cards a pump draws. `full` is the one the rest are read against. |

## Source

- `src/lib/robocn/rodpump.ts`
