Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Project Guidelines

## Overview

phreeqcinwt is a Python wrapper around PHREEQC (via `phreeqpy`) for aqueous geochemistry modeling in water treatment applications. It provides a dict-driven API for simulating water chemistry, predicting mineral scaling, and calculating solution properties.

## Code Style

- **Formatter**: Black 24.8.0 — all code must pass `black --check .`
- **Naming**: `snake_case` for functions/variables, `PascalCase` for classes (note: existing classes like `phreeqcWTapi` and `dataBaseManagment` use non-standard casing — follow existing patterns when modifying those classes)
- **Private methods**: Prefix with `_` (e.g., `_get_solution_comp()`)
- **Data interchange**: Use dicts with `{"value": X, "units": "..."}` structure for API outputs

## Architecture

The main class `phreeqcWTapi` in `src/phreeqcinwt/phreeqc_wt_api.py` uses **mixin inheritance**:

| Mixin | File | Purpose |
|-------|------|---------|
| `dataBaseManagment` | `core/data_base_utils.py` | Database loading, metadata management |
| `utilities` | `core/utility_functions.py` | Ion lookup, solution management, logging |
| `reaction_utils` | `core/reaction_utils.py` | Evaporation, pH adjust, mixing, reactions |
| `solution_utils` | `core/solution_state_utils.py` | Extract state: activities, osmotic pressure, scaling |

Database metadata lives in `databases/metadata/` as YAML files. Reactant definitions are in `databases/defined_reactants.yaml`.

## Build and Test

- **Python**: 3.10–3.13
- **Install (dev)**: `conda env create -f phreeqcinwt.yml && pip install -e .`
- **Install (pip)**: `pip install git+<repo_url>`
- **Build system**: setuptools + setuptools_scm (version from git tags)
- **Test**: `pytest --pyargs phreeqcinwt`
- **Format check**: `black --check .`
- **Dependencies**: `numpy`, `pyyaml`, `molmass`, `phreeqpy`

## Conventions

- PHREEQC commands are built as strings and executed via `run_string()` with optional command logging (`self.command_log`)
- Input compositions accept both scalar values and dicts with `value`/`formula`/`mw` keys
- The project targets **Windows** primarily (PHREEQC COM DLL); Linux/Mac support is aspirational
- Example scripts in `examples/` serve as informal integration tests
- Keep YAML metadata files in sync when modifying database handling logic
93 changes: 93 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Checks
on:
push:
branches: [main]
tags: ['*']
pull_request:
branches: [main]

concurrency:
group: ${{ github.head_ref }}
cancel-in-progress: true

env:
PYTEST_ADDOPTS: --color=yes
PIP_PROGRESS_BAR: "off"
KERNEL_NAME: phreeqcinwt
defaults:
run:
# -l: login shell, needed when using Conda:
shell: bash -l {0}

jobs:
code-formatting:
name: Check code is formatted (Black)
# OS and/or Python version don't make a difference, so we choose ubuntu and 3.12 as defaults
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Black
# unlike the other jobs, we don't need to install all the dev dependencies,
# but we still want to specify the Black version to use in requirements-dev.txt for local development
# so we extract the relevant line and pass it to a simple `pip install`
run: |
black_requirement="$(grep '^black==' requirements.txt)"
pip install "$black_requirement"
- name: Run Black to verify that the committed code is formatted
run: |
black --check .

pytest-dev:
name: pytest (${{ matrix.os }}/${{ matrix.python-version }}/${{ matrix.install-mode }}/${{ matrix.pytest_folders }})
runs-on: ${{ matrix.os-version }}
needs: [code-formatting]
strategy:
fail-fast: false
matrix:
install-mode:
- dev
- standard
python-version:
- '3.10'
- '3.11'
- '3.12'
- '3.13'
os:
- linux
# - mac
- win64
include:
- os: linux
os-version: ubuntu-24.04
# - os: mac
# os-version: macos-15
- os: win64
os-version: windows-2022
- install-mode: dev
python-version: "3.12" # choice of Python version is arbitrary among those in matrix
steps:
- if: matrix.install-mode == 'dev'
uses: actions/checkout@v4
- uses: conda-incubator/setup-miniconda@v3
with:
python-version: ${{ matrix.python-version }}
miniforge-version: latest
channels: conda-forge
activate-environment: phreeqcinwt
- if: matrix.install-mode == 'dev'
name: Install via conda.yml
run: |
conda env update --file phreeqcinwt.yml
pip list
- if: matrix.install-mode == 'standard'
name: Install (standard)
run: |
pip install "git+${{ format('{0}/{1}@{2}', github.server_url, github.repository, github.ref) }}"
- name: Run pytest
run: |
pip install pytest # ensure pytest is installed (should do nothing if already present from requirements-dev.txt)
pytest --pyargs phreeqcinwt

194 changes: 194 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# phreeqcinwt

