Files
rods/README.md
Conner Majic 725a72a773 Initial commit: establish deterministic rod-string solver stack.
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
2026-04-16 21:59:42 -06:00

9.5 KiB

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+drag to pan, mouse wheel / buttons to zoom, projection toggle (perspective/orthographic), and reset view.
  • Overlay modes:
    • DLS: uses fixed bad-section threshold 15 deg/100
    • Side-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,load rows.
  • Validate Surface Card calls POST /solve/validate-card.
  • Solver tab workflow=diagnostic now sends surfaceCard to POST /solve.
  • Solve calls include options.enableProfiles=true so 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
  1. AGENTS.md
  2. Agents/MATH_SPEC.md
  3. Agents/COMPUTE_PLAN.md
  4. docs/engineering/units.md
  5. docs/engineering/validation.md