cooling-tower-chem
The water-stability and corrosion math for cooling towers, as a small, dependency-free Python library.
Water-treatment engineers screen cooling-tower and process water with a handful
of classical indices — Langelier, Ryznar, Puckorius, Larson-Skold — plus the
evaporation / blowdown / makeup balance that sets cycles of concentration.
Those formulas are scattered across textbooks and spreadsheets. cooling-tower-chem
puts them in one tested, documented, importable place.
Try it in your browser: the interactive water-stability calculator computes LSI, RSI, PSI, and CCPP live — no install required, and cross-checked against this library.
# From PyPI:
pip install cooling-tower-chem
# Or install the latest development version from source:
pip install git+https://github.com/Madhvansh/cooling-tower-chem
No third-party dependencies. Pure standard-library Python, fully type-hinted, tested on CPython 3.9–3.13.
Quick start
from cooling_tower_chem import WaterSample
# One analysis in, every index out.
sample = WaterSample(
ph=8.2,
temperature_c=32,
calcium_hardness=450, # mg/L as CaCO3
total_alkalinity=250, # mg/L as CaCO3
conductivity_us_cm=2400, # TDS is derived from this if not given
chloride=180, # mg/L (for the Larson-Skold index)
sulfate=120, # mg/L
)
print(sample.lsi()) # 1.38 -> scale-forming
print(sample.rsi()) # 5.44 -> heavy scale
report = sample.report()
print(report["lsi"]["description"])
# "LSI +1.38: scale-forming; calcium carbonate will tend to precipitate."
Prefer plain functions? Every index is available directly:
from cooling_tower_chem import (
langelier_saturation_index,
ryznar_stability_index,
puckorius_scaling_index,
larson_skold_index,
)
lsi = langelier_saturation_index(
ph=7.5, temperature_c=25, tds=400,
calcium_hardness=240, total_alkalinity=180,
) # -> +0.19
From the command line
Installing the package also gives you a ctchem command:
$ ctchem report --ph 8.2 --temp 32 --calcium 450 --alkalinity 250 \
--conductivity 2400 --chloride 180 --sulfate 120
pH of saturation (pHs): 6.821
LSI: 1.379 [scale_forming]
LSI +1.38: scale-forming; calcium carbonate will tend to precipitate.
RSI: 5.443 [severely_scale_forming]
RSI 5.44: heavy scale formation expected.
...
$ ctchem lsi --ph 7.5 --temp 25 --tds 400 --calcium 240 --alkalinity 180
LSI = +0.189
Add --json to any command for machine-readable output.
What's included
Stability & corrosion indices
| Function | Index | Reads as |
|---|---|---|
langelier_saturation_index |
LSI = pH − pHs | > 0 scaling, < 0 corrosive |
ryznar_stability_index |
RSI = 2·pHs − pH | < 6 scaling, > 7 corrosive |
puckorius_scaling_index |
PSI = 2·pHs − pH_eq | better fit for buffered recirculating water |
larson_skold_index |
(Cl⁻+SO₄²⁻)/alkalinity | corrosivity toward mild steel |
aggressiveness_index |
AI = pH + log₁₀(Ca·Alk) | AWWA aggressiveness screen |
stiff_davis_index |
S&DSI = pH − pHs (ionic-strength corrected) | LSI for high-salinity / brine water |
ph_of_saturation |
pHs | the shared building block |
Precipitation potential (CCPP)
Beyond the direction the indices give, calcium_carbonate_precipitation_potential
reports the quantity: the signed mass of CaCO₃ (mg/L as CaCO₃) that must
precipitate (+) or dissolve (−) to reach calcite saturation, solved for a
closed system with the Plummer & Busenberg (1982) equilibrium constants and a
Davies activity model. It reproduces Wojtowicz's (2001) worked examples, and is
also available on WaterSample.ccpp() and the ctchem ccpp CLI.
Cooling-tower water balance
cycles_of_concentration, evaporation_loss (energy balance), drift_loss,
blowdown_loss, makeup_water, cycles_from_flows,
water_saved_by_increasing_cycles, and TDS ↔ conductivity conversion.
The CoolingTower object ties the balance together and can project how makeup
water concentrates in the basin:
from cooling_tower_chem import CoolingTower, WaterSample
tower = CoolingTower(circulation_rate=1000, delta_t_c=6, cycles=5) # m3/h
print(tower.water_balance())
# {'evaporation': 10.2514, 'drift': 0.2, 'blowdown': 2.3629, 'makeup': 12.8143, 'cycles': 5}
makeup = WaterSample(ph=7.8, temperature_c=32, calcium_hardness=90,
total_alkalinity=60, conductivity_us_cm=400)
basin = tower.concentrated(makeup) # conservative species x cycles
print(round(makeup.lsi(), 2), "->", round(basin.lsi(), 2)) # -0.26 -> 1.07
Interpretation
interpret_lsi, interpret_rsi, interpret_psi, interpret_larson_skold,
interpret_aggressiveness each return a coarse Tendency and a one-line
explanation, so you can turn a number into an actionable message.
Unit conversions
Water data arrives in mixed units. as_caco3 / caco3_to_ion (plus named
calcium_as_caco3, magnesium_as_caco3, bicarbonate_as_caco3) convert between
mg/L-of-ion and mg/L-as-CaCO3; grains_per_gallon_to_mg_l and
celsius_to_fahrenheit (and inverses) cover the common US/metric gaps.
If your analysis is already in customary US units — temperature in °F and
hardness/alkalinity in grains per gallon (as CaCO₃) — skip the manual
conversions and use WaterSample.from_us_units:
from cooling_tower_chem import WaterSample
# 90 °F, 26.3 gpg calcium hardness, 14.6 gpg alkalinity (both as CaCO3).
sample = WaterSample.from_us_units(
ph=8.2,
temperature_f=90,
calcium_hardness_gpg=26.3,
total_alkalinity_gpg=14.6,
conductivity_us_cm=2400, # TDS/conductivity keep their usual units
)
print(round(sample.lsi(), 2)) # same result as the SI constructor
It converts °F and grains/gallon internally (via fahrenheit_to_celsius and
grains_per_gallon_to_mg_l) and returns an ordinary WaterSample, so every
index method behaves exactly as if you had built it in SI units.
Runnable examples
See the Examples section below, or browse
examples/ directly.
Examples
Runnable, dependency-free cookbook scripts in examples/ — each
prints a plain-text table (real output in the
examples README):
| Script | What it screens |
|---|---|
plant_water_screening.py |
batch LSI/RSI/PSI/CCPP + Larson-Skold risk table for a set of cooling-tower waters |
pool_spa_check.py |
the same indices at pool/spa chemistry, read with pool-practice LSI bands |
boiler_feedwater_check.py |
residual-hardness screen across a boiler pre-treatment train |
assess_water.py |
full index report for a single analysis |
optimize_cycles.py |
cycles-of-concentration sweep: makeup water vs. basin LSI |
For the theory behind the cycles sweep, see the Cycles of concentration doc — the COC mass balance and the water-savings vs. scaling-risk trade-off, worked through with the library.
Units & conventions
- Calcium hardness and total alkalinity: mg/L as CaCO₃
- TDS and ions (chloride, sulfate): mg/L
- Temperature: °C
- Flows: any consistent volumetric unit (m³/h, gpm, …) — results carry the same unit
Inputs that would break a logarithm (non-positive hardness, alkalinity, or TDS)
raise a clear ValueError rather than returning a silent fallback.
Why this exists
These indices are the daily bread of industrial water treatment, but the Python
ecosystem had no small, focused, well-tested home for them. cooling-tower-chem
was extracted from the physics engine of
TGF, a cooling-tower dosing-control project,
and hardened into a standalone library so any process-engineering or
water-treatment codebase can depend on it.
References
- Langelier, W. F. (1936). The Analytical Control of Anti-Corrosion Water Treatment. J. AWWA 28(10).
- Ryznar, J. W. (1944). A New Index for Determining Amount of Calcium Carbonate Scale Formed by a Water. J. AWWA 36(4).
- Puckorius, P. R. & Brooke, J. M. (1991). A New Practical Index for Calcium Carbonate Scale Prediction in Cooling Tower Systems. Corrosion 47(4).
- Larson, T. E. & Skold, R. V. (1958). Laboratory Studies Relating Mineral Quality of Water to Corrosion of Steel and Cast Iron. Corrosion 14(6).
The interpretation thresholds are the conventional screening bands from these sources; they are guides, not a substitute for site-specific engineering judgment.
Contributing
Issues and pull requests are welcome — see CONTRIBUTING.md. Good first contributions: additional well-referenced indices, further unit-conversion helpers, and more worked examples from the literature to expand the test suite.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cooling_tower_chem-0.1.1.tar.gz.
File metadata
- Download URL: cooling_tower_chem-0.1.1.tar.gz
- Upload date:
- Size: 36.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
edd81f68c0cd4601116aabe896d723b3bee0798a2a70635225a3b93fb8e8d62a
|
|
| MD5 |
d561535d58a521c0cef37b03d256b95c
|
|
| BLAKE2b-256 |
72b2be3f6559e6da28d6418dba827b54dae9839d395c114b6612b2889d0e1200
|
Provenance
The following attestation bundles were made for cooling_tower_chem-0.1.1.tar.gz:
Publisher:
publish.yml on Madhvansh/cooling-tower-chem
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cooling_tower_chem-0.1.1.tar.gz -
Subject digest:
edd81f68c0cd4601116aabe896d723b3bee0798a2a70635225a3b93fb8e8d62a - Sigstore transparency entry: 2252991387
- Sigstore integration time:
-
Permalink:
Madhvansh/cooling-tower-chem@d9f76d1567b82b52d73dd3c7dc2a6fbf8d4545fe -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Madhvansh
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d9f76d1567b82b52d73dd3c7dc2a6fbf8d4545fe -
Trigger Event:
release
-
Statement type:
File details
Details for the file cooling_tower_chem-0.1.1-py3-none-any.whl.
File metadata
- Download URL: cooling_tower_chem-0.1.1-py3-none-any.whl
- Upload date:
- Size: 29.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57935244f4b75fe0a301c0a34e24c8fdce5e7177c42ce9bcebd753154f7212a9
|
|
| MD5 |
81fbd962d7e5ef3391cc123528498de5
|
|
| BLAKE2b-256 |
9ced98588bd26b7e1e2a075e0102afe98a94ce77b55e10489f84d9d26874a97e
|
Provenance
The following attestation bundles were made for cooling_tower_chem-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on Madhvansh/cooling-tower-chem
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cooling_tower_chem-0.1.1-py3-none-any.whl -
Subject digest:
57935244f4b75fe0a301c0a34e24c8fdce5e7177c42ce9bcebd753154f7212a9 - Sigstore transparency entry: 2252991609
- Sigstore integration time:
-
Permalink:
Madhvansh/cooling-tower-chem@d9f76d1567b82b52d73dd3c7dc2a6fbf8d4545fe -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Madhvansh
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d9f76d1567b82b52d73dd3c7dc2a6fbf8d4545fe -
Trigger Event:
release
-
Statement type: