Metadata-Version: 2.4
Name: libzrod
Version: 0.1.28
Summary: Sucker Rod Design Library
Keywords: zrod,srod,qrod,rodstar,rod pump,rod design,rod lift,sucker rod,gibbs,everitt jennings,petroleum,oil well,pumpjack,wave equation
Author-Email: Walter Phillips <dev@zrod.io>
License-Expression: LicenseRef-Commercial
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: C
Classifier: Programming Language :: C++
Classifier: Programming Language :: Python
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Project-URL: Homepage, https://zrod.io
Project-URL: Terms of Service, https://zrod.io/terms
Project-URL: Privacy Policy, https://zrod.io/privacy
Requires-Python: >=3.9
Requires-Dist: numpy
Description-Content-Type: text/markdown

# libzrod

### Sucker Rod Design Library (Wave Equation / Gibbs / Everitt-Jennings)

A wave equation library for sucker rod pump design and analysis, written in C/C++ and wrapped in Python.

## Overview

libzrod solves the 1D damped wave equation to calculate dynamometer cards for both **diagnostic** (measured card in, pump card out) and **predictive** (parameters in, surface + pump cards out) analysis of sucker rod pumping systems. The core engine is cross-platform C++, exposed to Python via ctypes.

**Key capabilities:**
- Diagnostic analysis: transform a measured surface dynamometer card into a downhole pump card
- Predictive analysis: generate surface and pump cards from well parameters (rod string, pump, fluid, geometry)
- Rod loading analysis: max/min stress per rod section with Modified Goodman limits
- Pump velocity and chamber pressure visualization
- Multi-format file I/O (.dyn, .rsdx/.rsvx, .inp6/.inp6e, .qrd, .zrod, .DAT)
- Cloud design management (save, fetch, share via AWS)

## Installation

```bash
pip install libzrod
```

**Requirements:** Python 3.9 - 3.13, numpy. Supported platforms: Windows (x86_64), macOS (x86_64/ARM), Linux (x86_64).

## Authorization

An account is required to use the library:

