WitZuk 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.
- witzuk-0.1.0/LICENSE_NOT_SELECTED.txt +3 -0
- witzuk-0.1.0/MANIFEST.in +3 -0
- witzuk-0.1.0/PKG-INFO +77 -0
- witzuk-0.1.0/README.md +48 -0
- witzuk-0.1.0/pyproject.toml +48 -0
- witzuk-0.1.0/setup.cfg +4 -0
- witzuk-0.1.0/src/WitZuk.egg-info/PKG-INFO +77 -0
- witzuk-0.1.0/src/WitZuk.egg-info/SOURCES.txt +21 -0
- witzuk-0.1.0/src/WitZuk.egg-info/dependency_links.txt +1 -0
- witzuk-0.1.0/src/WitZuk.egg-info/requires.txt +9 -0
- witzuk-0.1.0/src/WitZuk.egg-info/top_level.txt +1 -0
- witzuk-0.1.0/src/witzuk/__init__.py +5 -0
- witzuk-0.1.0/src/witzuk/_version.py +1 -0
- witzuk-0.1.0/src/witzuk/hcooh_fbr/__init__.py +19 -0
- witzuk-0.1.0/src/witzuk/hcooh_fbr/element_balance.py +47 -0
- witzuk-0.1.0/src/witzuk/hcooh_fbr/fluidized_bed.py +131 -0
- witzuk-0.1.0/src/witzuk/hcooh_fbr/heat_effect.py +106 -0
- witzuk-0.1.0/src/witzuk/hcooh_fbr/py.typed +0 -0
- witzuk-0.1.0/src/witzuk/hcooh_fbr/scrubber.py +46 -0
- witzuk-0.1.0/src/witzuk/hcooh_fbr/stream_from_feed.py +89 -0
- witzuk-0.1.0/src/witzuk/py.typed +0 -0
- witzuk-0.1.0/tests/test_imports.py +4 -0
- witzuk-0.1.0/tests/test_public_api.py +27 -0
witzuk-0.1.0/MANIFEST.in
ADDED
witzuk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: WitZuk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Scientific and engineering simulation functions by Witold Zukowski
|
|
5
|
+
Author: Witold Zukowski
|
|
6
|
+
Keywords: chemical-engineering,fluidized-bed,Kato-Wen,formic-acid,HCOOH
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: Intended Audience :: Education
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE_NOT_SELECTED.txt
|
|
20
|
+
Requires-Dist: numpy>=1.24
|
|
21
|
+
Requires-Dist: scipy>=1.10
|
|
22
|
+
Requires-Dist: pandas>=2.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
25
|
+
Requires-Dist: twine>=5.1; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# WitZuk
|
|
31
|
+
|
|
32
|
+
Biblioteka funkcji naukowych i inżynierskich autorstwa Witolda Żukowskiego.
|
|
33
|
+
|
|
34
|
+
## Moduł `hcooh_fbr`
|
|
35
|
+
|
|
36
|
+
Model płuczki z kwasem mrówkowym i reaktora fluidyzacyjnego Kato-Wena.
|
|
37
|
+
|
|
38
|
+
### Instalacja
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m pip install WitZuk
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Minimalny przykład z płuczką
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from witzuk.hcooh_fbr import simulate_scrubber, simulate_fluidized_bed
|
|
48
|
+
|
|
49
|
+
feed = simulate_scrubber()
|
|
50
|
+
result = simulate_fluidized_bed(feed, return_profile=False)
|
|
51
|
+
print(result["conversion"]["HCOOH"])
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Minimalny przykład bez płuczki
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from witzuk.hcooh_fbr import stream_from_feed, simulate_fluidized_bed
|
|
58
|
+
|
|
59
|
+
feed = stream_from_feed(
|
|
60
|
+
molar_fractions={"HCOOH": 0.02, "N2": 0.98},
|
|
61
|
+
total_flow_mol_s=5.0e-4,
|
|
62
|
+
)
|
|
63
|
+
result = simulate_fluidized_bed(feed, return_profile=False)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Bilans cieplny i pierwiastkowy
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from witzuk.hcooh_fbr import calculate_reactor_heat_effect, check_element_balance
|
|
70
|
+
|
|
71
|
+
heat = calculate_reactor_heat_effect(feed, result["outlet_stream"])
|
|
72
|
+
balance = check_element_balance(feed, result["outlet_stream"])
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Status
|
|
76
|
+
|
|
77
|
+
Wersja 0.1.0 (alpha). Przed publikacją wybierz licencję i uzupełnij metadane projektu.
|
witzuk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# WitZuk
|
|
2
|
+
|
|
3
|
+
Biblioteka funkcji naukowych i inżynierskich autorstwa Witolda Żukowskiego.
|
|
4
|
+
|
|
5
|
+
## Moduł `hcooh_fbr`
|
|
6
|
+
|
|
7
|
+
Model płuczki z kwasem mrówkowym i reaktora fluidyzacyjnego Kato-Wena.
|
|
8
|
+
|
|
9
|
+
### Instalacja
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
python -m pip install WitZuk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Minimalny przykład z płuczką
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from witzuk.hcooh_fbr import simulate_scrubber, simulate_fluidized_bed
|
|
19
|
+
|
|
20
|
+
feed = simulate_scrubber()
|
|
21
|
+
result = simulate_fluidized_bed(feed, return_profile=False)
|
|
22
|
+
print(result["conversion"]["HCOOH"])
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Minimalny przykład bez płuczki
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from witzuk.hcooh_fbr import stream_from_feed, simulate_fluidized_bed
|
|
29
|
+
|
|
30
|
+
feed = stream_from_feed(
|
|
31
|
+
molar_fractions={"HCOOH": 0.02, "N2": 0.98},
|
|
32
|
+
total_flow_mol_s=5.0e-4,
|
|
33
|
+
)
|
|
34
|
+
result = simulate_fluidized_bed(feed, return_profile=False)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Bilans cieplny i pierwiastkowy
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from witzuk.hcooh_fbr import calculate_reactor_heat_effect, check_element_balance
|
|
41
|
+
|
|
42
|
+
heat = calculate_reactor_heat_effect(feed, result["outlet_stream"])
|
|
43
|
+
balance = check_element_balance(feed, result["outlet_stream"])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Status
|
|
47
|
+
|
|
48
|
+
Wersja 0.1.0 (alpha). Przed publikacją wybierz licencję i uzupełnij metadane projektu.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "WitZuk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Scientific and engineering simulation functions by Witold Zukowski"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [{ name = "Witold Zukowski" }]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"numpy>=1.24",
|
|
14
|
+
"scipy>=1.10",
|
|
15
|
+
"pandas>=2.0",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"Intended Audience :: Education",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Chemistry",
|
|
28
|
+
]
|
|
29
|
+
keywords = ["chemical-engineering", "fluidized-bed", "Kato-Wen", "formic-acid", "HCOOH"]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"build>=1.2",
|
|
34
|
+
"twine>=5.1",
|
|
35
|
+
"pytest>=8.0",
|
|
36
|
+
"ruff>=0.6",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
addopts = "-ra"
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
target-version = "py310"
|
witzuk-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: WitZuk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Scientific and engineering simulation functions by Witold Zukowski
|
|
5
|
+
Author: Witold Zukowski
|
|
6
|
+
Keywords: chemical-engineering,fluidized-bed,Kato-Wen,formic-acid,HCOOH
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: Intended Audience :: Education
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE_NOT_SELECTED.txt
|
|
20
|
+
Requires-Dist: numpy>=1.24
|
|
21
|
+
Requires-Dist: scipy>=1.10
|
|
22
|
+
Requires-Dist: pandas>=2.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
25
|
+
Requires-Dist: twine>=5.1; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# WitZuk
|
|
31
|
+
|
|
32
|
+
Biblioteka funkcji naukowych i inżynierskich autorstwa Witolda Żukowskiego.
|
|
33
|
+
|
|
34
|
+
## Moduł `hcooh_fbr`
|
|
35
|
+
|
|
36
|
+
Model płuczki z kwasem mrówkowym i reaktora fluidyzacyjnego Kato-Wena.
|
|
37
|
+
|
|
38
|
+
### Instalacja
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m pip install WitZuk
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Minimalny przykład z płuczką
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from witzuk.hcooh_fbr import simulate_scrubber, simulate_fluidized_bed
|
|
48
|
+
|
|
49
|
+
feed = simulate_scrubber()
|
|
50
|
+
result = simulate_fluidized_bed(feed, return_profile=False)
|
|
51
|
+
print(result["conversion"]["HCOOH"])
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Minimalny przykład bez płuczki
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from witzuk.hcooh_fbr import stream_from_feed, simulate_fluidized_bed
|
|
58
|
+
|
|
59
|
+
feed = stream_from_feed(
|
|
60
|
+
molar_fractions={"HCOOH": 0.02, "N2": 0.98},
|
|
61
|
+
total_flow_mol_s=5.0e-4,
|
|
62
|
+
)
|
|
63
|
+
result = simulate_fluidized_bed(feed, return_profile=False)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Bilans cieplny i pierwiastkowy
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from witzuk.hcooh_fbr import calculate_reactor_heat_effect, check_element_balance
|
|
70
|
+
|
|
71
|
+
heat = calculate_reactor_heat_effect(feed, result["outlet_stream"])
|
|
72
|
+
balance = check_element_balance(feed, result["outlet_stream"])
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Status
|
|
76
|
+
|
|
77
|
+
Wersja 0.1.0 (alpha). Przed publikacją wybierz licencję i uzupełnij metadane projektu.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
LICENSE_NOT_SELECTED.txt
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
src/WitZuk.egg-info/PKG-INFO
|
|
6
|
+
src/WitZuk.egg-info/SOURCES.txt
|
|
7
|
+
src/WitZuk.egg-info/dependency_links.txt
|
|
8
|
+
src/WitZuk.egg-info/requires.txt
|
|
9
|
+
src/WitZuk.egg-info/top_level.txt
|
|
10
|
+
src/witzuk/__init__.py
|
|
11
|
+
src/witzuk/_version.py
|
|
12
|
+
src/witzuk/py.typed
|
|
13
|
+
src/witzuk/hcooh_fbr/__init__.py
|
|
14
|
+
src/witzuk/hcooh_fbr/element_balance.py
|
|
15
|
+
src/witzuk/hcooh_fbr/fluidized_bed.py
|
|
16
|
+
src/witzuk/hcooh_fbr/heat_effect.py
|
|
17
|
+
src/witzuk/hcooh_fbr/py.typed
|
|
18
|
+
src/witzuk/hcooh_fbr/scrubber.py
|
|
19
|
+
src/witzuk/hcooh_fbr/stream_from_feed.py
|
|
20
|
+
tests/test_imports.py
|
|
21
|
+
tests/test_public_api.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
witzuk
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""HCOOH scrubber and fluidized-bed reactor models."""
|
|
2
|
+
|
|
3
|
+
from .element_balance import check_element_balance, elemental_molar_flows
|
|
4
|
+
from .fluidized_bed import Params, simulate_fluidized_bed
|
|
5
|
+
from .heat_effect import calculate_reactor_heat_effect, stream_enthalpy_flow_W
|
|
6
|
+
from .scrubber import simulate_scrubber
|
|
7
|
+
from .stream_from_feed import reactor_feed_from_stream, stream_from_feed
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Params",
|
|
11
|
+
"calculate_reactor_heat_effect",
|
|
12
|
+
"check_element_balance",
|
|
13
|
+
"elemental_molar_flows",
|
|
14
|
+
"reactor_feed_from_stream",
|
|
15
|
+
"simulate_fluidized_bed",
|
|
16
|
+
"simulate_scrubber",
|
|
17
|
+
"stream_enthalpy_flow_W",
|
|
18
|
+
"stream_from_feed",
|
|
19
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Bilans molowy pierwiastkow dla uniwersalnych strumieni gazowych."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
ATOMS = {
|
|
6
|
+
"HCOOH": {"C": 1, "O": 2, "H": 2},
|
|
7
|
+
"CO": {"C": 1, "O": 1, "H": 0},
|
|
8
|
+
"H2O": {"C": 0, "O": 1, "H": 2},
|
|
9
|
+
"CO2": {"C": 1, "O": 2, "H": 0},
|
|
10
|
+
"H2": {"C": 0, "O": 0, "H": 2},
|
|
11
|
+
"CH4": {"C": 1, "O": 0, "H": 4},
|
|
12
|
+
"N2": {"C": 0, "O": 0, "H": 0},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def elemental_molar_flows(stream):
|
|
17
|
+
"""Zwraca strumienie molowe atomow C, O i H w mol_atom/s."""
|
|
18
|
+
flows = stream["molar_flows"]
|
|
19
|
+
return {
|
|
20
|
+
element: sum(
|
|
21
|
+
float(flows.get(species, 0.0)) * composition[element]
|
|
22
|
+
for species, composition in ATOMS.items()
|
|
23
|
+
)
|
|
24
|
+
for element in ("C", "O", "H")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def check_element_balance(inlet_stream, outlet_stream, rtol=1e-6, atol=1e-12):
|
|
29
|
+
"""Sprawdza bilans C, O i H pomiedzy wlotem i wylotem reaktora."""
|
|
30
|
+
inlet = elemental_molar_flows(inlet_stream)
|
|
31
|
+
outlet = elemental_molar_flows(outlet_stream)
|
|
32
|
+
result = {}
|
|
33
|
+
|
|
34
|
+
for element in ("C", "O", "H"):
|
|
35
|
+
difference = outlet[element] - inlet[element]
|
|
36
|
+
relative_error = difference / inlet[element] if abs(inlet[element]) > atol else 0.0
|
|
37
|
+
result[element] = {
|
|
38
|
+
"in_mol_atom_s": inlet[element],
|
|
39
|
+
"out_mol_atom_s": outlet[element],
|
|
40
|
+
"difference_mol_atom_s": difference,
|
|
41
|
+
"relative_error": relative_error,
|
|
42
|
+
"relative_error_percent": 100.0 * relative_error,
|
|
43
|
+
"balanced": math.isclose(outlet[element], inlet[element], rel_tol=rtol, abs_tol=atol),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
result["all_balanced"] = all(result[e]["balanced"] for e in ("C", "O", "H"))
|
|
47
|
+
return result
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Niezależny model reaktora fluidyzacyjnego Kato-Wena dla HCOOH/N2."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from dataclasses import dataclass, fields
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from scipy.integrate import solve_ivp, trapezoid
|
|
9
|
+
|
|
10
|
+
from .stream_from_feed import SPECIES, R, reactor_feed_from_stream, stream_from_feed
|
|
11
|
+
|
|
12
|
+
G, P0, T0 = 9.81, 1e5, 298.15
|
|
13
|
+
IDX = {s: i for i, s in enumerate(SPECIES)}
|
|
14
|
+
NS = len(SPECIES)
|
|
15
|
+
NU = np.array([[-1.,-1.,0.],[1.,0.,-1.],[1.,0.,1.],[0.,1.,0.],
|
|
16
|
+
[0.,1.,-3.],[0.,0.,1.],[0.,0.,0.]])
|
|
17
|
+
DNU = NU.sum(axis=0)
|
|
18
|
+
MW = np.array([46.0254,28.0101,18.01528,44.0095,2.01588,16.0425,28.0134])/1000
|
|
19
|
+
FULLER_V = np.array([41.,18.,13.1,26.9,6.12,25.15,18.5])
|
|
20
|
+
SIGMA = np.array([4.2,3.69,2.64,3.996,2.92,3.78,3.798])
|
|
21
|
+
EPSK = np.array([470.,91.7,809.1,190.,59.7,148.6,71.4])
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Params:
|
|
25
|
+
T_reactor: float=573.15; p: float=101325.; D_col: float=0.034
|
|
26
|
+
H: float=0.096; dp: float=0.00011; rho_s: float=1235.; eps_mf: float=0.43
|
|
27
|
+
k1_dehydr: float=1.5; k2_dehyd: float=1.; k3_meth: float=1e6
|
|
28
|
+
r3_reversible: bool=True; hole_density: float=125000.; d_hole: float=0.0006
|
|
29
|
+
Dbmax: float=0.020; Db0min: float=0.001; Db0max: float=0.010; alphaD: float=0.30
|
|
30
|
+
n_points: int=151; variable_viscosity: bool=True; add_equilibrium_diag: bool=True
|
|
31
|
+
|
|
32
|
+
def _omega(t): return 1.16145*t**-0.14874+0.52487*np.exp(-0.77320*t)+2.16178*np.exp(-2.43787*t)
|
|
33
|
+
def _pure_mu(T,M,s,e): return 2.6693e-6*math.sqrt(M*T)/(s*s*_omega(T/e))
|
|
34
|
+
def _gas_mu(p,y):
|
|
35
|
+
if not p.variable_viscosity: return 2e-5
|
|
36
|
+
mu=np.array([_pure_mu(p.T_reactor,MW[i]*1000,SIGMA[i],EPSK[i]) for i in range(NS)])
|
|
37
|
+
y=np.maximum(np.asarray(y),0); y=y/y.sum(); phi=np.ones((NS,NS))
|
|
38
|
+
for i in range(NS):
|
|
39
|
+
for j in range(NS):
|
|
40
|
+
phi[i,j]=(1+math.sqrt(mu[i]/mu[j])*(MW[j]/MW[i])**0.25)**2/math.sqrt(8*(1+MW[i]/MW[j]))
|
|
41
|
+
return float(sum(y[i]*mu[i]/sum(y[j]*phi[i,j] for j in range(NS)) for i in range(NS)))
|
|
42
|
+
def _gas_rho(p,y): return p.p*float(np.dot(y,MW))/(R*p.T_reactor)
|
|
43
|
+
def _umf(p,rho,mu):
|
|
44
|
+
Ar=max(p.dp**3*rho*(p.rho_s-rho)*G/mu**2,0); Re=math.sqrt(33.7**2+0.0408*Ar)-33.7
|
|
45
|
+
return Re*mu/(rho*p.dp),Ar,Re
|
|
46
|
+
|
|
47
|
+
def _fuller(T,p,Mi,Mj,Vi,Vj):
|
|
48
|
+
d=1e-3*T**1.75*math.sqrt(1/(Mi*1000)+1/(Mj*1000))/((p/101325)*(Vi**(1/3)+Vj**(1/3))**2)
|
|
49
|
+
return d*1e-4
|
|
50
|
+
def _dbin(p):
|
|
51
|
+
D=np.zeros((NS,NS))
|
|
52
|
+
for i in range(NS):
|
|
53
|
+
for j in range(NS): D[i,j]=np.nan if i==j else _fuller(p.T_reactor,p.p,MW[i],MW[j],FULLER_V[i],FULLER_V[j])
|
|
54
|
+
return D
|
|
55
|
+
def _dmix(i,y,D): return (1-y[i])/max(sum(y[j]/D[i,j] for j in range(NS) if j!=i),1e-30)
|
|
56
|
+
|
|
57
|
+
# Wartości Kp jak w aplikacji v7.1.
|
|
58
|
+
SH={'CO':[25.56759,6.096130,4.054656,-2.671301,.131021,-118.0089,227.3665,-110.5271],
|
|
59
|
+
'CO2':[24.99735,55.18696,-33.69137,7.948387,-.136638,-403.6075,228.2431,-393.5224],
|
|
60
|
+
'H2':[33.066178,-11.363417,11.432816,-2.772874,-.158558,-9.980797,172.707974,0],
|
|
61
|
+
'H2O':[30.092,6.832514,6.793435,-2.53448,.082139,-250.881,223.3967,-241.8264],
|
|
62
|
+
'CH4':[-.703029,108.4773,-42.52157,5.862788,.678565,-76.84376,158.7163,-74.8731]}
|
|
63
|
+
HF={'CO':-110.5271,'CO2':-393.5224,'H2':0.,'H2O':-241.8264,'CH4':-74.8731,'HCOOH':-378.6}
|
|
64
|
+
def _sh(T,c):
|
|
65
|
+
A,B,C,D,E,F,S,H=c; t=T/1000
|
|
66
|
+
return A*t+B*t*t/2+C*t**3/3+D*t**4/4-E/t+F-H, A*math.log(t)+B*t+C*t*t/2+D*t**3/3-E/(2*t*t)+S
|
|
67
|
+
def _hcooh(T):
|
|
68
|
+
k=1.380649e-23; h=6.62607015e-34; c=299792458.; Na=6.02214076e23; m=46.0254e-3/Na
|
|
69
|
+
rot=[2.58548,.40211,.34745]; vib=[3570,2943,1770,1387,1229,1105,625,1033,638]
|
|
70
|
+
St=R*(math.log(((2*math.pi*m*k*T)/(h*h))**1.5*(k*T/P0))+2.5)
|
|
71
|
+
th=[h*c*(b*100)/k for b in rot]; Sr=R*(math.log(math.sqrt(math.pi)*T**1.5/math.sqrt(np.prod(th)))+1.5)
|
|
72
|
+
def sv(x): return sum(R*((q:=h*c*(v*100)/(k*x))/(math.exp(q)-1)-math.log(1-math.exp(-q))) for v in vib)
|
|
73
|
+
def hv(x): return sum(R*(h*c*(v*100)/k)/(math.exp((h*c*(v*100)/k)/x)-1) for v in vib)/1000
|
|
74
|
+
return 4*R*(T-T0)/1000+hv(T)-hv(T0),St+Sr+sv(T)
|
|
75
|
+
def _kp(T):
|
|
76
|
+
gf={}
|
|
77
|
+
for s,c in SH.items(): H,S=_sh(T,c); gf[s]=HF[s]+H-T*S/1000
|
|
78
|
+
H,S=_hcooh(T); gf['HCOOH']=HF['HCOOH']+H-T*S/1000
|
|
79
|
+
rx=[{'CO':1,'H2O':1,'HCOOH':-1},{'CO2':1,'H2':1,'HCOOH':-1},{'CH4':1,'H2O':1,'CO':-1,'H2':-3}]
|
|
80
|
+
return [math.exp(-sum(n*gf[s] for s,n in r.items())*1000/(R*T)) for r in rx]
|
|
81
|
+
def _qp3(y,p):
|
|
82
|
+
co,h2,ch4,h2o=y[IDX['CO']],y[IDX['H2']],y[IDX['CH4']],y[IDX['H2O']]
|
|
83
|
+
if co<=1e-30 or h2<=1e-30: return 0. if ch4*h2o<=1e-30 else 1e300
|
|
84
|
+
return max(ch4,0)*max(h2o,0)/(co*h2**3)*(p/P0)**-2
|
|
85
|
+
|
|
86
|
+
def _reconstruct(x):
|
|
87
|
+
y=np.zeros(NS); y[:-1]=x; y[-1]=1-y[:-1].sum(); y=np.maximum(y,0)
|
|
88
|
+
return y/y.sum()
|
|
89
|
+
def _db0(p,a,Q): return min(max(1.38*((Q/a['Nh'] if a['Nh']>0 else Q)**2/G)**.2,p.Db0min),p.Db0max)
|
|
90
|
+
def _hydro(p,a,z,FTb,yb,ye):
|
|
91
|
+
FT=FTb+a['FTe']; yt=(FTb*yb+a['FTe']*ye)/FT; u0=FT/(a['A']*a['CT']); Ub=FTb/(a['A']*a['CT'])
|
|
92
|
+
dbmax=min(p.Dbmax,.6*p.D_col); d0=_db0(p,a,u0*a['A']); db=min(max(dbmax-(dbmax-d0)*math.exp(-(p.alphaD/p.D_col)*z),p.Db0min),dbmax)
|
|
93
|
+
ubr=.711*math.sqrt(G*db); up=max(Ub+ubr,1e-12); delta=min(max(Ub/up,1e-8),.95); ab=6*delta/db
|
|
94
|
+
rho=_gas_rho(p,yt); mu=_gas_mu(p,yt); umf,Ar,Re=_umf(p,rho,mu)
|
|
95
|
+
return dict(FT=FT,yt=yt,u0=u0,Ub=Ub,Ue=a['umf0'],Db=db,ubr=ubr,up=up,delta=delta,ab=ab,rho_g=rho,mu_g=mu,umf_diag=umf,Ar=Ar,Re=Re,Nf=u0/umf)
|
|
96
|
+
def _rates(p,a,z,FTb,yb,ye):
|
|
97
|
+
h=_hydro(p,a,z,FTb,yb,ye); eps=(1-h['delta'])*p.eps_mf; C=a['CT']*ye; yh2=max(ye[IDX['H2']],0)
|
|
98
|
+
r1=eps*p.k1_dehydr*C[IDX['HCOOH']]; r2=eps*p.k2_dehyd*C[IDX['HCOOH']]
|
|
99
|
+
rf=eps*p.k3_meth*C[IDX['CO']]*yh2**3; q=_qp3(ye,p.p); drive=1-q/a['Kp'][2] if p.r3_reversible else 1.; r3=rf*drive
|
|
100
|
+
rv=np.array([r1,r2,r3]); Rsp=NU@rv; Rtot=float(DNU@rv); MT=-Rtot
|
|
101
|
+
ym=(yb+ye)/2; ym/=ym.sum(); Dm=np.array([_dmix(i,ym,a['Dbin']) for i in range(NS)]); K=4*Dm/h['Db']+.5*a['umf0']
|
|
102
|
+
M=K*h['ab']*a['CT']*(yb-ye)+(ye if MT<0 else yb)*MT
|
|
103
|
+
h.update(r1=r1,r2=r2,r3=r3,r3_forward=rf,r3_reverse_equiv=rf*q/a['Kp'][2] if p.r3_reversible else 0.,r3_drive=drive,Qp3_e_local=q,Kp3_thermo=a['Kp'][2],Rsp=Rsp,Rtot=Rtot,MT=MT,M=M,K=K,Dm=Dm)
|
|
104
|
+
return h
|
|
105
|
+
|
|
106
|
+
def simulate_fluidized_bed(inlet_stream, return_profile=True, **kwargs):
|
|
107
|
+
"""Symuluje reaktor. Niepodane argumenty przyjmują wartości z aplikacji v7.1."""
|
|
108
|
+
valid={f.name for f in fields(Params)}; unknown=set(kwargs)-valid
|
|
109
|
+
if unknown: raise TypeError(f"Nieznane parametry: {sorted(unknown)}")
|
|
110
|
+
p=Params(**kwargs); fd=reactor_feed_from_stream(inlet_stream,p); y0=fd['y0']; F0=fd['F0']
|
|
111
|
+
rho=_gas_rho(p,y0); mu=_gas_mu(p,y0); umf,Ar,Re=_umf(p,rho,mu); FTe=fd['A']*fd['CT']*umf
|
|
112
|
+
Nh=p.hole_density*fd['A']; Ah=math.pi*p.d_hole**2/4
|
|
113
|
+
a={**fd,'rho0':rho,'mu0':mu,'umf0':umf,'Ar0':Ar,'Re0':Re,'FTe':FTe,'Nh':Nh,'Ah':Ah,'Aop':Nh*Ah,'Dbin':_dbin(p),'Kp':_kp(p.T_reactor)}
|
|
114
|
+
if FTe>=.98*F0: raise ValueError(f"FTe={FTe:.3e} mol/s jest zbyt duże względem F0={F0:.3e} mol/s.")
|
|
115
|
+
FTb0=F0-FTe; Y0=np.r_[FTb0,y0[:-1],y0[:-1]]
|
|
116
|
+
def ode(z,Y):
|
|
117
|
+
FTb=max(float(Y[0]),1e-20); yb=_reconstruct(Y[1:NS]); ye=_reconstruct(Y[NS:2*NS-1]); h=_rates(p,a,z,FTb,yb,ye)
|
|
118
|
+
dF=fd['A']*h['Rtot']; return np.r_[dF,(-fd['A']*h['M'][:-1]-yb[:-1]*dF)/FTb,fd['A']*(h['M'][:-1]+h['Rsp'][:-1])/FTe]
|
|
119
|
+
sol=solve_ivp(ode,(0,p.H),Y0,t_eval=np.linspace(0,p.H,int(p.n_points)),method='BDF',rtol=1e-6,atol=1e-9)
|
|
120
|
+
if not sol.success: raise RuntimeError(sol.message)
|
|
121
|
+
rows=[]; FH0=F0*y0[IDX['HCOOH']]
|
|
122
|
+
for k,z in enumerate(sol.t):
|
|
123
|
+
FTb=max(float(sol.y[0,k]),1e-20); yb=_reconstruct(sol.y[1:NS,k]); ye=_reconstruct(sol.y[NS:2*NS-1,k]); h=_rates(p,a,float(z),FTb,yb,ye)
|
|
124
|
+
F=FTb*yb+FTe*ye; FT=float(F.sum()); row={'z_m':float(z),'FT_mol_s':FT,'FTb_mol_s':FTb,'FTe_mol_s':FTe,'X_HCOOH':(FH0-F[IDX['HCOOH']])/FH0 if FH0>0 else np.nan,'u0_m_s':h['u0'],'Db_m':h['Db'],'delta_b':h['delta'],'r1_dehydr_mol_m3_s':h['r1'],'r2_dehyd_mol_m3_s':h['r2'],'r3_net_mol_m3_s':h['r3']}
|
|
125
|
+
for i,s in enumerate(SPECIES): row[f'F_{s}_mol_s']=F[i]; row[f'y_{s}_total']=F[i]/FT; row[f'C_{s}_total_mol_m3']=fd['CT']*F[i]/FT
|
|
126
|
+
rows.append(row)
|
|
127
|
+
profile=pd.DataFrame(rows); out=profile.iloc[-1]
|
|
128
|
+
outlet=stream_from_feed(molar_flows={s:float(out[f'F_{s}_mol_s']) for s in SPECIES},temperature_K=p.T_reactor,pressure_Pa=p.p)
|
|
129
|
+
outlet['concentrations_mol_m3']={s:float(out[f'C_{s}_total_mol_m3']) for s in SPECIES}
|
|
130
|
+
summary={'X_HCOOH':float(out['X_HCOOH']),'solid_mass_kg':float(math.pi*p.D_col**2/4*trapezoid((1-profile['delta_b'])*(1-p.eps_mf)*p.rho_s, profile['z_m'])),'Kp':a['Kp']}
|
|
131
|
+
return {'outlet_stream':outlet,'conversion':{'HCOOH':float(out['X_HCOOH'])},'outlet':out.to_dict(),'profile':profile if return_profile else None,'summary':summary,'parameters':{f.name:getattr(p,f.name) for f in fields(p)}}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Niezalezny bilans entalpii strumieni gazowych reaktora.
|
|
2
|
+
|
|
3
|
+
Konwencja znaku:
|
|
4
|
+
heat_duty_W > 0 - cieplo nalezy doprowadzic do reaktora,
|
|
5
|
+
heat_duty_W < 0 - cieplo nalezy odprowadzic z reaktora.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
R = 8.314462618
|
|
11
|
+
T0 = 298.15
|
|
12
|
+
P0 = 1.0e5
|
|
13
|
+
SPECIES = ("HCOOH", "CO", "H2O", "CO2", "H2", "CH4", "N2")
|
|
14
|
+
|
|
15
|
+
# Standardowe entalpie tworzenia gazow w 298.15 K [kJ/mol].
|
|
16
|
+
HF298_KJ_MOL = {
|
|
17
|
+
"HCOOH": -378.6,
|
|
18
|
+
"CO": -110.5271,
|
|
19
|
+
"H2O": -241.8264,
|
|
20
|
+
"CO2": -393.5224,
|
|
21
|
+
"H2": 0.0,
|
|
22
|
+
"CH4": -74.87310,
|
|
23
|
+
"N2": 0.0,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Wspolczynniki Shomate: A, B, C, D, E, F, G, H.
|
|
27
|
+
SHOMATE = {
|
|
28
|
+
"CO": [25.56759, 6.096130, 4.054656, -2.671301, 0.131021, -118.0089, 227.3665, -110.5271],
|
|
29
|
+
"CO2": [24.99735, 55.18696, -33.69137, 7.948387, -0.136638, -403.6075, 228.2431, -393.5224],
|
|
30
|
+
"H2": [33.066178, -11.363417, 11.432816, -2.772874, -0.158558, -9.980797, 172.707974, 0.0],
|
|
31
|
+
"H2O": [30.09200, 6.832514, 6.793435, -2.534480, 0.082139, -250.8810, 223.3967, -241.8264],
|
|
32
|
+
"CH4": [-0.703029, 108.4773, -42.52157, 5.862788, 0.678565, -76.84376, 158.7163, -74.87310],
|
|
33
|
+
"N2": [28.98641, 1.853978, -9.647459, 16.63537, 0.000117, -8.671914, 226.4168, 0.0],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _shomate_h_increment_kj_mol(T_K, coeff):
|
|
38
|
+
A, B, C, D, E, F, _G, H = coeff
|
|
39
|
+
t = T_K / 1000.0
|
|
40
|
+
return A*t + B*t*t/2.0 + C*t**3/3.0 + D*t**4/4.0 - E/t + F - H
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _hcooh_h_increment_kj_mol(T_K):
|
|
44
|
+
"""Przyrost entalpii HCOOH(g) zgodny z modelem termodynamicznym v7.1."""
|
|
45
|
+
kB = 1.380649e-23
|
|
46
|
+
h = 6.62607015e-34
|
|
47
|
+
c = 299792458.0
|
|
48
|
+
vib_cm = (3570, 2943, 1770, 1387, 1229, 1105, 625, 1033, 638)
|
|
49
|
+
|
|
50
|
+
def hv(T):
|
|
51
|
+
return sum(
|
|
52
|
+
R * (h*c*(nu*100.0)/kB) /
|
|
53
|
+
(math.exp((h*c*(nu*100.0)/kB)/T) - 1.0)
|
|
54
|
+
for nu in vib_cm
|
|
55
|
+
) / 1000.0
|
|
56
|
+
|
|
57
|
+
return 4.0*R*(T_K - T0)/1000.0 + hv(T_K) - hv(T0)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def species_enthalpy_J_mol(species, temperature_K):
|
|
61
|
+
"""Entalpia molowa gazu obejmujaca tworzenie i czesc jawna [J/mol]."""
|
|
62
|
+
if species not in SPECIES:
|
|
63
|
+
raise ValueError(f"Nieznany skladnik: {species}")
|
|
64
|
+
T = float(temperature_K)
|
|
65
|
+
if not math.isfinite(T) or T <= 0.0:
|
|
66
|
+
raise ValueError("temperature_K musi byc dodatnia i skonczona.")
|
|
67
|
+
dh = (_hcooh_h_increment_kj_mol(T) if species == "HCOOH"
|
|
68
|
+
else _shomate_h_increment_kj_mol(T, SHOMATE[species]))
|
|
69
|
+
return 1000.0 * (HF298_KJ_MOL[species] + dh)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def stream_enthalpy_flow_W(stream):
|
|
73
|
+
"""Zwraca strumien entalpii chemicznej i jawnej [W = J/s]."""
|
|
74
|
+
if not isinstance(stream, dict) or "molar_flows" not in stream:
|
|
75
|
+
raise TypeError("stream musi zawierac slownik molar_flows.")
|
|
76
|
+
T = float(stream["temperature_K"])
|
|
77
|
+
return sum(
|
|
78
|
+
float(stream["molar_flows"].get(sp, 0.0)) * species_enthalpy_J_mol(sp, T)
|
|
79
|
+
for sp in SPECIES
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def calculate_reactor_heat_effect(inlet_stream, outlet_stream, heat_loss_W=0.0, **kwargs):
|
|
84
|
+
"""Oblicza efekt cieplny reaktora z bilansu entalpii wlot-wylot.
|
|
85
|
+
|
|
86
|
+
Wynik dodatni oznacza wymagane doprowadzenie ciepla. Parametr
|
|
87
|
+
``heat_loss_W`` jest dodatni dla strat ciepla z reaktora do otoczenia.
|
|
88
|
+
"""
|
|
89
|
+
H_in = stream_enthalpy_flow_W(inlet_stream)
|
|
90
|
+
H_out = stream_enthalpy_flow_W(outlet_stream)
|
|
91
|
+
loss = float(heat_loss_W)
|
|
92
|
+
duty = H_out - H_in + loss
|
|
93
|
+
F_hcooh_in = float(inlet_stream["molar_flows"].get("HCOOH", 0.0))
|
|
94
|
+
conversion = None
|
|
95
|
+
if F_hcooh_in > 0.0:
|
|
96
|
+
conversion = 1.0 - float(outlet_stream["molar_flows"].get("HCOOH", 0.0)) / F_hcooh_in
|
|
97
|
+
return {
|
|
98
|
+
"enthalpy_in_W": H_in,
|
|
99
|
+
"enthalpy_out_W": H_out,
|
|
100
|
+
"heat_loss_W": loss,
|
|
101
|
+
"heat_duty_W": duty,
|
|
102
|
+
"heat_duty_kW": duty / 1000.0,
|
|
103
|
+
"thermal_character": "endothermic" if duty > 0 else "exothermic" if duty < 0 else "neutral",
|
|
104
|
+
"HCOOH_conversion": conversion,
|
|
105
|
+
"specific_heat_duty_kJ_per_mol_HCOOH_in": duty / F_hcooh_in / 1000.0 if F_hcooh_in > 0 else None,
|
|
106
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Model nasycania azotu parami kwasu mrówkowego w płuczce."""
|
|
2
|
+
|
|
3
|
+
from .stream_from_feed import stream_from_feed
|
|
4
|
+
|
|
5
|
+
R = 8.314462618
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def antoine_formic_acid_bar(T_K):
|
|
9
|
+
A, B, C = 2.00121, 515.0, -139.408
|
|
10
|
+
return 10 ** (A - B / (T_K + C))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def simulate_scrubber(Q_N2_L_min=0.5, T_flow_ref=298.15,
|
|
14
|
+
p_flow_ref=101325.0, T_bubbler_C=15.0,
|
|
15
|
+
acid_activity=1.0, saturation_factor=0.650,
|
|
16
|
+
pressure_Pa=101325.0, temperature_K=573.15,
|
|
17
|
+
HCOOH_in_override=None, **kwargs):
|
|
18
|
+
"""Symuluje płuczkę; niepodane parametry mają wartości z aplikacji v7.1."""
|
|
19
|
+
Q_N2 = float(Q_N2_L_min) * 1e-3 / 60.0
|
|
20
|
+
F_N2 = float(p_flow_ref) * Q_N2 / (R * float(T_flow_ref))
|
|
21
|
+
psat = antoine_formic_acid_bar(float(T_bubbler_C) + 273.15) * 1e5
|
|
22
|
+
|
|
23
|
+
if HCOOH_in_override is None:
|
|
24
|
+
p_hcooh = min(float(saturation_factor) * float(acid_activity) * psat,
|
|
25
|
+
0.95 * float(pressure_Pa))
|
|
26
|
+
F_HCOOH = F_N2 * p_hcooh / max(float(pressure_Pa) - p_hcooh, 1e-30)
|
|
27
|
+
else:
|
|
28
|
+
y = float(HCOOH_in_override)
|
|
29
|
+
if not 0.0 <= y < 1.0:
|
|
30
|
+
raise ValueError("HCOOH_in_override musi należeć do [0, 1).")
|
|
31
|
+
p_hcooh = y * float(pressure_Pa)
|
|
32
|
+
F_HCOOH = F_N2 * y / max(1.0 - y, 1e-30)
|
|
33
|
+
|
|
34
|
+
stream = stream_from_feed(
|
|
35
|
+
molar_flows={"HCOOH": F_HCOOH, "N2": F_N2},
|
|
36
|
+
temperature_K=temperature_K,
|
|
37
|
+
pressure_Pa=pressure_Pa,
|
|
38
|
+
)
|
|
39
|
+
stream["scrubber"] = {
|
|
40
|
+
"T_bubbler_C": float(T_bubbler_C),
|
|
41
|
+
"psat_HCOOH_Pa": psat,
|
|
42
|
+
"pHCOOH_feed_Pa": p_hcooh,
|
|
43
|
+
"acid_activity": float(acid_activity),
|
|
44
|
+
"saturation_factor": float(saturation_factor),
|
|
45
|
+
}
|
|
46
|
+
return stream
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Tworzenie uniwersalnego strumienia gazowego z podanego zasilania."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
R = 8.314462618
|
|
8
|
+
SPECIES = ("HCOOH", "CO", "H2O", "CO2", "H2", "CH4", "N2")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def stream_from_feed(*, molar_flows=None, molar_fractions=None,
|
|
12
|
+
total_flow_mol_s=None, temperature_K=573.15,
|
|
13
|
+
pressure_Pa=101325.0, **kwargs):
|
|
14
|
+
"""Zwraca ujednolicony strumien gazowy.
|
|
15
|
+
|
|
16
|
+
Podaj albo ``molar_flows``, albo parę ``molar_fractions`` i
|
|
17
|
+
``total_flow_mol_s``. Brakujące składniki są przyjmowane jako zero.
|
|
18
|
+
Dodatkowe argumenty nazwane są ignorowane, co ułatwia wspólne API.
|
|
19
|
+
"""
|
|
20
|
+
temperature_K = float(temperature_K)
|
|
21
|
+
pressure_Pa = float(pressure_Pa)
|
|
22
|
+
if not math.isfinite(temperature_K) or temperature_K <= 0:
|
|
23
|
+
raise ValueError("temperature_K musi być dodatnie.")
|
|
24
|
+
if not math.isfinite(pressure_Pa) or pressure_Pa <= 0:
|
|
25
|
+
raise ValueError("pressure_Pa musi być dodatnie.")
|
|
26
|
+
|
|
27
|
+
if molar_flows is not None:
|
|
28
|
+
if not isinstance(molar_flows, dict):
|
|
29
|
+
raise TypeError("molar_flows musi być słownikiem.")
|
|
30
|
+
unknown = set(molar_flows) - set(SPECIES)
|
|
31
|
+
if unknown:
|
|
32
|
+
raise ValueError(f"Nieznane składniki: {sorted(unknown)}")
|
|
33
|
+
flows = {s: float(molar_flows.get(s, 0.0)) for s in SPECIES}
|
|
34
|
+
elif molar_fractions is not None and total_flow_mol_s is not None:
|
|
35
|
+
if not isinstance(molar_fractions, dict):
|
|
36
|
+
raise TypeError("molar_fractions musi być słownikiem.")
|
|
37
|
+
unknown = set(molar_fractions) - set(SPECIES)
|
|
38
|
+
if unknown:
|
|
39
|
+
raise ValueError(f"Nieznane składniki: {sorted(unknown)}")
|
|
40
|
+
fractions = {s: float(molar_fractions.get(s, 0.0)) for s in SPECIES}
|
|
41
|
+
sy = sum(fractions.values())
|
|
42
|
+
if not math.isfinite(sy) or sy <= 0:
|
|
43
|
+
raise ValueError("Suma ułamków molowych musi być dodatnia.")
|
|
44
|
+
fractions = {s: y / sy for s, y in fractions.items()}
|
|
45
|
+
total = float(total_flow_mol_s)
|
|
46
|
+
flows = {s: total * fractions[s] for s in SPECIES}
|
|
47
|
+
else:
|
|
48
|
+
raise ValueError("Podaj molar_flows albo molar_fractions i total_flow_mol_s.")
|
|
49
|
+
|
|
50
|
+
if any((not math.isfinite(v) or v < 0) for v in flows.values()):
|
|
51
|
+
raise ValueError("Strumienie molowe muszą być nieujemne i skończone.")
|
|
52
|
+
total = sum(flows.values())
|
|
53
|
+
if total <= 0:
|
|
54
|
+
raise ValueError("Całkowity strumień molowy musi być dodatni.")
|
|
55
|
+
fractions = {s: flows[s] / total for s in SPECIES}
|
|
56
|
+
return {
|
|
57
|
+
"type": "gas_stream",
|
|
58
|
+
"temperature_K": temperature_K,
|
|
59
|
+
"pressure_Pa": pressure_Pa,
|
|
60
|
+
"total_flow_mol_s": total,
|
|
61
|
+
"molar_flows": flows,
|
|
62
|
+
"molar_fractions": fractions,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def reactor_feed_from_stream(inlet_stream, params):
|
|
67
|
+
"""Konwertuje uniwersalny strumień na dane wewnętrzne reaktora."""
|
|
68
|
+
if "molar_flows" in inlet_stream:
|
|
69
|
+
stream = stream_from_feed(
|
|
70
|
+
molar_flows=inlet_stream["molar_flows"],
|
|
71
|
+
temperature_K=inlet_stream.get("temperature_K", params.T_reactor),
|
|
72
|
+
pressure_Pa=inlet_stream.get("pressure_Pa", params.p),
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
stream = stream_from_feed(
|
|
76
|
+
molar_fractions=inlet_stream["molar_fractions"],
|
|
77
|
+
total_flow_mol_s=inlet_stream["total_flow_mol_s"],
|
|
78
|
+
temperature_K=inlet_stream.get("temperature_K", params.T_reactor),
|
|
79
|
+
pressure_Pa=inlet_stream.get("pressure_Pa", params.p),
|
|
80
|
+
)
|
|
81
|
+
flows = np.array([stream["molar_flows"][s] for s in SPECIES], dtype=float)
|
|
82
|
+
F0 = float(flows.sum())
|
|
83
|
+
return {
|
|
84
|
+
"A": math.pi * params.D_col**2 / 4.0,
|
|
85
|
+
"CT": params.p / (R * params.T_reactor),
|
|
86
|
+
"F0": F0,
|
|
87
|
+
"y0": flows / F0,
|
|
88
|
+
"species_flows": flows,
|
|
89
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
from witzuk.hcooh_fbr import (
|
|
4
|
+
calculate_reactor_heat_effect,
|
|
5
|
+
check_element_balance,
|
|
6
|
+
simulate_fluidized_bed,
|
|
7
|
+
simulate_scrubber,
|
|
8
|
+
stream_from_feed,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_stream_from_feed_normalizes():
|
|
13
|
+
stream = stream_from_feed(
|
|
14
|
+
molar_fractions={"HCOOH": 2.0, "N2": 98.0},
|
|
15
|
+
total_flow_mol_s=5e-4,
|
|
16
|
+
)
|
|
17
|
+
assert math.isclose(sum(stream["molar_fractions"].values()), 1.0)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_default_scrubber_and_reactor():
|
|
21
|
+
feed = simulate_scrubber()
|
|
22
|
+
result = simulate_fluidized_bed(feed, return_profile=False)
|
|
23
|
+
outlet = result["outlet_stream"]
|
|
24
|
+
assert 0.0 <= result["conversion"]["HCOOH"] <= 1.0
|
|
25
|
+
assert math.isclose(sum(outlet["molar_fractions"].values()), 1.0, rel_tol=1e-8)
|
|
26
|
+
assert check_element_balance(feed, outlet)["all_balanced"]
|
|
27
|
+
assert math.isfinite(calculate_reactor_heat_effect(feed, outlet)["heat_duty_W"])
|