Set up the C solver core, Node API orchestration, TS GUI workflow, and engineering documentation with cleaned repo hygiene for private Git hosting. Made-with: Cursor
Rods Cursor — Rod-string solver (math-first)
Deterministic C numerical core (FDM + FEA), Node API for XML and orchestration, TypeScript GUI for workflow. See AGENTS.md for agent rules and Agents/MATH_SPEC.md for equations and paper citations.
Repository layout
| Directory | Purpose |
|---|---|
solver-c/ |
C: damped-wave FDM, dynamic bar FEM, diagnostic transfer, trajectory preprocess, JSON stdin drivers |
solver-api/ |
Express: POST /solve, GET /solve/default, POST /case/parse, POST /solve/validate-card, XML → SI |
gui-ts/ |
Vite + React tabbed case editor + uPlot dynacards |
data/cases/ |
base-case.xml and regression inputs |
data/golden/ |
Golden SHA-256 fingerprint for default solve regression |
Agents/ |
MATH_SPEC.md, COMPUTE_PLAN.md (handoff) |
docs/engineering/ |
Architecture, schema, units, validation |
references/papers/ |
Literature citations and access notes for the solver math backbone |
Vision
Build a transparent, deterministic rod-string and wellbore mechanics platform that is field-usable and research-grade.
- Physically explainable pumping-system simulations, not black-box outputs.
- Dynamometer cards as the primary interpretation surface.
- Inspectability of imported case data and solver assumptions.
- Multi-model validation (FDM vs FEA) with clear comparison metadata.
Prerequisites
- Local:
gcc,make, Node 20+,npm - Docker: Docker Engine + Compose plugin
Run locally
Build and run C solver (stdin JSON)
The API spawns solver-c/solver_main and pipes one JSON object on stdin (schemaVersion: 2). Legacy 9-argument CLI is removed.
gcc -std=c99 -I./solver-c/include \
./solver-c/src/solver_common.c \
./solver-c/src/json_stdin.c \
./solver-c/src/trajectory.c \
./solver-c/src/solver_diagnostic.c \
./solver-c/src/solver.c \
./solver-c/src/solver_fea.c \
./solver-c/src/solver_fourier.c \
./solver-c/src/main.c -lm -o ./solver-c/solver_main
gcc -std=c99 -I./solver-c/include \
./solver-c/src/solver_common.c \
./solver-c/src/json_stdin.c \
./solver-c/src/trajectory.c \
./solver-c/src/solver_diagnostic.c \
./solver-c/src/solver.c \
./solver-c/src/solver_fea.c \
./solver-c/src/solver_fourier.c \
./solver-c/src/main_fea.c -lm -o ./solver-c/solver_fea_main
./solver-c/test_solver
API + GUI
cd solver-api && npm install && npm run dev
cd gui-ts && npm install && npm run dev
- API:
http://localhost:4400/health - GUI:
http://localhost:5173
Run with Docker
make run # or: docker compose up --build
make smoke # requires API on 4400
make down
Validation
make test # solver-api vitest + gui-ts tests + solver-c test_solver
./solver-c/test_solver
Golden hash: solver-api tests assert /solve/default body matches data/golden/default.solve.sha256 (after normalizing generatedAt).
API examples
Predictive (default base case)
curl -sS "http://localhost:4400/solve/default?solverModel=both" | jq '.schemaVersion, .runMetadata, .comparison | keys'
Predictive (POST XML)
curl -sS -X POST http://localhost:4400/solve \
-H "Content-Type: application/json" \
-d "{\"xml\": $(jq -Rs . < data/cases/base-case.xml), \"solverModel\": \"fdm\"}" | jq '.solver.pointCount, .schemaVersion'
Extended physics outputs (profiles/diagnostics/fourier)
curl -sS -X POST http://localhost:4400/solve \
-H "Content-Type: application/json" \
-d "{\"xml\": $(jq -Rs . < data/cases/base-case.xml), \"solverModel\": \"both\", \"options\": {\"enableProfiles\": true, \"enableDiagnosticsDetail\": true, \"enableFourierBaseline\": true, \"fourierHarmonics\": 10}}" \
| jq '.solver.profiles.nodeCount, .solver.diagnostics.valveStates[0], .comparison.fourier.harmonics'
Diagnostic (measured surface card)
Build surfaceCard from a predictive run or field data:
CARD=$(curl -sS "http://localhost:4400/solve/default?solverModel=fdm")
# Then POST xml + workflow + surfaceCard (see solver-api/tests/api.test.js)
Surface card QA only
curl -sS -X POST http://localhost:4400/solve/validate-card \
-H "Content-Type: application/json" \
-d '{"surfaceCard":{"position":[0,1,2],"load":[10,11,12]}}' | jq .
Parse XML only (no solve)
curl -sS -X POST http://localhost:4400/case/parse \
-H "Content-Type: application/json" \
-d "{\"xml\": $(jq -Rs . < data/cases/base-case.xml)}" | jq '.schemaVersion, .model.wellName, (.unsupportedFields | length)'
Returns { model, rawFields, unsupportedFields, warnings, schemaVersion: 2 } — same shape as GET /case/default, but for an uploaded XML string. Used by the GUI to hydrate its case editor from an import.
GUI tabbed case editor
gui-ts renders a tabbed UI backed by a single CaseState that round-trips to/from the XML document. On first load it pulls GET /case/default and populates every tab; editing any field mutates CaseState, which is serialized back into <INPRoot><Case>…</Case></INPRoot> on solve or export. Untouched XML fields (fatigue, IPR blocks, pumping-unit catalog keys, etc.) are preserved verbatim in the output.
| Tab | Contents |
|---|---|
| Well | Well name, company, units selection, pump depth, tubing anchor / size |
| Trajectory | Editable MD / Inc / Az survey table with vertical + deviated presets |
| Kinematics | Pumping speed (SPM), speed option, unit ID, upstroke/downstroke percentages |
| Rod String | Taper table (diameter, length, modulus, rod type) with base-case preset |
| Pump | Plunger diameter, friction, intake pressure, fillage option + percent |
| Fluid | Water cut, water SG, oil API, tubing gradient |
| Solver | solverModel (fdm/fea/both), workflow selector, damping + friction knobs, engineering checks gate, Run Solver |
| Results | KPI banner, uPlot dynacard (polished + downhole, with FEA overlay when applicable), FDM↔FEA comparison, warnings, unsupported-field list, 3D wellbore/rod/pump view with DLS or side-load contour, trajectory analytics table, pump diagnostics, export tools |
| Advanced / XML | File upload + paste box (POST /case/parse), export current state as XML, raw-field inspector |
Built-in engineering checks
- Pump depth vs total rod length: solver run is blocked if the absolute mismatch exceeds 15 m.
- Trajectory integrity: requires at least 2 stations and strictly increasing measured depth.
- DLS warning threshold: if max dogleg severity exceeds 15 deg/100, the UI surfaces a warning.
These are fixed guardrails (not user configurable) to keep behavior deterministic and consistent across sessions.
3D wellbore visualization
The Results tab includes a 3D projected wellbore panel:
- Tubing trajectory polyline colored by DLS contour (green/yellow/red).
- Bad sections highlighted in red for DLS >= 15 deg/100.
- Rod string overlay drawn from surface to rod total length with depth color gradient.
- Pump marker placed along trajectory at
PumpDepth. - Interactive controls: drag to rotate,
Shift+dragto pan, mouse wheel / buttons to zoom, projection toggle (perspective/orthographic), and reset view. - Overlay modes:
DLS: uses fixed bad-section threshold15 deg/100Side-load risk: colors by normalized side-load profile returned from solver outputs (options.enableProfiles=true)
Trajectory analytics + cross-highlight
- Results includes a per-segment trajectory table (
MD start/end,ΔMD,DLS, severity). - Clicking a segment row highlights the corresponding 3D trajectory segment.
- Clicking a segment in 3D highlights the corresponding table row.
- Filter toggle supports "bad sections only".
- Keyboard accessibility: segment rows are focusable and selectable with
Enter/Space.
Pump placement diagnostics
Results tab now reports:
- nearest survey station to pump depth,
- pump-to-station
ΔMD, - survey-end to pump
ΔMD, - rod-total to pump
Δ, - tubing-anchor to pump
Δ, with quick navigation buttons back to Well / Trajectory / Rod tabs for correction.
Visualization artifact export
Results tab export buttons:
- 3D wellbore SVG
- 3D wellbore PNG
- run/check summary JSON
These are generated client-side from the rendered SVG and current run/check state.
Diagnostic workflow (GUI wired)
- Kinematics tab accepts measured surface card points as
position,loadrows. Validate Surface CardcallsPOST /solve/validate-card.- Solver tab
workflow=diagnosticnow sendssurfaceCardtoPOST /solve. - Solve calls include
options.enableProfiles=trueso side-load overlays can be rendered.
Solver modes
solverModel |
Behavior |
|---|---|
fdm |
Finite-difference damped wave + variable rod + trajectory friction |
fea |
1D bar FEM + Newmark + Rayleigh damping |
both |
Runs FDM + FEA; returns solvers and extended comparison |
workflow |
Behavior |
|---|---|
predictive |
Harmonic surface motion (unless overridden later) |
diagnostic |
Surface card BC; FDM in C; FEA uses bisection on pump load to match measured top load |
Optional CI image
docker build -t rods-ci .
docker run --rm rods-ci