psdm 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. psdm-0.1.0/.gitattributes +2 -0
  2. psdm-0.1.0/.gitignore +15 -0
  3. psdm-0.1.0/.readthedocs.yaml +17 -0
  4. psdm-0.1.0/CHANGELOG.md +47 -0
  5. psdm-0.1.0/CONTRIBUTING.md +31 -0
  6. psdm-0.1.0/LICENSE +27 -0
  7. psdm-0.1.0/PKG-INFO +118 -0
  8. psdm-0.1.0/README.md +89 -0
  9. psdm-0.1.0/benchmarks/report.html +197 -0
  10. psdm-0.1.0/benchmarks/results.json +21193 -0
  11. psdm-0.1.0/docs/api.md +94 -0
  12. psdm-0.1.0/docs/changelog.md +2 -0
  13. psdm-0.1.0/docs/conf.py +33 -0
  14. psdm-0.1.0/docs/design.md +116 -0
  15. psdm-0.1.0/docs/getting-started.md +83 -0
  16. psdm-0.1.0/docs/guide.md +90 -0
  17. psdm-0.1.0/docs/index.md +15 -0
  18. psdm-0.1.0/docs/model.md +64 -0
  19. psdm-0.1.0/examples/Example_Multi.xlsx +0 -0
  20. psdm-0.1.0/examples/Example_TCE.xlsx +0 -0
  21. psdm-0.1.0/pyproject.toml +82 -0
  22. psdm-0.1.0/scripts/benchmark.py +1035 -0
  23. psdm-0.1.0/src/psdm/__init__.py +31 -0
  24. psdm-0.1.0/src/psdm/capacity.py +279 -0
  25. psdm-0.1.0/src/psdm/collocation.py +144 -0
  26. psdm-0.1.0/src/psdm/correlations.py +70 -0
  27. psdm-0.1.0/src/psdm/fitting.py +240 -0
  28. psdm-0.1.0/src/psdm/fouling.py +123 -0
  29. psdm-0.1.0/src/psdm/fullscale.py +455 -0
  30. psdm-0.1.0/src/psdm/io.py +212 -0
  31. psdm-0.1.0/src/psdm/isotherms.py +128 -0
  32. psdm-0.1.0/src/psdm/model.py +193 -0
  33. psdm-0.1.0/src/psdm/plotting.py +55 -0
  34. psdm-0.1.0/src/psdm/properties.py +32 -0
  35. psdm-0.1.0/src/psdm/py.typed +0 -0
  36. psdm-0.1.0/src/psdm/solver.py +344 -0
  37. psdm-0.1.0/src/psdm/uncertainty.py +193 -0
  38. psdm-0.1.0/src/psdm/units.py +106 -0
  39. psdm-0.1.0/tests/conftest.py +28 -0
  40. psdm-0.1.0/tests/data/addesigns_test.xlsx +0 -0
  41. psdm-0.1.0/tests/data/reference/addesigns_single.csv +350 -0
  42. psdm-0.1.0/tests/data/reference/capacity.csv +13 -0
  43. psdm-0.1.0/tests/data/reference/capacity_cases.csv +127 -0
  44. psdm-0.1.0/tests/data/reference/dsfit.csv +2 -0
  45. psdm-0.1.0/tests/data/reference/dsfit_effluent.csv +89 -0
  46. psdm-0.1.0/tests/data/reference/generate.py +91 -0
  47. psdm-0.1.0/tests/data/reference/generate_capacity.py +68 -0
  48. psdm-0.1.0/tests/data/reference/generate_uncertainty_dsfit.py +90 -0
  49. psdm-0.1.0/tests/data/reference/multi_organic_free.csv +302 -0
  50. psdm-0.1.0/tests/data/reference/multi_rhine.csv +302 -0
  51. psdm-0.1.0/tests/data/reference/uncertainty.csv +176 -0
  52. psdm-0.1.0/tests/test_building_blocks.py +122 -0
  53. psdm-0.1.0/tests/test_capacity.py +99 -0
  54. psdm-0.1.0/tests/test_fullscale.py +145 -0
  55. psdm-0.1.0/tests/test_io_isotherms_fitting.py +93 -0
  56. psdm-0.1.0/tests/test_solver.py +122 -0
  57. psdm-0.1.0/tests/test_uncertainty_dsfit.py +78 -0
  58. psdm-0.1.0/uv.lock +2770 -0
