A scientific Python library for analysing postcombustion CO₂ capture processes based on monoethanolamine (MEA) absorption.
meapy provides a clean, tested, and typed API for the core calculations performed when commissioning and evaluating MEA based carbon capture pilot plants , heat exchanger thermal analysis, packed column mass transfer, and pump commissioning , styled after scientific packages like pvlib and scipy.
A research library shipped with the rigour of a production service:
- Science: LMTD/NTU heat exchanger analysis, K_OGa packed column profiling, and exponential pump commissioning models, all benchmarked against an Imperial College pilot plant.
- Quality:
mypy --strict, ruff lint+format, ≥ 90 % coverage gate, 124 unit tests across 8 modules, Google style docstrings on every public symbol. - Cross platform CI: Python 3.10/3.11/3.12 × Ubuntu/macOS test matrix, Codecov upload, every action pinned to a full SHA.
- Containerised: multi stage
Dockerfile(non root, healthcheck, < 150 MB), plusDockerfile.lambdafor AWS andDockerfile.cloudrunfor GCP. - Supply chain hardened: released images are signed with cosign (keyless OIDC), ship an SPDX SBOM as a cosign attestation, and carry SLSA build provenance.
- Multi cloud IaC: Terraform modules for AWS Lambda (arm64 container, ECR, IAM least priv, Function URL or API Gateway v2) and GCP Cloud Run (Artifact Registry, scale to zero).
- Keyless deploys: GitHub Actions assumes a least privileged AWS role via OIDC; no long lived access keys.
- Static analysis: weekly CodeQL
security-and-qualityqueries, Dependabot across 4 ecosystems, CODEOWNERS gating sensitive paths. - Trusted publishing: PyPI releases via PEP 740 trusted publisher, validated through TestPyPI before promotion.
See DEPLOYMENT.md for the full release runbook and architecture diagram.
| Module | What it does |
|---|---|
meapy.heat_transfer |
LMTD · overall heat transfer coefficient U · thermal efficiency · NTU effectiveness for plate heat exchangers |
meapy.mass_transfer |
K_OGa profiles · NTU_OG · H_OG · composition profiling along packed absorber columns |
meapy.pump |
Exponential & linear regression commissioning models · safe operating speed · alarm threshold checking |
meapy.constants |
Curated MEA thermophysical properties (DOW, 2003) · pilot plant geometry · alarm setpoints |
meapy.utils |
Unit conversions · steady state detection · descriptive statistics |
- 100 % type annotated: full
mypy --strictcompliance - Google-style docstrings on every public function and class
- ≥ 90 % test coverage across unit and integration suites
- No magic numbers: every hard coded value lives in
constants.pywith a source citation - Logging, not print - configure output with standard
logging
meapy was developed from the Imperial College London MEA carbon capture pilot plant analysis (Hale, 2025). The plant operates a 15 % (w/w) MEA solution in a counter current packed absorber (E101) and stripper (E100), with plate heat exchangers C100 (intercooler) and C200 (trim cooler), and pump J100 driving lean MEA recirculation.
CO₂-lean N₂ out ↑
┌───────┐
│ E101 │ ← Lean MEA in (from C200)
│ Absorb│
│ er │
└───────┘
Rich MEA ↓ ↑ CO₂ + N₂ in
┌───────┐
│ C100 │ intercooler (MEA↔MEA)
└───────┘
┌───────┐
│ C200 │ trim cooler (MEA↔water)
└───────┘
┌───────┐
│ E100 │ stripper
│ J100 │ pump
└───────┘
From PyPI (once published):
pip install meapyFrom source (recommended for development):
git clone https://github.com/defnalk/meapy.git
cd meapy
pip install -e ".[dev]"Requirements: Python 3.10+, NumPy ≥ 1.24, SciPy ≥ 1.11
With Docker (no local Python install required):
# Build the multi-stage production image (non-root, healthcheck, < 150 MB)
docker build -t meapy:latest .
# Run the CLI
docker run --rm meapy:latest --version
docker run --rm meapy:latest --help
# Drop into a Python REPL with meapy importable
docker run --rm -it --entrypoint python meapy:latest
# Mount your analysis scripts and run them inside the container
docker run --rm -v "$PWD":/work -w /work meapy:latest python my_analysis.pyFor local development with a hot reload bind mount and a separate test service, use the bundled Compose file:
docker compose up meapy-dev # interactive dev container
docker compose run --rm meapy-test # one-shot pytest runSpecialised images are available for serverless deployments:
Dockerfile.lambda (AWS Lambda) and Dockerfile.cloudrun (GCP Cloud Run,
FastAPI/uvicorn). See DEPLOYMENT.md for the full deploy
recipes.
from meapy.heat_transfer import analyse_exchanger
from meapy.constants import MEAProperties
result = analyse_exchanger(
mea_flow_kg_h=900,
cp_mea_j_kg_k=MEAProperties.CP_15_PCT_AT_40C, # 3980 J/(kg·K)
t_mea_in_c=85.0,
t_mea_out_c=42.0,
utility_flow_kg_h=800,
cp_utility_j_kg_k=MEAProperties.CP_15_PCT_AT_40C,
t_utility_in_c=30.0,
t_utility_out_c=68.0,
area_m2=0.30,
)
print(f"U = {result['u_kw_m2_k']:.2f} kW/(m²·K)")
print(f"Efficiency = {result['efficiency']:.3f}")
print(f"LMTD = {result['lmtd_k']:.2f} K")
# U = 1.74 kW/(m²·K)
# Efficiency = 0.961
# LMTD = 19.03 Kimport numpy as np
from meapy.pump import (
fit_exponential_level_model,
fit_linear_flowrate_model,
safe_pump_speed,
)
# Data from LT101 and FT103 transmitters
speeds = np.array([10, 20, 30, 40, 50, 53], dtype=float)
levels = np.array([80.1, 65.3, 52.0, 38.7, 24.1, 15.0], dtype=float)
flows = np.array([177.5, 365.2, 552.8, 740.4, 927.0, 980.1], dtype=float)
level_model = fit_exponential_level_model(speeds, levels)
flow_model = fit_linear_flowrate_model(speeds, flows)
result = safe_pump_speed(level_model, flow_model)
print(result)
# ── Pump Commissioning Result ────────────────────────────
# Safe operating speed : 53.0 %
# Predicted MEA level : 15.1 %
# Predicted flowrate : 980.1 kg/h
# Level alarm at : 62.8 %
# Flow alarm at : 54.2 %
# Limiting constraint : flowimport numpy as np
from meapy.mass_transfer import composition_profile, koga_profile
from meapy.constants import PlantGeometry
from meapy.utils import kg_h_to_mol_s, summarise_array
# Six sampling ports at fixed heights
heights_m = PlantGeometry.SAMPLING_HEIGHTS_M # [0.5, 1.5, ..., 5.5] m
co2_vol_pct = [14.0, 11.5, 9.0, 6.0, 3.5, 1.5] # gas analyser readings
_, y_co2 = composition_profile(heights_m, co2_vol_pct)
inert_flow_mol_s = kg_h_to_mol_s(500.0, 28.014) # N₂ at 500 kg/h
cross_section_m2 = PlantGeometry.COLUMN_CROSS_SECTION_M2
koga = koga_profile(inert_flow_mol_s, cross_section_m2, heights_m, list(y_co2))
stats = summarise_array(koga[~np.isnan(koga)])
print(f"Mean K_OGa = {stats['mean']:.1f} kmol/(m³·h)")
print(f"Range = [{stats['min']:.1f}, {stats['max']:.1f}] kmol/(m³·h)")| Function | Signature | Returns |
|---|---|---|
stream_duty |
(mass_flow_kg_s, cp_j_kg_k, t_in_c, t_out_c) |
float W |
energy_loss |
(q_hot_w, q_cold_w) |
float W |
lmtd_counter_current |
(t_hot_in, t_hot_out, t_cold_in, t_cold_out) |
float K |
lmtd_co_current |
(t_hot_in, t_hot_out, t_cold_in, t_cold_out) |
float K |
overall_heat_transfer_coefficient |
(q_w, lmtd_k, area_m2) |
float W/(m²·K) |
efficiency |
(q_cold_w, q_hot_w) |
float |
effectiveness |
(q_actual_w, c_hot, c_cold, t_hot_in, t_cold_in) |
float |
ntu |
(u_w_m2_k, area_m2, c_min_w_k) |
float |
analyse_exchanger |
(**kwargs) |
dict[str, float] |
| Function | Returns |
|---|---|
mole_fraction_to_ratio(y) |
mole ratio Y |
mole_ratio_to_fraction(Y) |
mole fraction y |
koga_from_flux(...) |
K_OGa in mol/(m³·s) |
koga_profile(...) |
ndarray in kmol/(m³·h) |
composition_profile(...) |
(heights, y_co2) ndarrays |
ntu_og(y_bottom, y_top) |
dimensionless NTU_OG |
hog(koga, flux) |
H_OG in m |
absorption_factor(L, G, m) |
dimensionless A |
| Class / Function | Description |
|---|---|
ExponentialLevelModel |
Frozen dataclass: l0, k, r_squared; methods predict(), invert() |
LinearFlowModel |
Frozen dataclass: slope, intercept, r_squared; methods predict(), invert() |
PumpCommissioningResult |
Result dataclass with safe_speed_pct, limiting_constraint, etc. |
fit_exponential_level_model(speeds, levels) |
Fit L = L₀·exp(k·PS) by log linearisation |
fit_linear_flowrate_model(speeds, flows) |
Fit F = slope·PS + intercept by OLS |
safe_pump_speed(level_model, flow_model) |
Determine binding constraint and safe speed |
Full API documentation: meapy.readthedocs.io
# All tests with coverage report
make test
# Unit tests only (fast)
pytest tests/unit/
# Integration tests only
pytest tests/integration/ -m integration
# With verbose output
pytest -v --tb=short# Install in editable mode with all dev extras
make install
# Lint and format
make lint
# Type-check
mypy src/meapy
# Run examples
python examples/heat_exchanger_analysis.py
python examples/pump_commissioning.pymeapy/
├── src/meapy/
│ ├── __init__.py # Package entry point and version
│ ├── constants.py # MEA properties, plant geometry, alarm limits
│ ├── heat_transfer.py # LMTD, U, efficiency, effectiveness
│ ├── mass_transfer.py # K_OGa, composition profiling, NTU
│ ├── pump.py # Commissioning models and safe speed
│ └── utils.py # Unit conversions, steady state detection
├── tests/
│ ├── conftest.py # Shared fixtures
│ ├── unit/
│ │ ├── test_heat_transfer.py
│ │ ├── test_mass_transfer.py
│ │ ├── test_pump.py
│ │ └── test_utils.py
│ └── integration/
│ └── test_workflow.py
├── examples/
│ ├── heat_exchanger_analysis.py
│ └── pump_commissioning.py
├── docs/
├── infra/ # Terraform — AWS Lambda + ECR + IAM + OIDC
│ ├── main.tf variables.tf lambda.tf api_gateway.tf
│ ├── outputs.tf oidc.tf backend.tf deploy.sh
│ └── cloudrun/main.tf # GCP Cloud Run alternative
├── Dockerfile # Multi-stage production image (non-root)
├── Dockerfile.lambda # AWS Lambda container image
├── Dockerfile.cloudrun # GCP Cloud Run image (FastAPI/uvicorn)
├── docker-compose.yml # meapy-dev / meapy-test services
├── .github/
│ ├── workflows/ci.yml # lint + test matrix + Docker smoke + Scout
│ ├── workflows/release.yml # PyPI (OIDC) + GHCR + cosign + SBOM + GH release
│ ├── workflows/codeql.yml # weekly static analysis
│ ├── dependabot.yml # actions / pip / docker / terraform
│ └── CODEOWNERS
├── .pre-commit-config.yaml # ruff + mypy + terraform fmt/validate
├── DEPLOYMENT.md # Release runbook + Mermaid architecture diagram
├── pyproject.toml
├── Makefile
├── CHANGELOG.md
├── CONTRIBUTING.md
└── LICENSE
| Concern | How meapy handles it |
|---|---|
| Builds | Multi stage Dockerfile, tests run inside the builder, image < 150 MB |
| CI | py3.10/3.11/3.12 × Ubuntu/macOS, ruff + mypy + pytest, Docker smoke + Scout CVE scan |
| Releases | Tag v* → TestPyPI → PyPI (OIDC) → multi arch GHCR → signed GitHub Release |
| Image signing | cosign sign --yes (keyless, Sigstore Fulcio/Rekor) on every released tag |
| SBOM | SPDX JSON via Syft, attached as a cosign attest --type spdxjson predicate |
| AWS deploy | Container Lambda on Graviton, ECR with 5 image retention, Function URL or API Gateway v2 |
| GCP deploy | Cloud Run v2, Artifact Registry, scale to zero, public via IAM binding |
| Secrets | Zero hardcoded credentials: PyPI uses OIDC, AWS uses OIDC trust role, GHCR uses GITHUB_TOKEN |
| Security scans | CodeQL security-and-quality, Docker Scout (critical/high), Dependabot weekly |
| Branch hygiene | CODEOWNERS for infra/ and .github/; ready for branch protection rulesets |
Verify a published image yourself:
cosign verify ghcr.io/defnalk/meapy:latest \
--certificate-identity-regexp "https://github.com/defnalk/meapy/.*" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com- DOW Chemical (2003). MEA product data and thermophysical properties.
- Engineering Toolbox (2003). Overall heat transfer coefficients for plate heat exchangers.
- Hale, R. (2025). Imperial College London MEA Carbon Capture Pilot Plant Manual.
- Treybal, R. E. (1981). Mass Transfer Operations (3rd ed.). McGraw-Hill.
- Coulson, J. M., & Richardson, J. F. (2002). Chemical Engineering Volume 1 (6th ed.). Butterworth-Heinemann.
Contributions are welcome! Please read CONTRIBUTING.md first.
# Fork the repo, create a branch, commit your changes, open a PR
git checkout -b feature/my-improvement
git commit -m "feat: add packed column flooding correlation"
git push origin feature/my-improvementMIT © 2025 Defne Nihal Ertugrul. See LICENSE for details.