diff --git a/.github/workflows/pr-validation.yaml b/.github/workflows/pr-validation.yaml new file mode 100644 index 0000000..91bef96 --- /dev/null +++ b/.github/workflows/pr-validation.yaml @@ -0,0 +1,103 @@ +name: PR Validation + +on: + pull_request: + branches: + - main + - dev + +jobs: + validate-source-branch: + runs-on: ubuntu-latest + steps: + - name: Validate PR source branch + run: | + TARGET_BRANCH="${{ github.base_ref }}" + SOURCE_BRANCH="${{ github.head_ref }}" + + echo "PR: $SOURCE_BRANCH → $TARGET_BRANCH" + echo "" + + # ============================================================ + # PRs to main: Only allow dev and hotfix/* branches + # ============================================================ + if [ "$TARGET_BRANCH" = "main" ]; then + # Allow dev branch (normal release workflow) + if [ "$SOURCE_BRANCH" = "dev" ]; then + echo "✅ Release PR: dev → main" + exit 0 + fi + + # Allow hotfix/* branches (emergency production fixes) + if [[ "$SOURCE_BRANCH" =~ ^hotfix/ ]]; then + echo "✅ Hotfix PR: $SOURCE_BRANCH → main (emergency fix)" + echo "" + echo "⚠️ REMINDER: After merging, sync hotfix back to dev:" + echo " git checkout dev && git merge main && git push origin dev" + exit 0 + fi + + # Block feature/* branches + if [[ "$SOURCE_BRANCH" =~ ^feature/ ]]; then + echo "❌ Error: Feature branches cannot merge directly to main" + echo " Current: $SOURCE_BRANCH → main" + echo "" + echo "Correct workflow:" + echo " 1. Create PR: $SOURCE_BRANCH → dev" + echo " 2. After testing in dev, create release PR: dev → main" + exit 1 + fi + + # Block bugfix/* branches + if [[ "$SOURCE_BRANCH" =~ ^bugfix/ ]]; then + echo "❌ Error: Bugfix branches cannot merge directly to main" + echo " Current: $SOURCE_BRANCH → main" + echo "" + echo "Correct workflow:" + echo " 1. Create PR: $SOURCE_BRANCH → dev (test with other changes)" + echo " 2. After testing in dev, create release PR: dev → main" + echo "" + echo "💡 TIP: For production emergencies, use hotfix/* branches instead" + exit 1 + fi + + # Block any other branch + echo "❌ Error: PRs to main must come from dev or hotfix/* branches" + echo " Current: $SOURCE_BRANCH → main" + echo "" + echo "Allowed sources for main:" + echo " • dev (normal releases)" + echo " • hotfix/* (emergency production fixes)" + exit 1 + fi + + # ============================================================ + # PRs to dev: Recommend feature/bugfix/refactor branches + # ============================================================ + if [ "$TARGET_BRANCH" = "dev" ]; then + # Allow main → dev (syncing after hotfix) + if [ "$SOURCE_BRANCH" = "main" ]; then + echo "✅ Sync PR: main → dev (syncing hotfix)" + exit 0 + fi + + # Check for standard branch prefixes + if [[ "$SOURCE_BRANCH" =~ ^(feature|bugfix|refactor)/ ]]; then + echo "✅ Development PR: $SOURCE_BRANCH → dev" + exit 0 + fi + + # Warn about non-standard branch names + echo "⚠️ Warning: PRs to dev should use standard branch prefixes" + echo " Current: $SOURCE_BRANCH → dev" + echo "" + echo "Recommended prefixes:" + echo " • feature/* - New features" + echo " • bugfix/* - Bug fixes" + echo " • refactor/* - Code improvements" + echo "" + echo "✅ Validation passed (warning only)" + exit 0 + fi + + echo "✅ PR validation passed" diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 832072d..55bb766 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -1,8 +1,6 @@ name: quality on: - push: - branches: [main] pull_request: jobs: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8f759f6..b7ef74d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -202,4 +202,4 @@ jobs: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index ad7fcac..8dd6ee0 100644 --- a/.gitignore +++ b/.gitignore @@ -92,4 +92,4 @@ docs/_build/ site/ .spec-workflow/ -.mcp.json \ No newline at end of file +.mcp.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..bb33c65 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,54 @@ +# Pre-commit hooks for code quality +# Install: uv run pre-commit install +# Run manually: uv run pre-commit run --all-files +# Update hooks: uv run pre-commit autoupdate +# Skip hooks: git commit --no-verify + +repos: + # Standard pre-commit hooks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + exclude: ^mkdocs\.yml$ # MkDocs uses Python-specific YAML tags + - id: check-toml + - id: check-added-large-files + args: ['--maxkb=1000'] # Prevent files >1MB + - id: check-merge-conflict + - id: check-case-conflict + - id: no-commit-to-branch + args: ['--branch=main', '--branch=dev'] # Prevent commits to protected branches + + # Ruff - Fast linting and formatting (Rust-based) + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + # Run the formatter + - id: ruff-format + types_or: [python, pyi] + + # Run the linter with auto-fix + - id: ruff + types_or: [python, pyi] + args: [--fix] + + # Ty - Fast type checking (Rust-based) + - repo: local + hooks: + - id: ty + name: ty type checker + entry: uv run ty check + language: system + types: [python] + pass_filenames: false # ty checks whole project + + # Codespell - Catch common typos + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + args: + - --ignore-words-list=nd,te,ue # Common false positives in scientific code + - --skip="*.ipynb,*.json,*.lock,*.svg" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9e21d53 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,666 @@ +# Contributing to Relperm + +Thank you for your interest in contributing to Relperm! This document provides guidelines and instructions for contributing to this petroleum engineering library. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [Getting Started](#getting-started) +- [Development Workflow](#development-workflow) +- [Branch Strategy](#branch-strategy) +- [Code Quality Standards](#code-quality-standards) +- [Testing Requirements](#testing-requirements) +- [Documentation Standards](#documentation-standards) +- [Pull Request Process](#pull-request-process) +- [Release Process](#release-process) + +## Code of Conduct + +This project is intended to be a welcoming space for collaboration. We expect all contributors to: +- Be respectful and inclusive +- Focus on constructive feedback +- Prioritize scientific accuracy and code quality +- Help maintain clear documentation + +## Getting Started + +### Prerequisites + +- **Python**: >=3.12 +- **Package Manager**: [UV](https://github.com/astral-sh/uv) (not pip or poetry) +- **Git**: For version control +- **GitHub CLI** (optional but recommended): `gh` for easier PR management + +### Setting Up Your Development Environment + +1. **Fork and clone the repository:** + ```bash + gh repo fork oskrgab/relperm --clone + cd relperm + ``` + +2. **Install UV** (if not already installed): + ```bash + curl -LsSf https://astral.sh/uv/install.sh | sh + ``` + +3. **Set up the development environment:** + ```bash + uv sync # Installs all dependencies including dev dependencies + ``` + +4. **Install pre-commit hooks** (IMPORTANT): + ```bash + uv run pre-commit install + ``` + + This installs git hooks that automatically check code quality before each commit and **prevent accidental commits to protected branches** (`main` and `dev`). + +5. **Verify your setup:** + ```bash + uv run pytest # Run tests + uv run ruff check . # Check code quality + uv run ty check # Type check + uv run pre-commit run --all-files # Test pre-commit hooks + ``` + +### Optional: Git Aliases for Easier Workflow + +Add these to `~/.gitconfig`: + +```ini +[alias] + # Create feature branch from updated dev + feature = "!f() { git checkout dev && git pull && git checkout -b feature/$1; }; f" + + # Sync dev with remote + sync = "!git checkout dev && git pull origin dev" + + # Clean up merged branches + cleanup = "!git branch --merged dev | grep -v '^* dev$' | xargs git branch -d" +``` + +Usage: +```bash +git feature my-new-feature # Creates feature/my-new-feature from updated dev +git sync # Updates local dev +git cleanup # Deletes merged feature branches +``` + +### Pre-commit Hooks: Your First Line of Defense + +Pre-commit hooks run **automatically before each commit** to catch issues early. + +**What's checked:** + +1. **Branch protection** - Blocks commits to `main` and `dev` (forces feature branch workflow) +2. **Code formatting** - Auto-formats with ruff +3. **Linting** - Auto-fixes common issues with ruff +4. **Type checking** - Validates types with ty +5. **File hygiene** - Removes trailing whitespace, fixes line endings +6. **YAML/TOML validation** - Checks workflow files and pyproject.toml +7. **Spell checking** - Catches typos with codespell + +**Normal workflow:** + +```bash +git checkout -b feature/my-feature +# ... make changes ... +git add . +git commit -m "Add feature" + +# Pre-commit runs automatically: +# ✅ All checks pass → commit succeeds +# ❌ Any check fails → commit blocked, files may be auto-fixed +``` + +**If hooks auto-fix files:** + +```bash +git commit -m "Add feature" +# ruff-format............Failed +# - files were modified by this hook + +# Ruff reformatted your code, re-add and commit: +git add . +git commit -m "Add feature" +# ✅ Passes this time +``` + +**Protection from mistakes:** + +```bash +# ❌ Trying to commit on protected branch +git checkout main +git commit -m "oops" + +# Output: +# no-commit-to-branch........Failed +# You're attempting to commit on branch 'main' +# Committing directly to 'main' is not allowed. + +# ✅ Use feature branches instead +git checkout -b feature/my-fix +git commit -m "Add fix" # Works! +``` + +**Skip hooks temporarily (use sparingly!):** + +```bash +git commit --no-verify # Bypasses pre-commit hooks +# WARNING: CI will still catch issues, and you can't push to main/dev anyway! +``` + +**Update hooks to latest versions:** + +```bash +uv run pre-commit autoupdate +``` + +## Development Workflow + +### Branch Strategy + +This project uses a **three-branch model**: + +``` +main (production, stable releases only) + ↑ + └── dev (integration branch, latest development) + ↑ + ├── feature/* (new features) + ├── bugfix/* (bug fixes) + ├── refactor/* (code improvements) + └── hotfix/* (emergency production fixes - can also go to main) +``` + +### Branch Naming Conventions + +Use these prefixes for your branches: + +- **`feature/*`** - New features or enhancements + - Example: `feature/add-let-model`, `feature/dataset-loader` + +- **`bugfix/*`** - Bug fixes found during development + - Example: `bugfix/fix-calculation-error`, `bugfix/validation-bug` + +- **`refactor/*`** - Code improvements without changing functionality + - Example: `refactor/improve-validation`, `refactor/simplify-api` + +- **`hotfix/*`** - Emergency fixes for production issues + - Example: `hotfix/security-patch`, `hotfix/critical-bug` + - **Note**: Hotfix branches can merge directly to `main`, bypassing `dev` + +### Standard Development Flow + +**⚠️ IMPORTANT**: Branch protection is **remote-only**. Git allows you to commit to local `main` or `dev`, but GitHub will reject the push. Always work on feature branches! + +#### For Regular Development (Features, Bugfixes, Refactoring) + +1. **Create a branch from `dev`:** + ```bash + git checkout dev + git pull origin dev # Sync with remote first + git checkout -b feature/your-feature-name + ``` + +2. **Make your changes:** + - Write code following our [code quality standards](#code-quality-standards) + - Add tests (we require >90% coverage) + - Update documentation + - Commit frequently (commit message quality doesn't matter - we squash merge) + +3. **Run quality checks locally:** + + **Option A: Let pre-commit handle it (recommended)** + ```bash + git add . + git commit -m "Add feature" + # Pre-commit automatically runs: ruff format, ruff check, ty check + # You still need to run tests manually: + uv run pytest --cov=relperm + ``` + + **Option B: Run checks manually before committing** + ```bash + uv run ruff format . # Auto-format code + uv run ruff check --fix . # Fix linting issues + uv run ty check # Type check + uv run pytest --cov=relperm # Run tests with coverage + ``` + + **Note:** Tests are NOT run by pre-commit hooks (too slow). Always run tests manually before pushing. + +4. **Push and create PR to `dev`:** + ```bash + git push -u origin feature/your-feature-name + gh pr create --base dev --title "Add feature: your description" + ``` + +5. **Wait for CI checks:** + - `test` - Runs pytest + - `lint` - Runs ruff format check, ruff check, ty check + - `validate-source-branch` - Ensures correct branch workflow + +6. **Address review feedback** (if any) and push updates + +7. **After merge**, clean up: + ```bash + git checkout dev + git pull origin dev + git branch -d feature/your-feature-name + ``` + +#### For Emergency Production Fixes (Hotfixes) + +1. **Create a hotfix branch from `main`:** + ```bash + git checkout main + git pull origin main + git checkout -b hotfix/critical-issue + ``` + +2. **Make your fix and test thoroughly** + +3. **Create PR directly to `main`:** + ```bash + git push -u origin hotfix/critical-issue + gh pr create --base main --title "Hotfix: critical issue description" + ``` + +4. **After merge to `main`, sync back to `dev`:** + ```bash + git checkout dev + git pull origin dev + git merge main + git push origin dev + ``` + +### Recovery: Accidentally Committed to Local Main/Dev + +**Note:** Pre-commit hooks should prevent this! If you're seeing this scenario, make sure you installed pre-commit hooks with `uv run pre-commit install`. + +If you somehow bypassed pre-commit hooks (or didn't install them) and committed to `main` or `dev`: + +```bash +# 1. Check what commits you made +git log --oneline -5 + +# 2. Create feature branch from current position (saves your commits) +git checkout -b feature/recover-my-work + +# 3. Reset local main/dev to match remote (discards local commits) +git checkout dev # or main +git reset --hard origin/dev # or origin/main + +# 4. Continue working on feature branch +git checkout feature/recover-my-work +git push -u origin feature/recover-my-work +gh pr create --base dev +``` + +## Branch Strategy + +### PR Validation Rules + +Our CI automatically validates that PRs follow the correct workflow: + +**PRs to `main` - Only these are allowed:** +- ✅ `dev` branch (normal release workflow) +- ✅ `hotfix/*` branches (emergency production fixes) + +**PRs to `main` - These are blocked:** +- ❌ `feature/*` branches (must merge to `dev` first) +- ❌ `bugfix/*` branches (must merge to `dev` first) +- ❌ Any other branches + +**PRs to `dev` - Recommended:** +- ✅ `feature/*` branches (new features) +- ✅ `bugfix/*` branches (bug fixes) +- ✅ `refactor/*` branches (code improvements) +- ✅ `main` branch (syncing hotfixes) +- ⚠️ Other branches (allowed with warning) + +## Code Quality Standards + +### Design Principles + +- **SOLID principles** - Use appropriate design patterns for Python +- **Not everything needs to be a class** - Use pure functions for mathematical operations +- **Use classes where appropriate** - Data models, correlation families, dataset loaders +- **Readability first** - Code should be self-documenting +- **Separation of concerns** - Keep validation, calculation, and presentation logic separate + +### Ruff (Linter & Formatter) + +We use Ruff for code formatting and linting: + +**Configuration** (in `pyproject.toml`): +- Line length: 88 characters +- Python target: 3.12 +- Double quotes for strings +- NumPy-style docstrings (mandatory) + +**Run locally before committing:** +```bash +uv run ruff format . # Auto-format code +uv run ruff check . # Check for issues +uv run ruff check --fix . # Auto-fix issues +``` + +**Enabled rule sets:** +- `E`, `F` - Standard errors and pyflakes +- `I` - Import sorting (isort) +- `B` - flake8-bugbear (catches subtle bugs) +- `UP` - pyupgrade (modern Python syntax) +- `N` - PEP8 naming conventions +- `D` - Docstrings (MANDATORY) +- `NPY` - NumPy-specific rules +- `PT` - pytest best practices +- `SIM` - Simplification suggestions + +### Ty (Type Checker) + +**Type hints are required for:** +- ✅ All function arguments +- ✅ All function return types +- ✅ All class attributes (in `__init__`) + +**Run locally:** +```bash +uv run ty check +``` + +**Example:** +```python +import numpy as np +import numpy.typing as npt + +def krw( + s_eff: npt.NDArray[np.float64], + krw0: float, + nw: float, +) -> npt.NDArray[np.float64]: + """Calculate relative permeability.""" + return krw0 * s_eff**nw +``` + +### Performance Philosophy + +1. **Start simple** - Direct NumPy array operations +2. **Vectorize early** - Avoid Python loops +3. **Profile before optimizing** - Don't guess bottlenecks +4. **Benchmark against NumPy** - Keep it simple if not significantly faster +5. **Document complexity** - Explain optimizations that hurt readability + +### Input Validation + +All public functions should validate inputs, but keep validation logic separate: + +```python +# _core/validation.py (internal) +def validate_saturation(s: npt.NDArray, name: str = "saturation") -> None: + """Validate saturation is in [0, 1] range.""" + if np.any((s < 0) | (s > 1)): + raise ValueError(f"{name} must be in [0, 1]") + +# Public API function +def krw(sw: npt.NDArray, swr: float, snwr: float, krw0: float, nw: float) -> npt.NDArray: + """Calculate relative permeability.""" + validate_saturation(sw, "sw") + validate_corey_params(swr, snwr, krw0, nw) + # ... calculation +``` + +## Testing Requirements + +### Coverage Requirement: >90% + +All contributions must maintain test coverage above 90%. + +**Check coverage:** +```bash +uv run pytest --cov=relperm --cov-report=term-missing +uv run pytest --cov=relperm --cov-report=html # HTML report in htmlcov/ +``` + +### Testing Strategy + +1. **Unit tests** - Test individual functions with known inputs/outputs +2. **Property-based testing** - Use `hypothesis` for mathematical properties +3. **Integration tests** - Test correlation models end-to-end +4. **Validation tests** - Ensure input validation catches invalid data +5. **Example tests** - Verify documentation examples work + +### Property-Based Testing + +For mathematical functions, use `hypothesis` to test properties: + +```python +from hypothesis import given, strategies as st +import numpy as np + +@given( + sw=st.lists(st.floats(0.2, 0.8), min_size=1, max_size=100), + swr=st.floats(0.0, 0.15), + snwr=st.floats(0.0, 0.15), +) +def test_effective_saturation_bounds(sw, swr, snwr): + """Property: Effective saturation should be in [0, 1].""" + sw_array = np.array(sw) + result = s_eff(sw_array, swr, snwr) + + assert np.all(result >= 0) + assert np.all(result <= 1) +``` + +**What is property-based testing?** +Instead of specific test cases, you define properties that should always be true. `hypothesis` generates hundreds of random test cases to validate these properties. Perfect for mathematical functions! + +## Documentation Standards + +### Docstring Requirements (Mandatory) + +**All public functions MUST have NumPy-style docstrings** with examples: + +```python +def krw( + s_eff: npt.NDArray[np.float64], + krw0: float, + nw: float, +) -> npt.NDArray[np.float64]: + r"""Calculate the relative permeability of the wetting phase. + + Parameters + ---------- + s_eff : npt.NDArray[np.float64] + Effective wetting phase saturation array. + krw0 : float + Endpoint relative permeability for the wetting phase. + nw : float + Corey exponent for the wetting phase. + + Returns + ------- + npt.NDArray[np.float64] + Relative permeability of the wetting phase array. + + Notes + ----- + The relative permeability ($k_{rw}$) is calculated using the Corey model: + + $$k_{rw} = k_{rw0} \cdot S_{eff}^{n_w}$$ + + where $k_{rw0}$ is the endpoint relative permeability, $S_{eff}$ is the + effective saturation, and $n_w$ is the Corey exponent. + + Examples + -------- + Calculate relative permeability for a saturation range: + + >>> import numpy as np + >>> s_eff = np.linspace(0, 1, 50) + >>> krw_values = krw(s_eff, krw0=0.8, nw=2.0) + >>> krw_values.shape + (50,) + >>> krw_values[0] # At s_eff=0 + 0.0 + >>> krw_values[-1] # At s_eff=1 + 0.8 + + References + ---------- + .. [1] Corey, A.T., "The Interrelation Between Gas and Oil Relative + Permeabilities", Producers Monthly, 1954. + """ + return krw0 * s_eff**nw +``` + +### Documentation Structure + +- **NumPy-style docstrings** for all public functions +- **LaTeX equations** using `$$...$$` syntax (rendered with MathJax) +- **Runnable examples** in docstrings (tested with pytest) +- **References** to scientific papers when applicable + +### Building Documentation Locally + +```bash +uv run mkdocs serve # Preview at http://127.0.0.1:8000 +``` + +Documentation is automatically deployed to GitHub Pages on release. + +## Pull Request Process + +### Before Creating a PR + +1. **Ensure pre-commit hooks are installed:** + ```bash + uv run pre-commit install # If you haven't already + ``` + +2. **Commit your changes** (pre-commit hooks run automatically): + ```bash + git add . + git commit -m "Clear description of changes" + # Pre-commit runs: format, lint, type check, branch protection + ``` + +3. **Run tests manually** (not covered by pre-commit): + ```bash + uv run pytest --cov=relperm + ``` + +4. **Ensure all tests pass** and **coverage is >90%** + +5. **Update documentation** if you changed the API + +6. **Add examples** to docstrings for new functions + +**Alternative:** Run all checks manually before committing: +```bash +uv run ruff format . +uv run ruff check --fix . +uv run ty check +uv run pytest --cov=relperm +``` + +### Creating a PR + +1. **Push your branch:** + ```bash + git push -u origin feature/your-feature-name + ``` + +2. **Create PR to `dev` branch:** + ```bash + gh pr create --base dev --title "Clear description of changes" + ``` + +3. **Fill out the PR description:** + - What problem does this solve? + - What changes were made? + - How was it tested? + - Any breaking changes? + +### PR Review Process + +- **Automated checks** will run: `test`, `lint`, `validate-source-branch` + - If you used pre-commit hooks, `lint` should already pass! + - `test` checks ensure coverage is >90% + - `validate-source-branch` confirms you're targeting the correct branch +- **All checks must pass** before merging +- **Maintainers will review** code quality, tests, and documentation +- **Address feedback** by pushing new commits to your branch +- **Squash merge** - All commits will be squashed into one clean commit on `dev` + +### After Your PR is Merged + +```bash +git checkout dev +git pull origin dev +git branch -d feature/your-feature-name # Clean up local branch +``` + +## Release Process + +Releases are managed by maintainers. The process is: + +1. **Batch features on `dev`** until ready for release +2. **Create release PR**: `dev` → `main` +3. **After merge, tag the release:** + ```bash + git checkout main + git pull origin main + git tag X.Y.Z + git push origin X.Y.Z + ``` +4. **Automated release workflow** handles: + - Building the package + - Publishing to PyPI + - Creating GitHub Release + - Deploying documentation + +## Common Commands Reference + +```bash +# Development +uv sync # Install/sync dependencies +uv run pytest # Run tests +uv run pytest --cov=relperm --cov-report=html # Test with coverage report +uv run ruff format . # Format code +uv run ruff check --fix . # Lint and auto-fix +uv run ty check # Type check +uv run mkdocs serve # Preview docs locally + +# Pre-commit hooks +uv run pre-commit install # Install git hooks (one-time setup) +uv run pre-commit run --all-files # Run all hooks on all files +uv run pre-commit autoupdate # Update hooks to latest versions +git commit --no-verify # Skip pre-commit hooks (emergency only) + +# Git workflow +git checkout dev # Switch to dev +git pull origin dev # Sync with remote +git checkout -b feature/my-feature # Create feature branch +git push -u origin feature/my-feature # Push feature branch +gh pr create --base dev # Create PR to dev +``` + +## Getting Help + +- **Issues**: Open a [GitHub issue](https://github.com/oskrgab/relperm/issues) for bugs or feature requests +- **Discussions**: Use [GitHub Discussions](https://github.com/oskrgab/relperm/discussions) for questions +- **Documentation**: Check the [official docs](https://oskrgab.github.io/relperm) + +## Project Goals + +This library aims to: +- Provide **industry-standard** relative permeability and capillary pressure correlations +- Maintain **scientific accuracy** with proper references +- Offer **high performance** using NumPy vectorization +- Include **public datasets** for easy access +- Support the **petroleum engineering community** with quality open-source tools + +Thank you for contributing to Relperm! 🛢️ diff --git a/README.md b/README.md index 49c69d0..6d40f13 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,4 @@ pip install relperm ```python import relperm # Your usage examples here -``` \ No newline at end of file +``` diff --git a/pyproject.toml b/pyproject.toml index 67a3a77..0732354 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,10 +14,12 @@ build-backend = "uv_build" [dependency-groups] dev = [ + "codespell>=2.4.1", "mkdocs>=1.6.1", "mkdocs-material>=9.7.1", "mkdocstrings>=1.0.0", "mkdocstrings-python>=2.0.1", + "pre-commit>=4.5.1", "pymdown-extensions>=10.19.1", "pytest>=8.4.2", "ruff>=0.14.10", @@ -67,4 +69,4 @@ python-version = "3.12" [tool.ty.rules] # "check" usually implies running all default rules, but you can be specific: # For scientific code, we generally want strict typing on mathematical functions. -# If you find 'ty' too noisy, you can downgrade specific rules to "warn" \ No newline at end of file +# If you find 'ty' too noisy, you can downgrade specific rules to "warn" diff --git a/src/relperm/__init__.py b/src/relperm/__init__.py index b10af73..a272131 100644 --- a/src/relperm/__init__.py +++ b/src/relperm/__init__.py @@ -1,3 +1,3 @@ from .relperm import krw, s_eff -__all__ = ["krw", "s_eff"] \ No newline at end of file +__all__ = ["krw", "s_eff"] diff --git a/uv.lock b/uv.lock index 974e03d..5606859 100644 --- a/uv.lock +++ b/uv.lock @@ -34,6 +34,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.4" @@ -103,6 +112,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] +[[package]] +name = "codespell" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/e0/709453393c0ea77d007d907dd436b3ee262e28b30995ea1aa36c6ffbccaf/codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5", size = 344740, upload-time = "2025-01-28T18:52:39.411Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/01/b394922252051e97aab231d416c86da3d8a6d781eeadcdca1082867de64e/codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425", size = 344501, upload-time = "2025-01-28T18:52:37.057Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -112,6 +130,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -136,6 +172,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, ] +[[package]] +name = "identify" +version = "2.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -361,6 +406,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "numpy" version = "2.3.2" @@ -469,6 +523,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -587,10 +657,12 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "codespell" }, { name = "mkdocs" }, { name = "mkdocs-material" }, { name = "mkdocstrings" }, { name = "mkdocstrings-python" }, + { name = "pre-commit" }, { name = "pymdown-extensions" }, { name = "pytest" }, { name = "ruff" }, @@ -602,10 +674,12 @@ requires-dist = [{ name = "numpy", specifier = ">=2.3.2" }] [package.metadata.requires-dev] dev = [ + { name = "codespell", specifier = ">=2.4.1" }, { name = "mkdocs", specifier = ">=1.6.1" }, { name = "mkdocs-material", specifier = ">=9.7.1" }, { name = "mkdocstrings", specifier = ">=1.0.0" }, { name = "mkdocstrings-python", specifier = ">=2.0.1" }, + { name = "pre-commit", specifier = ">=4.5.1" }, { name = "pymdown-extensions", specifier = ">=10.19.1" }, { name = "pytest", specifier = ">=8.4.2" }, { name = "ruff", specifier = ">=0.14.10" }, @@ -696,6 +770,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, ] +[[package]] +name = "virtualenv" +version = "20.35.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, +] + [[package]] name = "watchdog" version = "6.0.0"