@@ -0,0 +1,2 @@
1
+ * text=auto eol=lf
2
+ *.xlsx binary
psdm-0.1.0/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .coverage
10
+ coverage.xml
11
+ htmlcov/
12
+ docs/_build/
13
+ docs/api/generated/
14
+ .DS_Store
15
+ ~$*.xlsx
@@ -0,0 +1,17 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-24.04
5
+ tools:
6
+ python: "3.12"
7
+ jobs:
8
+ create_environment:
9
+ - asdf plugin add uv
10
+ - asdf install uv latest
11
+ - asdf global uv latest
12
+ install:
13
+ - UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --frozen --group docs
14
+
15
+ sphinx:
16
+ configuration: docs/conf.py
17
+ fail_on_warning: true
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [semantic versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-10-14
10
+
11
+ First release.
12
+
13
+ ### Added
14
+
15
+ - `psdm.simulate`: multi-component PSDM with NOM fouling, rewritten from the US EPA code.
16
+ - `psdm.Column` and `psdm.Compound` input objects with unit conversion.
17
+ - `psdm.fit_freundlich` and `psdm.fit_isotherm` for parameter estimation.
18
+ - `psdm.io.read_excel` for the EPA PSDM Excel template.
19
+ - Validation tests against AdDesignS and the original EPA implementation.
20
+ - `scripts/benchmark.py` and `benchmarks/report.html`: case-by-case comparison with the
21
+ original EPA PSDM code (11 cases).
22
+ - `psdm.estimate_capacity`: Freundlich `K` from breakthrough data for an assumed `1/n`
23
+ (upstream `__calculate_capacity`), with breakthrough detection and logistic/linear
24
+ extrapolation. Validated against the EPA code.
25
+ - `psdm.model_uncertainty`: uncertainty bands from ± perturbations of capacity, `K`, `1/n`,
26
+ influent concentration, mass, flow rate, `kf`, `dp` and `ds` (upstream `model_uncertainty`).
27
+ - `psdm.fit_surface_diffusivity`: grid search plus refinement for `ds` (upstream
28
+ `run_psdm_dsfit`).
29
+ - `psdm.fullscale`: `mesh_diameter`, `build_large_scale_column`, `rssct_design`,
30
+ `specific_throughput`, `replacement_interval` (2024 US EPA PFAS MCLs and hazard index) and
31
+ `staggered_bed_interval` (upstream `PSDM_extra.py` and `PSDM_tools.py`).
32
+
33
+ ### Fixed relative to the EPA code
34
+
35
+ - The surface-diffusivity reference includes the Freundlich `K` term.
36
+ - The PFBS term is added to the hazard index in the replacement interval.
37
+ - Longer EBCTs in `specific_throughput` enlarge the bed instead of shrinking it.
38
+ - RSSCT particle diameters given with units are converted correctly.
39
+
40
+ ### Still not ported
41
+
42
+ - Lead–lag configurations (`lead_lag_class.py`, needs the `ixpy` package).
43
+ - The equilibrium column model (`ecm.py`).
44
+ - `run_all_smart` fouling-factor estimates and `break_type='force'` breakthrough input.
45
+
46
+ [Unreleased]: https://github.com/zohoMatt/psdm/compare/v0.1.0...HEAD
47
+ [0.1.0]: https://github.com/zohoMatt/psdm/releases/tag/v0.1.0
@@ -0,0 +1,31 @@
1
+ # Contributing
2
+
3
+ Development uses [uv](https://docs.astral.sh/uv/).
4
+
5
+ ```bash
6
+ uv sync # create .venv with the package and dev tools
7
+ uv run pytest # tests
8
+ uv run ruff check . # lint
9
+ uv run ruff format . # format
10
+ uv run --group docs sphinx-build -W -b html docs docs/_build/html # docs
11
+ ```
12
+
13
+ ## Reference data
14
+
15
+ `tests/data/reference/*.csv` are breakthrough curves computed with the original US EPA code
16
+ (`Water_Treatment_Models` commit `3f514c2`). To regenerate them, check out that repository and
17
+ run
18
+
19
+ ```bash
20
+ uv run --no-project --with numpy --with scipy --with pandas --with openpyxl --with matplotlib \
21
+ python tests/data/reference/generate.py <Water_Treatment_Models>/PSDM tests/data/reference
22
+ ```
23
+
24
+ `tests/data/addesigns_test.xlsx` holds AdDesignS output for the same case and is the
25
+ independent check.
26
+
27
+ ## Releasing
28
+
29
+ 1. Update `__version__` in `src/psdm/__init__.py` and the changelog.
30
+ 2. Commit, tag `vX.Y.Z`, push, and publish a GitHub release for the tag.
31
+ 3. The `Release` workflow tests, builds and uploads to PyPI via trusted publishing.
psdm-0.1.0/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matt Zuo
4
+
5
+ This project is a rewrite of the Python PSDM code by Jonathan Burkhardt, Levi Haupert,
6
+ Boris Datsov and colleagues at the US Environmental Protection Agency
7
+ (https://github.com/USEPA/Water_Treatment_Models), itself released under the MIT License,
8
+ and is based on the AdDesignS(TM) PSDM by D.R. Hokanson, D.W. Hand, J.C. Crittenden,
9
+ T.N. Rogers and E.J. Oman (Michigan Technological University).
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
psdm-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.5
2
+ Name: psdm
3
+ Version: 0.1.0
4
+ Summary: Pore and Surface Diffusion Model (PSDM) for fixed-bed granular activated carbon adsorbers
5
+ Project-URL: Documentation, https://psdm.readthedocs.io
6
+ Project-URL: Source, https://github.com/zohoMatt/psdm
7
+ Project-URL: Issues, https://github.com/zohoMatt/psdm/issues
8
+ Project-URL: Changelog, https://github.com/zohoMatt/psdm/blob/main/CHANGELOG.md
9
+ Author: Matt Zuo
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: GAC,PFAS,PSDM,activated carbon,adsorption,water treatment
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: numpy>=1.23
22
+ Requires-Dist: pandas>=1.5
23
+ Requires-Dist: scipy>=1.9
24
+ Provides-Extra: excel
25
+ Requires-Dist: openpyxl>=3.0; extra == 'excel'
26
+ Provides-Extra: plot
27
+ Requires-Dist: matplotlib>=3.6; extra == 'plot'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # psdm
31
+
32
+ [![CI](https://github.com/zohoMatt/psdm/actions/workflows/ci.yml/badge.svg)](https://github.com/zohoMatt/psdm/actions/workflows/ci.yml)
33
+ [![PyPI](https://img.shields.io/pypi/v/psdm.svg)](https://pypi.org/project/psdm/)
34
+ [![Docs](https://readthedocs.org/projects/psdm/badge/?version=latest)](https://psdm.readthedocs.io)
35
+
36
+ The **Pore and Surface Diffusion Model (PSDM)** for fixed-bed granular activated carbon (GAC)
37
+ adsorbers, as a small, tested and documented Python package.
38
+
39
+ - Multi-component competitive adsorption (ideal adsorbed solution theory, Freundlich isotherms)
40
+ - Film, pore and surface diffusion with built-in correlations or user-supplied coefficients
41
+ - Natural organic matter fouling for the AdDesignS water types and compound classes, including PFAS
42
+ - Time-varying influent, any time / concentration / flow units
43
+ - Fitting Freundlich `K` and `1/n` to column data; batch isotherm fitting
44
+ - Reader for the US EPA PSDM Excel input template
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install psdm # core
50
+ pip install "psdm[excel]" # + Excel input files
51
+ pip install "psdm[plot]" # + plotting
52
+ ```
53
+
54
+ ## Quick start
55
+
56
+ ```python
57
+ import pandas as pd
58
+ import psdm
59
+
60
+ column = psdm.Column.from_units(
61
+ length=(2.765, "m"), diameter=(10, "ft"), mass=(20000, "lb"), flow_rate=(567, "gpm"),
62
+ particle_radius=(0.513, "mm"), particle_density=(0.803, "g/mL"), particle_porosity=0.641,
63
+ )
64
+ tce = psdm.Compound("TCE", mw=131.39, molar_volume=102.0, k=5026.04, n_inv=0.43)
65
+ influent = pd.DataFrame({"TCE": [50.0, 50.0]}, index=[0.0, 174.0]) # days, mg/L
66
+
67
+ result = psdm.simulate(column, tce, influent, conc_unit="mg/L", water="Rhine")
68
+ result.effluent # DataFrame: effluent concentration vs. time
69
+ result([30, 60, 90]) # interpolated at given times
70
+ ```
71
+
72
+ Reading the EPA Excel template:
73
+
74
+ ```python
75
+ from psdm.io import read_excel
76
+
77
+ data = read_excel("Example_Multi.xlsx")
78
+ result = data.simulate(nr=4, nz=8, ne=2)
79
+ ```
80
+
81
+ See the [documentation](https://psdm.readthedocs.io) for the model description, the full API
82
+ and more examples.
83
+
84
+ ## Validation
85
+
86
+ psdm is a rewrite of the original
87
+ [US EPA Python PSDM](https://github.com/USEPA/Water_Treatment_Models/tree/3f514c2/PSDM)
88
+ (commit `3f514c2`). It solves the same model equations: orthogonal-collocation discretisation,
89
+ film, pore and surface diffusion, and the AdDesignS fouling correlations. It integrates them with
90
+ the same ODE method, scipy's BDF solver with the same tolerances and Jacobian sparsity pattern, and
91
+ reproduces the original's results.
92
+
93
+ - **Against the original EPA code.** The comparison covers 11 cases: 7 single-compound AdDesignS
94
+ cases (Rhine, Organic Free and Portage water at 5–50 mg/L), 2 multi-component cases from
95
+ `Example_Multi.xlsx` and 2 time-varying influent cases from `Example_TCE.xlsx`. The largest
96
+ difference anywhere on a breakthrough curve is **0.33 % of the influent concentration C0**, and
97
+ the RMS difference is at most 0.06 % of C0.
98
+ - **Against AdDesignS.** For the 7 single-compound cases with AdDesignS output, psdm stays within
99
+ 0.57 % of C0 of AdDesignS. The original EPA code stays within 0.62 %.
100
+ - **Test suite.** The tests check psdm against reference curves generated with the EPA code and
101
+ against the AdDesignS output, with a tolerance of 0.5 % of C0.
102
+
103
+ The case-by-case comparison, with overlaid breakthrough curves, is in
104
+ [`benchmarks/report.html`](benchmarks/report.html) (download it and open it in a browser). It
105
+ is generated by [`scripts/benchmark.py`](scripts/benchmark.py).
106
+
107
+ ## Credits
108
+
109
+ This package is a rewrite of the Python PSDM from the US EPA
110
+ [Water Treatment Models](https://github.com/USEPA/Water_Treatment_Models) repository
111
+ (J. Burkhardt, L. Haupert, B. Datsov et al.), which converted the Fortran PSDM in AdDesignS™
112
+ (D.R. Hokanson, D.W. Hand, J.C. Crittenden, T.N. Rogers, E.J. Oman, Michigan Technological
113
+ University) to Python. It is not affiliated with or endorsed by the US EPA or Michigan
114
+ Technological University.
115
+
116
+ ## License
117
+
118
+ MIT — see [LICENSE](LICENSE).
psdm-0.1.0/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # psdm
2
+
3
+ [![CI](https://github.com/zohoMatt/psdm/actions/workflows/ci.yml/badge.svg)](https://github.com/zohoMatt/psdm/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/psdm.svg)](https://pypi.org/project/psdm/)
5
+ [![Docs](https://readthedocs.org/projects/psdm/badge/?version=latest)](https://psdm.readthedocs.io)
6
+
7
+ The **Pore and Surface Diffusion Model (PSDM)** for fixed-bed granular activated carbon (GAC)
8
+ adsorbers, as a small, tested and documented Python package.
9
+
10
+ - Multi-component competitive adsorption (ideal adsorbed solution theory, Freundlich isotherms)
11
+ - Film, pore and surface diffusion with built-in correlations or user-supplied coefficients
12
+ - Natural organic matter fouling for the AdDesignS water types and compound classes, including PFAS
13
+ - Time-varying influent, any time / concentration / flow units
14
+ - Fitting Freundlich `K` and `1/n` to column data; batch isotherm fitting
15
+ - Reader for the US EPA PSDM Excel input template
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install psdm # core
21
+ pip install "psdm[excel]" # + Excel input files
22
+ pip install "psdm[plot]" # + plotting
23
+ ```
24
+
25
+ ## Quick start
26
+
27
+ ```python
28
+ import pandas as pd
29
+ import psdm
30
+
31
+ column = psdm.Column.from_units(
32
+ length=(2.765, "m"), diameter=(10, "ft"), mass=(20000, "lb"), flow_rate=(567, "gpm"),
33
+ particle_radius=(0.513, "mm"), particle_density=(0.803, "g/mL"), particle_porosity=0.641,
34
+ )
35
+ tce = psdm.Compound("TCE", mw=131.39, molar_volume=102.0, k=5026.04, n_inv=0.43)
36
+ influent = pd.DataFrame({"TCE": [50.0, 50.0]}, index=[0.0, 174.0]) # days, mg/L
37
+
38
+ result = psdm.simulate(column, tce, influent, conc_unit="mg/L", water="Rhine")
39
+ result.effluent # DataFrame: effluent concentration vs. time
40
+ result([30, 60, 90]) # interpolated at given times
41
+ ```
42
+
43
+ Reading the EPA Excel template:
44
+
45
+ ```python
46
+ from psdm.io import read_excel
47
+
48
+ data = read_excel("Example_Multi.xlsx")
49
+ result = data.simulate(nr=4, nz=8, ne=2)
50
+ ```
51
+
52
+ See the [documentation](https://psdm.readthedocs.io) for the model description, the full API
53
+ and more examples.
54
+
55
+ ## Validation
56
+
57
+ psdm is a rewrite of the original
58
+ [US EPA Python PSDM](https://github.com/USEPA/Water_Treatment_Models/tree/3f514c2/PSDM)
59
+ (commit `3f514c2`). It solves the same model equations: orthogonal-collocation discretisation,
60
+ film, pore and surface diffusion, and the AdDesignS fouling correlations. It integrates them with
61
+ the same ODE method, scipy's BDF solver with the same tolerances and Jacobian sparsity pattern, and
62
+ reproduces the original's results.
63
+
64
+ - **Against the original EPA code.** The comparison covers 11 cases: 7 single-compound AdDesignS
65
+ cases (Rhine, Organic Free and Portage water at 5–50 mg/L), 2 multi-component cases from
66
+ `Example_Multi.xlsx` and 2 time-varying influent cases from `Example_TCE.xlsx`. The largest
67
+ difference anywhere on a breakthrough curve is **0.33 % of the influent concentration C0**, and
68
+ the RMS difference is at most 0.06 % of C0.
69
+ - **Against AdDesignS.** For the 7 single-compound cases with AdDesignS output, psdm stays within
70
+ 0.57 % of C0 of AdDesignS. The original EPA code stays within 0.62 %.
71
+ - **Test suite.** The tests check psdm against reference curves generated with the EPA code and
72
+ against the AdDesignS output, with a tolerance of 0.5 % of C0.
73
+
74
+ The case-by-case comparison, with overlaid breakthrough curves, is in
75
+ [`benchmarks/report.html`](benchmarks/report.html) (download it and open it in a browser). It
76
+ is generated by [`scripts/benchmark.py`](scripts/benchmark.py).
77
+
78
+ ## Credits
79
+
80
+ This package is a rewrite of the Python PSDM from the US EPA
81
+ [Water Treatment Models](https://github.com/USEPA/Water_Treatment_Models) repository
82
+ (J. Burkhardt, L. Haupert, B. Datsov et al.), which converted the Fortran PSDM in AdDesignS™
83
+ (D.R. Hokanson, D.W. Hand, J.C. Crittenden, T.N. Rogers, E.J. Oman, Michigan Technological
84
+ University) to Python. It is not affiliated with or endorsed by the US EPA or Michigan
85
+ Technological University.
86
+
87
+ ## License
88
+
89
+ MIT — see [LICENSE](LICENSE).