A Python wrapper around [PHREEQC](https://www.usgs.gov/software/phreeqc-version-3) (via [phreeqpy](https://www.phreeqpy.com/)) for aqueous geochemistry modeling in water treatment applications.

**Key capabilities:**

- Build water compositions and compute solution properties (density, ionic strength, TDS, conductivity, osmotic pressure)
- Predict mineral scaling tendencies (saturation indices)
- Perform pH adjustment and chemical dosing simulations
- Calculate vapor pressures (gas-phase fugacities)
- Simulate mineral precipitation (form phases)
- Mix solutions at arbitrary ratios
- Directly modify solution composition, temperature, and pressure
- Compare results across multiple thermodynamic databases

## Prerequisites

- **Windows** (PHREEQC COM DLL; Linux SO is included, but MAC is not)
- **Conda** (recommended) or any Python 3.10–3.13 environment

## Installation

### Option A — Conda environment (recommended)

Create a dedicated environment with all dependencies:

```bash
conda env create -f phreeqcinwt.yml
conda activate phreeqcinwt
pip install -e .
```

To update an existing environment:

```bash
conda env update -n phreeqcinwt --file phreeqcinwt.yml
```

Or install into a different conda environment:

```bash
conda env update -n YOUR_ENV --file phreeqcinwt.yml
```

### Option B — pip only

```bash
pip install -e .
```

Dependencies (`numpy`, `pyyaml`, `molmass`, `phreeqpy`) are installed automatically.

## Bundled databases

The following PHREEQC databases are included in `databases/`:

| Database | Description |
|---|---|
| `phreeqc.dat` | Standard PHREEQC database |
| `pitzer.dat` | Pitzer ion-interaction model (high ionic strength) |
| `llnl.dat` | Lawrence Livermore National Laboratory database |
| `minteq.dat` | MINTEQA2 database |
| `minteq.v4.dat` | MINTEQA2 v4 database |
| `wateq4f.dat` | WATEQ4F database |
| `sit.dat` | Specific Ion Interaction Theory |
| `pitzer.dat` | Pitzer model |
| `frezchem.dat` | Cold-region geochemistry |
| `Amm.dat` | Ammonia-focused database |

## Quick start

```python
from phreeqcinwt.phreeqc_wt_api import phreeqcWTapi

# Initialize with a thermodynamic database
wt = phreeqcWTapi(database="phreeqc.dat")

# Define a water composition (values in g/L)
composition = {
"Na": 0.739,
"K": 0.009,
"Ca": 0.258,
"Mg": 0.090,
"Cl": 1.109,
"HCO3": 0.385,
"SO4": 1.011,
}

# Build the solution
result = wt.build_water_composition(
input_composition=composition,
charge_balance="Cl",
pH=7,
pe=4,
units="g/L",
pressure=1,
temperature=20,
)

# Inspect the solution state
state = wt.get_solution_state(report=True)
```

### pH adjustment

```python
titrant = wt.perform_reaction(
ph_adjust={"pH": 5.0, "reactant": "HCl"},
pressure=1,
report=True,
)
# titrant["HCl"]["value"] gives the required dose in mg/kgw
```

### Scaling tendencies

Scaling tendencies (saturation indices) are returned automatically in the
`build_water_composition` result:

```python
result["scaling_tendencies"]
# {"Calcite": {"value": 0.46, ...}, "Gypsum": {"value": -0.53, ...}, ...}
```

### Mineral precipitation

```python
precipitated = wt.form_phases(
phases=["Calcite", "Gypsum", "Aragonite"],
report=True,
)
# precipitated["Calcite"]["value"] gives moles precipitated per kgw
```

### Vapor pressure

```python
vp = wt.get_vapor_pressure(report=True)
# vp["H2O(g)"]["value"] -> partial pressure in atm
# vp["total_fugacity"]["value"] -> total vapor pressure in atm
```

### Mixing solutions

```python
wt = phreeqcWTapi(database="pitzer.dat")

wt.build_water_composition(
input_composition=water_a,
charge_balance="Cl", pH=7, pe=4,
units="g/kgw", pressure=1, temperature=20,
solution_number=1, water_mass=0.75,
)
wt.build_water_composition(
input_composition=water_b,
charge_balance="Cl", pH=8, pe=4,
units="g/kgw", pressure=1, temperature=20,
solution_number=2, water_mass=0.25,
)

wt.mix_solutions({1: 1, 2: 1}, new_solution_number=3)
mixed_state = wt.get_solution_state(report=True)
```

### Modifying a solution

Directly manipulate an existing solution's elemental totals (in moles),
temperature, or pressure using PHREEQC's `SOLUTION_MODIFY`:

```python
# Absolute: set Na to exactly 0.05 mol and change temperature to 40 °C
result = wt.solution_modify(absolute={"Na": 0.05}, temperature=40)

# Relative: add 0.01 mol Ca to the current amount
result = wt.solution_modify(relative={"Ca": 0.01})

# Both at once
result = wt.solution_modify(
absolute={"Na": 0.05},
relative={"Ca": 0.01},
pressure=2,
)
```

## Testing

```bash
pytest --pyargs phreeqcinwt
```

## Additional examples

See the `examples/` directory for more usage patterns including treatment trains,
database comparisons, density calculations, and osmotic pressure estimation.
29 changes: 0 additions & 29 deletions README.txt

This file was deleted.

6 changes: 1 addition & 5 deletions phreeqcinwt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@ channels:
- conda-forge
- defaults
dependencies:
- python=3.11*
- python=3.12*
- pip
- numpy
- pyyaml
- molmass
- pip:
- phreeqpy
- -r requirements.txt

2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
black==24.8.0
pytest-cov
--editable .
Loading
Loading