1. Create an account at [zrod.io](https://zrod.io)
2. Email for manual authorization of your trial account (account creation is not monitored)
3. Note: The beta library includes time restrictions

Or see the [Colab Example](https://colab.research.google.com/drive/1kIxbrbpOUuc1pFZcnCw43Jhnt-wc3XLd?usp=sharing) for a quick demo.

## Quick Start

```python
from libzrod import zrod, TaperBase_t, TubingBase_t, WaveParams_t
import numpy as np

# Create engine (singleton — only one instance allowed)
myzrod = zrod()
myzrod.Login("username", "password")

# Configure well parameters
waveParams = myzrod.GetWaveParams()
waveParams.WellDepth = 8000              # feet
waveParams.diagSpm = 6.0                 # strokes per minute
waveParams.predSpm = 6.0
waveParams.diagDampUp = 0.05             # upstroke damping factor
waveParams.diagDampDn = 0.05             # downstroke damping factor
waveParams.predDampUp = 0.05
waveParams.predDampDn = 0.05
waveParams.predFo = 2000                 # predicted fluid load (lbs)
myzrod.SetWaveParams(waveParams)

# Define rod string (3-taper example)
tarr = (TaperBase_t * 3)()
tarr[0] = TaperBase_t(id=b"", L=1000, D=0.875, W=2.224, E=30500000, R=492)
tarr[1] = TaperBase_t(id=b"", L=1700, D=0.750, W=1.630, E=30500000, R=492)
tarr[2] = TaperBase_t(id=b"", L=300,  D=1.500, W=6.500, E=30500000, R=492)
myzrod.xSetDiagTapers(tarr, 1.0)         # 1.0 = fluid specific gravity
myzrod.xSetPredTapers(tarr, 1.0)

# Define tubing
tuarr = (TubingBase_t * 1)()
tuarr[0] = TubingBase_t(L=8000, innerDiameter=2.992, outerDiameter=3.500, weight=9.3, W=2.904, E=30500000, R=490)
myzrod.xSetDiagTubings(tuarr)
myzrod.xSetPredTubings(tuarr)

# Load a measured surface card
myzrod.LoadDynFromDAT("SX4D05B.DAT")     # .DAT format parser (Python-only)
# Or: myzrod.LoadDyn(spm, position_array, load_array)
# Or: myzrod.ParseDesignFile("wellfile.zrod")

# Run the wave equation solver
if myzrod.RunDesign():
    # Retrieve diagnostic results (measured card → pump card)
    (surfX, surfY) = myzrod.GetMeasuredDyno()       # surface card (position, load)
    (pumpX, pumpY) = myzrod.GetMeasuredPump()        # calculated pump card

    # Retrieve upscaled (curve-fitted) diagnostic
    (upscX, upscY) = myzrod.GetUpscaledDyno()
    (upscPX, upscPY) = myzrod.GetUpscaledPump()

    # Retrieve predictive results
    (predDynX, predDynY) = myzrod.GetPredDyno()      # predicted surface card
    (predPmpX, predPmpY) = myzrod.GetPredPump()       # predicted pump card

    # Numeric results
    results = myzrod.GetWaveResults()
    print(f"Diag Fo: {results.diag.FoSKr:.0f} lbs")
    print(f"Pred pump stroke: {results.pred.PumpStrokeNet:.2f} in")
```

## API Reference

### Initialization & Auth

| Method | Description |
|--------|-------------|
| `zrod()` | Create engine instance (singleton) |
| `Login(username, password) -> bool` | Authenticate with zrod.io |
| `Logout() -> bool` | End session |
| `SetClientVersion(platform, major, minor, rev, build)` | Set client identifier |
| `GetZrodVersion() -> np.ndarray` | Get C++ library version [major, minor, rev, build] |
| `GetZrodVersionString() -> str` | Get version as "M.m.r.b" string |

### Configuration

| Method | Description |
|--------|-------------|
| `GetWaveParams() -> WaveParams_t` | Get current wave parameters |
| `SetWaveParams(params) -> bool` | Set wave parameters |
| `GetWaveSettings() -> WaveSettings_t` | Get advanced solver settings |
| `SetWaveSettings(settings) -> bool` | Set advanced solver settings |
| `xSetDiagTapers(tapers, fluidSG) -> bool` | Set diagnostic rod string (infers count) |
| `xSetPredTapers(tapers, fluidSG) -> bool` | Set predictive rod string (infers count) |
| `SetDiagTapers(tapers, count, fluidSG) -> bool` | Set diagnostic rod string (manual count) |
| `SetPredTapers(tapers, count, fluidSG) -> bool` | Set predictive rod string (manual count) |
| `xSetDiagTubings(tubings) -> bool` | Set diagnostic tubing string (infers count) |
| `xSetPredTubings(tubings) -> bool` | Set predictive tubing string (infers count) |
| `SetDiagTubings(tubings, count) -> bool` | Set diagnostic tubing string (manual count) |
| `SetPredTubings(tubings, count) -> bool` | Set predictive tubing string (manual count) |
| `xSetCasings(casings) -> bool` | Set casing string (infers count) |
| `SetCasings(casings, count) -> bool` | Set casing string (manual count) |
| `SetDiagApiTaperByNumber(num, pumpDia, pumpDepth) -> bool` | Set diagnostic taper from API rod number |
| `SetPredApiTaperByNumber(num, pumpDia, pumpDepth) -> bool` | Set predictive taper from API rod number |
| `SetPuApi(puApi) -> bool` | Set pumping unit geometry |
| `SetPuInfo(puInfo) -> bool` | Set pumping unit info (with designation) |
| `SetPuByName(name) -> bool` | Set pumping unit by database name |
| `SetFourierCoeffCountPos(count)` | Set Fourier coefficients for position curve fitting |
| `SetFourierCoeffCountLoad(count)` | Set Fourier coefficients for load curve fitting |
| `SetUpscaledDynoPointCount(count)` | Set upscaled output resolution |

### Calculation

| Method | Description |
|--------|-------------|
| `RunDesign() -> bool` | Execute wave equation solver (diagnostic + predictive) |
| `CalculateFo() -> float` | Calculate fluid load from pump/pressure parameters (lbs) |
| `CalculateKrToDepth(depth) -> float` | Calculate rod spring constant to a given depth |
| `ValidateDesign() -> list[dict]` | Validate current design, returns list of issues |

### Results — Diagnostic

| Method | Returns | Description |
|--------|---------|-------------|
| `GetMeasuredDyno()` | `(pos, load)` | Measured surface card |
| `GetMeasuredPump()` | `(pos, load)` | Calculated downhole pump card |
| `GetMeasuredPumpColors()` | `np.ndarray[int32]` | Pump valve state colors per point |
| `GetMeasuredPumpVelocity()` | `np.ndarray` | Plunger velocity (in/sec) |
| `GetMeasuredPumpVelocityNormalized()` | `np.ndarray` | Plunger velocity (0-100 scale) |
| `GetMeasuredPumpChamberPressure()` | `np.ndarray` | Chamber pressure (psi) |
| `GetMeasuredPumpChamberPressureNormalized()` | `np.ndarray` | Chamber pressure (0-100 scale) |
| `GetUpscaledDyno()` | `(pos, load)` | Curve-fitted surface card |
| `GetUpscaledPump()` | `(pos, load)` | Pump card from curve-fitted input |
| `GetUpscaledPumpColors()` | `np.ndarray[int32]` | Valve state colors |
| `GetUpscaledPumpVelocity()` | `np.ndarray` | Plunger velocity |
| `GetUpscaledPumpVelocityNormalized()` | `np.ndarray` | Plunger velocity (0-100) |
| `GetUpscaledPumpChamberPressure()` | `np.ndarray` | Chamber pressure |
| `GetUpscaledPumpChamberPressureNormalized()` | `np.ndarray` | Chamber pressure (0-100) |

### Results — Predictive

| Method | Returns | Description |
|--------|---------|-------------|
| `GetPredDyno()` | `(pos, load)` | Predicted surface card |
| `GetPredPump()` | `(pos, load)` | Predicted pump card |
| `GetPredPumpColors()` | `np.ndarray[int32]` | Valve state colors |
| `GetPredPumpVelocity()` | `np.ndarray` | Plunger velocity |
| `GetPredPumpVelocityNormalized()` | `np.ndarray` | Plunger velocity (0-100) |
| `GetPredPumpChamberPressure()` | `np.ndarray` | Chamber pressure |
| `GetPredPumpChamberPressureNormalized()` | `np.ndarray` | Chamber pressure (0-100) |

### Results — Rod Loading

| Method | Returns | Description |
|--------|---------|-------------|
| `xGetMeasuredRodLoading()` | `(depth, lbsMax, lbsMin, pctMax, pctMin)` | Measured rod loading per section |
| `xGetUpscaledRodLoading()` | `(depth, lbsMax, lbsMin, pctMax, pctMin)` | Upscaled rod loading |
| `xGetPredRodLoading()` | `(depth, lbsMax, lbsMin, pctMax, pctMin)` | Predicted rod loading |

### Results — Other

| Method | Returns | Description |
|--------|---------|-------------|
| `GetWaveResults()` | `WaveResults_t` | Numeric results (Fo, Kr, pump stroke, production, etc.) |
| `GetWaveParamsReadOnly()` | `WaveParamsReadOnly_t` | Computed parameters (point counts, DT, buoyancy) |
| `GetIntermediateCard(nodeindex, dynotype)` | `(pos, load)` | FEA node card (dynotype: 1=meas, 2=upsc, 3=pred) |
| `GetIntermediateTimeSlice(timestep, dynotype)` | `(pos, load)` | All nodes at one timestep |
| `GetPermissibleLoads(destX, destY, count, clipmin, clipmax)` | `bool` | Modified Goodman envelope |

### File I/O

| Method | Description |
|--------|-------------|
| `LoadDyn(spm, pos_array, load_array) -> bool` | Load measured card from arrays |
| `LoadDynFromFile(filepath) -> bool` | Load .dyn file |
| `LoadDynFromFileContents(text) -> bool` | Load .dyn from string |
| `LoadDynFromDAT(filepath) -> bool` | Load .DAT format (Python-only parser) |
| `ParseDesignFile(filepath) -> bool` | Parse design file (.zrod, .rsdx, .inp6, etc.) |
| `ParseDesignFileContents(contents, ftype) -> bool` | Parse from string |
| `WriteDesignFile(filepath) -> bool` | Write .zrod design file |
| `WriteDesignFileWithTemplate(filepath, templatepath) -> bool` | Write with template |

### Design Management (Cloud)

> **Note:** Cloud design management is a work in progress and subject to change.

| Method | Description |
|--------|-------------|
| `FetchDesigns(fromserver) -> int` | Fetch design list (True=server, False=cached). Returns count. |
| `GetDesigns() -> list[DesignInfo]` | Get list of DesignInfo objects |
| `GetDesign(designId) -> bool` | Load a design by ID |
| `SaveCurrentDesign(overwrite) -> bool` | Save current design to cloud |
| `SubmitDesign(email, nonblocking) -> bool` | Submit design for review |
| `DeleteDesign(designId) -> bool` | Delete a cloud design |
| `SetDesignTitle(title) -> bool` | Set design title |
| `GetDesignTitle() -> str` | Get design title |
| `SetComment(comment) -> bool` | Set design comment |
| `GetComment() -> str` | Get design comment |
| `GetDesignID() -> str` | Get current design ID (compact UUID) |

### Metadata & Debug

| Method | Description |
|--------|-------------|
| `GetPuInfo() -> PuInfo_t` | Get pumping unit info |
| `GetCasingCount() -> int` | Get casing section count |
| `GetCasings() -> list[CasingBase_t]` | Get casing sections |
| `GetDeviationSurveyCount() -> int` | Get deviation survey point count |
| `GetDeviationSurvey() -> list[DeviationSurveyPoint_t]` | Get deviation survey |
| `PrintZrodObj()` | Print internal state to console |
| `DebugZrodObj(filepath)` | Dump debug info to file (if available) |

## Data Structures

### TaperBase_t — Rod Section

| Field | Type | Unit | Description |
|-------|------|------|-------------|
| `id` | `bytes[32]` | — | Rod grade identifier (e.g. `b"D"`) |
| `L` | `float` | feet | Section length |
| `D` | `float` | inches | Rod body diameter |
| `W` | `float` | lb/ft | Weight per foot |
| `T` | `float` | psi | Minimum tensile strength |
| `E` | `float` | psi | Young's modulus (typically 30,500,000) |
| `R` | `float` | lb/ft^3 | Density (typically 490 for steel) |
| `RL` | `float` | feet | Individual rod length for UI (default 25) |
| `K` | `float` | — | Modified Goodman K constant (default 2.8) |
| `M` | `float` | — | Modified Goodman M constant (default 0.375) |

### TubingBase_t — Tubing Section

| Field | Type | Unit | Description |
|-------|------|------|-------------|
| `L` | `float` | feet | Section length |
| `innerDiameter` | `float` | inches | Tubing ID |
| `outerDiameter` | `float` | inches | Tubing OD |
| `weight` | `float` | lb/ft | Weight per foot |
| `W` | `float` | lb/ft | Rod-in-tubing weight factor |
| `T` | `float` | psi | Tensile strength |
| `E` | `float` | psi | Young's modulus |
| `R` | `float` | lb/ft^3 | Density |

### WaveParams_t — Core Parameters

Key fields (see CLAUDE.md for full list):

| Field | Type | Unit | Description |
|-------|------|------|-------------|
| `WellDepth` | `float` | feet | Total well depth |
| `diagSpm` / `predSpm` | `float` | SPM | Strokes per minute |
| `diagDampUp` / `diagDampDn` | `float` | — | Damping factors (0.0–1.0, typical 0.05) |
| `predDampUp` / `predDampDn` | `float` | — | Predictive damping factors |
| `diagFluidSG` / `predFluidSG` | `float` | — | Fluid specific gravity |
| `diagPumpPlungerDiameter` | `float` | inches | Plunger diameter |
| `diagPumpDepth` / `predPumpDepth` | `float` | feet | Pump setting depth |
| `predFo` | `float` | lbs | Target fluid load (or use `CalculateFo()`) |
| `predFillage` | `float` | % | Pump fillage (0–100) |
| `predCompression` | `float` | — | Gas compression ratio |
| `usePumpingUnitForPosition` | `bool` | — | Use PU geometry for position (vs measured) |

## Naming Conventions

**`x`-prefix methods** (e.g. `xSetDiagTapers`, `xGetMeasuredRodLoading`) are Pythonic convenience wrappers:
- `xSet*` infers the array count from `len()` and passes `ctypes.byref()` automatically
- `xGet*` allocates numpy output arrays automatically and returns them

The non-`x` versions require you to pass count and pre-allocated arrays manually.

## Workflow Patterns

### Diagnostic + Predictive Analysis

```
1. zrod() → Login()
2. GetWaveParams() → set fields → SetWaveParams()
3. xSetDiagTapers() + xSetPredTapers()
4. xSetDiagTubings() + xSetPredTubings()
5. LoadDyn() or LoadDynFromDAT() or ParseDesignFile()
6. RunDesign()
7. GetMeasuredDyno(), GetMeasuredPump(), GetPredDyno(), GetPredPump()
8. GetWaveResults() for numeric output
```

### Predictive-Only (No Measured Card)

```
1. zrod() → Login()
2. GetWaveParams() → set fields, usePumpingUnitForPosition=True → SetWaveParams()
3. SetPuApi() or SetPuByName()
4. xSetPredTapers(), xSetPredTubings()
5. CalculateFo() or set predFo manually
6. RunDesign()
7. GetPredDyno(), GetPredPump()
```

### File Round-Trip

```
1. ParseDesignFile("input.zrod")    # loads all parameters + measured card
2. RunDesign()
3. WriteDesignFile("output.zrod")   # saves current state
```

## Constants

```python
# Prediction algorithm types (for WaveSettings_t.predAlgorithmType)
# Note: Integer values are subject to change — use the PRED_ALGORITHM_* names.
PRED_ALGORITHM_STANDARD = 0       # Default
PRED_ALGORITHM_OLD = 1            # Legacy algorithm
PRED_ALGORITHM_EXPERIMENTAL = 2   # Experimental
PRED_ALGORITHM_LEAKAGE = 3       # With leakage modeling
```

## Known Limitations

The following C API functions are **not yet wrapped** in the Python interface:

| C Function | Purpose |
|------------|---------|
| `SetDeviationSurvey()` | Set wellbore deviation survey |
| `SetDeviationSurveyWithMIA()` | Set deviation survey (interleaved format) |
| `SetSaveIntermediateCards()` | Enable intermediate FEA node card output |
| `SetStatusCallback()` | Async status callbacks |
| `GetDiagTaperCount/GetPredTaperCount` | Query rod section counts |
| `GetDiagTapers/GetPredTapers` | Retrieve rod sections |
| `GetDiagTubingCount/GetPredTubingCount` | Query tubing section counts |
| `GetDiagTubings/GetPredTubings` | Retrieve tubing sections |

## Platform Support

| Platform | Architecture | Python Versions |
|----------|-------------|-----------------|
| Windows | x86_64 | 3.9 - 3.13 |
| macOS | x86_64, ARM64 | 3.9 - 3.13 |
| Linux | x86_64 | 3.9 - 3.13 |

## Mobile Apps

Beta access available for iOS and Android demo apps. Contact for access.

## Web App

The [ZROD web app](https://zrod.io) demonstrates the full feature set of the library, including interactive dynamometer card visualization, rod loading analysis, pump velocity display, multi-format file import/export, and cloud design management.

## Troubleshooting

**`RunDesign()` returns False**: Call `ValidateDesign()` to get a list of issues with codes and messages. Common causes: missing rod string, missing measured card (when `usePumpingUnitForPosition=False`), zero SPM, or invalid pump parameters.

**Singleton warning**: `zrod` is a singleton. Creating a second instance returns the same object with a warning. Use `del myzrod` before creating a new one if needed.

**Platform binary not found**: The library ships platform-specific binaries. Ensure your Python version and platform match a supported wheel.

## Contact & Licensing

For licensing inquiries, reach out via [LinkedIn](https://www.linkedin.com/in/wansco/).

## Legal

- [Terms of Service](https://zrod.io/terms)
- [Privacy Policy](https://zrod.io/privacy)

---

*Help improve this document. Contact me with feedback or ambiguities.
