Merge pull request #2 from DLR-Institute-of-Future-Fuels/example_generation_feature
- Example generation feature - Code example added - Unit tests and doc generation/config updated to handle examples
This commit is contained in:
commit
ce48083e77
1
.flake8
1
.flake8
|
@ -14,6 +14,7 @@ exclude =
|
||||||
dist,
|
dist,
|
||||||
.conda,
|
.conda,
|
||||||
tests/autogenerated_*,
|
tests/autogenerated_*,
|
||||||
|
docs/source/_autogenerated
|
||||||
.venv,
|
.venv,
|
||||||
venv
|
venv
|
||||||
|
|
||||||
|
|
|
@ -17,17 +17,20 @@ jobs:
|
||||||
uses: actions/setup-python@v3
|
uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- name: Install dependencies
|
- name: Install gaspype and dependencies
|
||||||
run: pip install sphinx sphinx_rtd_theme sphinx-autodoc-typehints myst-parser
|
|
||||||
- name: Generate Class List
|
|
||||||
run: |
|
run: |
|
||||||
echo "Create a dummy file to ensure the class list generation works"
|
echo "Create a dummy file to ensure gaspype does't crash"
|
||||||
mkdir -p src/gaspype/data
|
mkdir -p src/gaspype/data
|
||||||
printf 'gapy\x00\x00\x00\x00' > src/gaspype/data/therm_data.bin
|
printf 'gapy\x00\x00\x00\x00' > src/gaspype/data/therm_data.bin
|
||||||
pip install .
|
pip install .[doc_build]
|
||||||
python ./docs/source/generate_class_list.py
|
python -m ipykernel install --user --name temp_kernel --display-name "Python (temp_kernel)"
|
||||||
|
- name: Generate Docs
|
||||||
|
run: python ./docs/source/generate_class_list.py
|
||||||
|
- name: Generate Examples
|
||||||
|
run: python ./docs/source/render_examples.py
|
||||||
- name: Build Docs
|
- name: Build Docs
|
||||||
run: |
|
run: |
|
||||||
|
cp LICENSE docs/source/LICENSE.md
|
||||||
cd docs
|
cd docs
|
||||||
sphinx-apidoc -o ./source/ ../src/ -M --no-toc
|
sphinx-apidoc -o ./source/ ../src/ -M --no-toc
|
||||||
rm ./source/*.rst
|
rm ./source/*.rst
|
||||||
|
|
|
@ -9,7 +9,7 @@ __pycache__
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
tests/autogenerated_*.py
|
tests/autogenerated_*.py
|
||||||
docs/build/
|
docs/build/
|
||||||
docs/source/modules.md
|
docs/source/_autogenerated/
|
||||||
venv/
|
venv/
|
||||||
.venv/
|
.venv/
|
||||||
thermo_data/combined_data.yaml
|
thermo_data/combined_data.yaml
|
||||||
|
|
25
README.md
25
README.md
|
@ -62,7 +62,8 @@ fl.get_density(t=t_range, p=1e5)
|
||||||
```
|
```
|
||||||
array([0.10122906, 0.09574625, 0.09082685, 0.08638827, 0.08236328])
|
array([0.10122906, 0.09574625, 0.09082685, 0.08638827, 0.08236328])
|
||||||
```
|
```
|
||||||
A ```fluid``` object can have multiple compositions. A multidimensional ```fluid``` object can be created for example by multiplication with a numpy array:
|
A ```fluid``` object can have multiple compositions. A multidimensional ```fluid``` object
|
||||||
|
can be created for example by multiplication with a numpy array:
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
fl2 = gp.fluid({'H2O': 1, 'N2': 2}) + \
|
fl2 = gp.fluid({'H2O': 1, 'N2': 2}) + \
|
||||||
|
@ -125,7 +126,8 @@ array([[[0. , 0.5 , 0.5 ],
|
||||||
```
|
```
|
||||||
|
|
||||||
### Elements
|
### Elements
|
||||||
In some cases not the molecular but the atomic composition is of interest. The ```elements``` class can be used for atom based balances and works similar:
|
In some cases not the molecular but the atomic composition is of interest.
|
||||||
|
The ```elements``` class can be used for atom based balances and works similar:
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
el = gp.elements({'N': 1, 'Cl': 2})
|
el = gp.elements({'N': 1, 'Cl': 2})
|
||||||
|
@ -134,7 +136,9 @@ el.get_mass()
|
||||||
```
|
```
|
||||||
np.float64(0.08490700000000001)
|
np.float64(0.08490700000000001)
|
||||||
```
|
```
|
||||||
A ```elements``` object can be as well instantiated from a ```fluid``` object. Arithmetic operations between ```elements``` and ```fluid``` result in an ```elements``` object:
|
A ```elements``` object can be as well instantiated from a ```fluid``` object.
|
||||||
|
Arithmetic operations between ```elements``` and ```fluid``` result in
|
||||||
|
an ```elements``` object:
|
||||||
``` python
|
``` python
|
||||||
el2 = gp.elements(fl) + el - 0.3 * fl
|
el2 = gp.elements(fl) + el - 0.3 * fl
|
||||||
el2
|
el2
|
||||||
|
@ -146,7 +150,8 @@ N 1.000e+00 mol
|
||||||
O 7.000e-01 mol
|
O 7.000e-01 mol
|
||||||
```
|
```
|
||||||
|
|
||||||
Going from an atomic composition to an molecular composition is a little bit less straight forward, since there is no universal approach. One way is to calculate the thermodynamic equilibrium for a mixture:
|
Going from an atomic composition to an molecular composition is possible as well.
|
||||||
|
One way is to calculate the thermodynamic equilibrium for a mixture:
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
fs = gp.fluid_system('CH4, H2, CO, CO2, O2')
|
fs = gp.fluid_system('CH4, H2, CO, CO2, O2')
|
||||||
|
@ -163,7 +168,13 @@ CO2 33.07 %
|
||||||
O2 0.00 %
|
O2 0.00 %
|
||||||
```
|
```
|
||||||
|
|
||||||
The ```equilibrium``` function can be called with a ```fluid``` or ```elements``` object as first argument. ```fluid``` and ```elements``` referencing a ```fluid_system``` object witch can be be set as shown above during the object instantiation. If not provided, a new one will be created automatically. Providing a ```fluid_system``` gives more control over which molecular species are included in derived ```fluid``` objects. Furthermore arithmetic operations between objects with the same ```fluid_system``` are potentially faster:
|
The ```equilibrium``` function can be called with a ```fluid``` or ```elements``` object
|
||||||
|
as first argument. ```fluid``` and ```elements``` referencing a ```fluid_system``` object
|
||||||
|
witch can be be set as shown above during the object instantiation. If not provided,
|
||||||
|
a new one will be created automatically. Providing a ```fluid_system``` gives more
|
||||||
|
control over which molecular species are included in derived ```fluid``` objects.
|
||||||
|
Furthermore arithmetic operations between objects with the same ```fluid_system```
|
||||||
|
are potentially faster:
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
fl3 + gp.fluid({'CH4': 1}, fs)
|
fl3 + gp.fluid({'CH4': 1}, fs)
|
||||||
|
@ -177,7 +188,9 @@ CO2 18.07 %
|
||||||
O2 0.00 %
|
O2 0.00 %
|
||||||
```
|
```
|
||||||
|
|
||||||
Especially if the ```fluid_system``` of one of the operants has not a subset of molecular species of the other ```fluid_system``` a new ```fluid_system``` will be created for the operation which might degrade performance:
|
Especially if the ```fluid_system``` of one of the operants has not a subset of
|
||||||
|
molecular species of the other ```fluid_system``` a new ```fluid_system``` will
|
||||||
|
be created for the operation which might degrade performance:
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
fl3 + gp.fluid({'NH3': 1})
|
fl3 + gp.fluid({'NH3': 1})
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="164.70512mm"
|
||||||
|
height="86.731506mm"
|
||||||
|
viewBox="0 0 164.70512 86.731506"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||||
|
sodipodi:docname="soc_inverted.svg"
|
||||||
|
inkscape:export-filename="soc_export.svg"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="1.0041055"
|
||||||
|
inkscape:cx="425.25411"
|
||||||
|
inkscape:cy="332.13641"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1009"
|
||||||
|
inkscape:window-x="1912"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<marker
|
||||||
|
style="overflow:visible"
|
||||||
|
id="Triangle"
|
||||||
|
refX="0"
|
||||||
|
refY="0"
|
||||||
|
orient="auto-start-reverse"
|
||||||
|
inkscape:stockid="Triangle arrow"
|
||||||
|
markerWidth="2"
|
||||||
|
markerHeight="1"
|
||||||
|
viewBox="0 0 1 1"
|
||||||
|
inkscape:isstock="true"
|
||||||
|
inkscape:collect="always"
|
||||||
|
preserveAspectRatio="none">
|
||||||
|
<path
|
||||||
|
transform="scale(0.5)"
|
||||||
|
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||||
|
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||||
|
id="path135" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-18.027099,-19.242669)">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#dadada;stroke-width:0.465;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Triangle);marker-end:url(#Triangle)"
|
||||||
|
d="M 23.663111,22.545169 V 97.81995 H 179.42973"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#dadada;stroke-width:0.372829;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 170.85884,97.81995 V 95.304579"
|
||||||
|
id="path2" />
|
||||||
|
<circle
|
||||||
|
style="fill:#dddddd;fill-opacity:1;stroke:#dadada;stroke-width:0.468334;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path3"
|
||||||
|
cx="40.768127"
|
||||||
|
cy="46.63377"
|
||||||
|
r="1.2658331" />
|
||||||
|
<circle
|
||||||
|
style="fill:#dddddd;fill-opacity:1;stroke:#dadada;stroke-width:0.468334;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path3-7"
|
||||||
|
cx="60.487274"
|
||||||
|
cy="54.953339"
|
||||||
|
r="1.2658331" />
|
||||||
|
<circle
|
||||||
|
style="fill:#dddddd;fill-opacity:1;stroke:#dadada;stroke-width:0.468334;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path3-6"
|
||||||
|
cx="94.775032"
|
||||||
|
cy="67.060722"
|
||||||
|
r="1.2658331" />
|
||||||
|
<circle
|
||||||
|
style="fill:#dddddd;fill-opacity:1;stroke:#dadada;stroke-width:0.468334;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path3-8"
|
||||||
|
cx="133.16524"
|
||||||
|
cy="76.384407"
|
||||||
|
r="1.2658331" />
|
||||||
|
<circle
|
||||||
|
style="fill:#dddddd;fill-opacity:1;stroke:#dadada;stroke-width:0.468334;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path3-1"
|
||||||
|
cx="23.607611"
|
||||||
|
cy="32.419498"
|
||||||
|
r="1.2658331" />
|
||||||
|
<circle
|
||||||
|
style="fill:#dddddd;fill-opacity:1;stroke:#dadada;stroke-width:0.468334;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path3-8-3"
|
||||||
|
cx="170.49799"
|
||||||
|
cy="83.681023"
|
||||||
|
r="1.2658331" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#dadada;stroke-width:0.465;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 23.61653,32.379465 17.141782,14.300346 19.796893,8.151661 34.283565,12.111043 38.38268,9.502507 37.26475,7.266625"
|
||||||
|
id="path4"
|
||||||
|
sodipodi:nodetypes="cccccc" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:4.23333px;fill:#dedede;fill-opacity:1;stroke:none;stroke-width:0.465;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
x="86.083488"
|
||||||
|
y="105.09981"
|
||||||
|
id="text4"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4"
|
||||||
|
style="font-size:4.23333px;fill:#dedede;fill-opacity:1;stroke:none;stroke-width:0.465"
|
||||||
|
x="86.083488"
|
||||||
|
y="105.09981">Relative cell lenghs</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:4.23333px;fill:#dedede;fill-opacity:1;stroke:none;stroke-width:0.465;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
x="169.54897"
|
||||||
|
y="103.00477"
|
||||||
|
id="text4-1"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4-1"
|
||||||
|
style="font-size:4.23333px;fill:#dedede;fill-opacity:1;stroke:none;stroke-width:0.465"
|
||||||
|
x="169.54897"
|
||||||
|
y="103.00477">1</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:4.23333px;fill:#dedede;fill-opacity:1;stroke:none;stroke-width:0.465;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
x="22.457224"
|
||||||
|
y="103.68204"
|
||||||
|
id="text4-1-1"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4-1-2"
|
||||||
|
style="font-size:4.23333px;fill:#dedede;fill-opacity:1;stroke:none;stroke-width:0.465"
|
||||||
|
x="22.457224"
|
||||||
|
y="103.68204">0</tspan></text>
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:#dadada;stroke-width:0.3;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 40.710985,49.035087 V 65.438055"
|
||||||
|
id="path5" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:#dadada;stroke-width:0.3;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 60.546682,57.469645 v 7.773293"
|
||||||
|
id="path5-9" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:#dadada;stroke-width:0.3;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Triangle);marker-end:url(#Triangle)"
|
||||||
|
d="M 58.396033,62.880678 H 42.878103"
|
||||||
|
id="path5-0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:4.23333px;fill:#dedede;fill-opacity:1;stroke:none;stroke-width:0.465;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
x="48.511959"
|
||||||
|
y="61.409679"
|
||||||
|
id="text4-1-1-5"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4-1-2-2"
|
||||||
|
style="font-size:4.23333px;fill:#dedede;fill-opacity:1;stroke:none;stroke-width:0.465"
|
||||||
|
x="48.511959"
|
||||||
|
y="61.409679">Δz</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:4.23333px;fill:#dedede;fill-opacity:1;stroke:none;stroke-width:0.465;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
x="-72.630066"
|
||||||
|
y="21.160755"
|
||||||
|
id="text4-9"
|
||||||
|
transform="rotate(-90)"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4-5"
|
||||||
|
style="font-size:4.23333px;fill:#dedede;fill-opacity:1;stroke:none;stroke-width:0.465"
|
||||||
|
x="-72.630066"
|
||||||
|
y="21.160755">Current</tspan></text>
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:#dadada;stroke-width:0.3;stroke-dasharray:1.2, 0.3;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
d="M 94.728796,67.061137 V 97.890816"
|
||||||
|
id="path6" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:#dadada;stroke-width:0.3;stroke-dasharray:1.2, 0.3;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
d="M 133.2181,76.566042 V 97.753468"
|
||||||
|
id="path6-1"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 7.3 KiB |
|
@ -17,7 +17,7 @@ author = 'Nicolas Kruse'
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||||
|
|
||||||
extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx_autodoc_typehints", "myst_parser"]
|
extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx_autodoc_typehints", "myst_parser", "sphinx.ext.autosummary"]
|
||||||
|
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
exclude_patterns = []
|
exclude_patterns = []
|
||||||
|
@ -26,7 +26,8 @@ exclude_patterns = []
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||||
|
|
||||||
# html_theme = 'alabaster'
|
# html_theme = 'alabaster'
|
||||||
html_theme = 'sphinx_rtd_theme'
|
html_theme = 'pydata_sphinx_theme'
|
||||||
html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
autodoc_inherit_docstrings = True
|
autodoc_inherit_docstrings = True
|
||||||
|
autoclass_content = 'both'
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# gaspype.FloatArray
|
||||||
|
|
||||||
|
```{eval-rst}
|
||||||
|
.. autoclass:: gaspype.FloatArray
|
||||||
|
```
|
|
@ -1,11 +1,17 @@
|
||||||
|
# This script generates the source md-files for all classes and functions for the docs
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from io import TextIOWrapper
|
from io import TextIOWrapper
|
||||||
|
|
||||||
|
|
||||||
def write_classes(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = []) -> None:
|
def write_manual(f: TextIOWrapper, doc_files: list[str], title: str) -> None:
|
||||||
|
write_dochtree(f, title, doc_files)
|
||||||
|
|
||||||
|
|
||||||
|
def write_classes(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = []) -> None:
|
||||||
|
"""Write the classes to the file."""
|
||||||
module = importlib.import_module(module_name)
|
module = importlib.import_module(module_name)
|
||||||
|
|
||||||
classes = [
|
classes = [
|
||||||
|
@ -15,26 +21,25 @@ def write_classes(f: TextIOWrapper, patterns: list[str], module_name: str, title
|
||||||
obj.__doc__ and '(Automatic generated stub)' not in obj.__doc__)
|
obj.__doc__ and '(Automatic generated stub)' not in obj.__doc__)
|
||||||
]
|
]
|
||||||
|
|
||||||
"""Write the classes to the file."""
|
|
||||||
f.write(f'## {title}\n\n')
|
|
||||||
if description:
|
if description:
|
||||||
f.write(f'{description}\n\n')
|
f.write(f'{description}\n\n')
|
||||||
|
|
||||||
|
write_dochtree(f, title, classes)
|
||||||
|
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
f.write('```{eval-rst}\n')
|
with open(f'docs/source/_autogenerated/{cls}.md', 'w') as f2:
|
||||||
f.write(f'.. autoclass:: {module_name}.{cls}\n')
|
f2.write(f'# {module_name}.{cls}\n')
|
||||||
f.write(' :members:\n')
|
f2.write('```{eval-rst}\n')
|
||||||
f.write(' :class-doc-from: both\n')
|
f2.write(f'.. autoclass:: {module_name}.{cls}\n')
|
||||||
f.write(' :undoc-members:\n')
|
f2.write(' :members:\n')
|
||||||
f.write(' :show-inheritance:\n')
|
f2.write(' :undoc-members:\n')
|
||||||
f.write(' :inherited-members:\n')
|
f2.write(' :show-inheritance:\n')
|
||||||
if title != 'Base classes':
|
f2.write(' :inherited-members:\n')
|
||||||
f.write(' :exclude-members: select\n')
|
f2.write('```\n\n')
|
||||||
f.write('```\n\n')
|
|
||||||
|
|
||||||
|
|
||||||
def write_functions(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = []) -> None:
|
def write_functions(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = []) -> None:
|
||||||
|
"""Write the classes to the file."""
|
||||||
module = importlib.import_module(module_name)
|
module = importlib.import_module(module_name)
|
||||||
|
|
||||||
functions = [
|
functions = [
|
||||||
|
@ -43,19 +48,37 @@ def write_functions(f: TextIOWrapper, patterns: list[str], module_name: str, tit
|
||||||
any(fnmatch.fnmatch(name, pat) for pat in patterns if pat not in exclude))
|
any(fnmatch.fnmatch(name, pat) for pat in patterns if pat not in exclude))
|
||||||
]
|
]
|
||||||
|
|
||||||
"""Write the classes to the file."""
|
|
||||||
f.write(f'## {title}\n\n')
|
|
||||||
if description:
|
if description:
|
||||||
f.write(f'{description}\n\n')
|
f.write(f'{description}\n\n')
|
||||||
|
|
||||||
|
write_dochtree(f, title, functions)
|
||||||
|
|
||||||
for func in functions:
|
for func in functions:
|
||||||
if not func.startswith('_'):
|
if not func.startswith('_'):
|
||||||
f.write('```{eval-rst}\n')
|
with open(f'docs/source/_autogenerated/{func}.md', 'w') as f2:
|
||||||
f.write(f'.. autofunction:: {module_name}.{func}\n')
|
f2.write(f'# {module_name}.{func}\n')
|
||||||
|
f2.write('```{eval-rst}\n')
|
||||||
|
f2.write(f'.. autofunction:: {module_name}.{func}\n')
|
||||||
|
f2.write('```\n\n')
|
||||||
|
|
||||||
|
|
||||||
|
def write_dochtree(f: TextIOWrapper, title: str, items: list[str]):
|
||||||
|
f.write('```{toctree}\n')
|
||||||
|
f.write(':maxdepth: 1\n')
|
||||||
|
f.write(f':caption: {title}:\n')
|
||||||
|
#f.write(':hidden:\n')
|
||||||
|
for text in items:
|
||||||
|
if not text.startswith('_'):
|
||||||
|
f.write(f"{text}\n")
|
||||||
f.write('```\n\n')
|
f.write('```\n\n')
|
||||||
|
|
||||||
|
|
||||||
with open('docs/source/modules.md', 'w') as f:
|
if __name__ == "__main__":
|
||||||
f.write('# Functions and classes\n\n')
|
with open('docs/source/_autogenerated/index.md', 'w') as f:
|
||||||
|
f.write('# Classes and functions\n\n')
|
||||||
|
|
||||||
write_classes(f, ['*'], 'gaspype', title='Classes')
|
write_classes(f, ['*'], 'gaspype', title='Classes')
|
||||||
|
|
||||||
write_functions(f, ['*'], 'gaspype', title='Functions')
|
write_functions(f, ['*'], 'gaspype', title='Functions')
|
||||||
|
|
||||||
|
write_manual(f, ['../ndfloat', '../floatarray'], title='Types')
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
```{toctree}
|
```{toctree}
|
||||||
:maxdepth: 2
|
:maxdepth: 1
|
||||||
:caption: Contents:
|
:hidden:
|
||||||
|
_autogenerated/index
|
||||||
readme
|
_autogenerated/examples
|
||||||
modules
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```{include} ../../README.md
|
```{include} ../../README.md
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# gaspype.NDFloat
|
||||||
|
|
||||||
|
```{eval-rst}
|
||||||
|
.. autoclass:: gaspype.NDFloat
|
||||||
|
```
|
|
@ -1,2 +0,0 @@
|
||||||
```{include} ../../README.md
|
|
||||||
```
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
# This script converts the example md-files as jupyter notebook,
|
||||||
|
# execute the notebook and convert the notebook back to a md-file
|
||||||
|
# with outputs included.
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from glob import glob
|
||||||
|
import os
|
||||||
|
from io import TextIOWrapper
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(command: list[str]):
|
||||||
|
result = subprocess.run(command, capture_output=True, text=True)
|
||||||
|
|
||||||
|
print('> ' + ' '.join(command))
|
||||||
|
print(result.stdout)
|
||||||
|
|
||||||
|
assert (not result.stderr or
|
||||||
|
any('RuntimeWarning: ' in line for line in result.stderr.splitlines()) or
|
||||||
|
any('[NbConvertApp]' in line and 'error' not in line.lower() for line in result.stderr.splitlines())), 'ERROR: ' + result.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def run_rendering(input_path: str, output_directory: str):
|
||||||
|
file_name = '.'.join(os.path.basename(input_path).split('.')[:-1])
|
||||||
|
assert file_name
|
||||||
|
|
||||||
|
print(f'- Convert {input_path} ...')
|
||||||
|
run_cmd(['notedown', input_path, '--to', 'notebook', '--output', f'{output_directory}/{file_name}.ipynb', '--run'])
|
||||||
|
run_cmd(['jupyter', 'nbconvert', '--to', 'markdown', f'{output_directory}/{file_name}.ipynb', '--output', f'{file_name}.md'])
|
||||||
|
run_cmd(['python', 'tests/md_to_code.py', 'script', f'{input_path}', f'{output_directory}/{file_name}.py'])
|
||||||
|
|
||||||
|
|
||||||
|
def write_dochtree(f: TextIOWrapper, title: str, items: list[str]):
|
||||||
|
f.write('```{toctree}\n')
|
||||||
|
f.write(':maxdepth: 1\n')
|
||||||
|
#f.write(':hidden:\n')
|
||||||
|
#f.write(f':caption: {title}:\n')
|
||||||
|
for text in items:
|
||||||
|
if not text.startswith('_'):
|
||||||
|
f.write(f"{text}\n")
|
||||||
|
f.write('```\n\n')
|
||||||
|
|
||||||
|
|
||||||
|
def render_examples(filter: str, example_file: str):
|
||||||
|
files = glob(filter)
|
||||||
|
names = ['.'.join(os.path.basename(path).split('.')[:-1]) for path in files]
|
||||||
|
|
||||||
|
with open(example_file, 'w') as f:
|
||||||
|
f.write('# Gaspype examples\n\n')
|
||||||
|
write_dochtree(f, '', [n for n in names if n.lower() != 'readme'])
|
||||||
|
|
||||||
|
f.write('## Download Jupyter Notebooks\n\n')
|
||||||
|
for path, name in zip(files, names):
|
||||||
|
if name.lower() != 'readme':
|
||||||
|
run_rendering(path, 'docs/source/_autogenerated')
|
||||||
|
notebook = name + '.ipynb'
|
||||||
|
f.write(f'- [{notebook}]({notebook})\n\n')
|
||||||
|
|
||||||
|
f.write('## Download plain python files\n\n')
|
||||||
|
for path, name in zip(files, names):
|
||||||
|
if name.lower() != 'readme':
|
||||||
|
script_name = name + '.py'
|
||||||
|
f.write(f'- [{script_name}]({script_name})\n\n')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
render_examples('examples/*.md', 'docs/source/_autogenerated/examples.md')
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Example scripts
|
||||||
|
|
||||||
|
Examples can be looked-up in the
|
||||||
|
[documentation](https://dlr-institute-of-future-fuels.github.io/gaspype/)
|
||||||
|
rendered with results.
|
||||||
|
|
||||||
|
The gaspype examples from this directory are available in the documentation as
|
||||||
|
downloadable Jupyter Notebooks or plain python scripts with comments.
|
||||||
|
|
||||||
|
The conversion is done like the following automated by the
|
||||||
|
[docs/source/render_examples.py](../docs/source/render_examples.py) script:
|
||||||
|
``` bash
|
||||||
|
# Converting markdown with code sections to Jupyter Notebook and run it:
|
||||||
|
notedown examples/soec_methane.md --to notebook --output docs/source/_autogenerated/soec_methane.ipynb --run
|
||||||
|
|
||||||
|
# Converting the Jupyter Notebook to Markdown and a folder with image
|
||||||
|
# files placed in docs/source/_autogenerated/:
|
||||||
|
jupyter nbconvert --to markdown docs/source/_autogenerated/soec_methane.ipynb --output soec_methane.md
|
||||||
|
```
|
||||||
|
|
||||||
|
A new example Markdown file can be created from a Jupyter Notebook running
|
||||||
|
the following command:
|
||||||
|
``` bash
|
||||||
|
jupyter nbconvert --to markdown new_example.ipynb --NbConvertApp.use_output_suffix=False --ClearOutputPreprocessor.enabled=True --output-dir examples/ --output new_example.md
|
||||||
|
```
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Carbon Activity
|
||||||
|
|
||||||
|
This example shows the equilibrium calculation for solid carbon.
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
import gaspype as gp
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting temperatures and pressure:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
t_range = np.array([600, 700, 800, 900, 1100, 1500]) # °C
|
||||||
|
|
||||||
|
p = 1e5 # Pa
|
||||||
|
|
||||||
|
fs = gp.fluid_system(['H2', 'H2O', 'CO2', 'CO', 'CH4'])
|
||||||
|
```
|
||||||
|
|
||||||
|
Equilibrium calculation for methane steam mixtures:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
ratio = np.linspace(0.01, 1.5, num=128)
|
||||||
|
|
||||||
|
fl = gp.fluid({'CH4': 1}, fs) + ratio * gp.fluid({'H2O': 1}, fs)
|
||||||
|
```
|
||||||
|
|
||||||
|
gaspype.carbon_activity supports currently only 0D fluids therefore we build this helper function:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
def partial_c_activity(fl: gp.fluid, t: float, p: float):
|
||||||
|
fls = fl.array_composition.shape
|
||||||
|
|
||||||
|
eq_fl = gp.equilibrium(fl, t, p)
|
||||||
|
|
||||||
|
ret = np.zeros(fls[0])
|
||||||
|
for i in range(fls[0]):
|
||||||
|
ret[i] = gp.carbon_activity(gp.fluid(eq_fl.array_composition[i,:], fs), t, p)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we use the helper function to calculate the carbon activitx for all
|
||||||
|
compositions in equilibrium_h2o times all temperatures in t_range:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
carbon_activity = np.vstack([partial_c_activity(fl, tc + 273.15, p) for tc in t_range])
|
||||||
|
```
|
||||||
|
|
||||||
|
Plot carbon activities, a activity of > 1 means there is thermodynamically the formation of sold carbon favored.
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
fig, ax = plt.subplots(figsize=(6, 4), dpi=120)
|
||||||
|
ax.set_xlabel("CO2/CH4")
|
||||||
|
ax.set_ylabel("carbon activity")
|
||||||
|
ax.set_ylim(1e-1, 1e3)
|
||||||
|
ax.set_yscale('log')
|
||||||
|
ax.plot(ratio, carbon_activity.T)
|
||||||
|
ax.hlines(1, np.min(ratio), np.max(ratio), colors='k', linestyles='dashed')
|
||||||
|
ax.legend([f'{tc} °C' for tc in t_range])
|
||||||
|
```
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Methane Mixtures
|
||||||
|
|
||||||
|
This example shows equilibria of methane mixed with steam and CO2
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
import gaspype as gp
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting temperature and pressure:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
t = 900 + 273.15
|
||||||
|
p = 1e5
|
||||||
|
|
||||||
|
fs = gp.fluid_system(['H2', 'H2O', 'CO2', 'CO', 'CH4', 'O2'])
|
||||||
|
```
|
||||||
|
|
||||||
|
Equilibrium calculation for methane steam mixtures:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
ratio = np.linspace(0.01, 1.5, num=64)
|
||||||
|
|
||||||
|
el = gp.fluid({'CH4': 1}, fs) + ratio * gp.fluid({'H2O': 1}, fs)
|
||||||
|
equilibrium_h2o = gp.equilibrium(el, t, p)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
fig, ax = plt.subplots(figsize=(6, 4), dpi=120)
|
||||||
|
ax.set_xlabel("H2O/CH4")
|
||||||
|
ax.set_ylabel("molar fraction")
|
||||||
|
ax.set_ylim(0, 1.1)
|
||||||
|
#ax.set_xlim(0, 100)
|
||||||
|
ax.plot(ratio, equilibrium_h2o.get_x())
|
||||||
|
ax.legend(fs.active_species)
|
||||||
|
```
|
||||||
|
|
||||||
|
Equilibrium calculation for methane CO2 mixtures:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
el = gp.fluid({'CH4': 1}, fs) + ratio * gp.fluid({'H2O': 1}, fs)
|
||||||
|
equilibrium_co2 = gp.equilibrium(el, t, p)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
fig, ax = plt.subplots(figsize=(6, 4), dpi=120)
|
||||||
|
ax.set_xlabel("CO2/CH4")
|
||||||
|
ax.set_ylabel("molar fraction")
|
||||||
|
ax.set_ylim(0, 1.1)
|
||||||
|
#ax.set_xlim(0, 100)
|
||||||
|
ax.plot(ratio, equilibrium_co2.get_x())
|
||||||
|
ax.legend(fs.active_species)
|
||||||
|
```
|
|
@ -0,0 +1,115 @@
|
||||||
|
# SOEC with Methane
|
||||||
|
|
||||||
|
This example shows a 1D isothermal SOEC (Solid oxide electrolyzer cell) model.
|
||||||
|
|
||||||
|
The operating parameters chosen here are not necessary realistic
|
||||||
|
|
||||||
|
```python
|
||||||
|
import gaspype as gp
|
||||||
|
from gaspype import R, F
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
```
|
||||||
|
|
||||||
|
Calculation of the local equilibrium compositions on the fuel and air
|
||||||
|
side in counter flow along the fuel flow direction:
|
||||||
|
```python
|
||||||
|
fuel_utilization = 0.90
|
||||||
|
air_utilization = 0.5
|
||||||
|
t = 800 + 273.15 #K
|
||||||
|
p = 1e5 #Pa
|
||||||
|
|
||||||
|
fs = gp.fluid_system('H2, H2O, O2, CH4, CO, CO2')
|
||||||
|
feed_fuel = gp.fluid({'CH4': 1, 'H2O': 0.1}, fs)
|
||||||
|
|
||||||
|
o2_full_conv = np.sum(gp.elements(feed_fuel)[['H', 'C' ,'O']] * [1/4, 1, -1/2])
|
||||||
|
|
||||||
|
feed_air = gp.fluid({'O2': 1, 'N2': 4}) * o2_full_conv / air_utilization
|
||||||
|
|
||||||
|
conversion = np.linspace(0, fuel_utilization, 32)
|
||||||
|
perm_oxygen = o2_full_conv * conversion * gp.fluid({'O2': 1})
|
||||||
|
|
||||||
|
fuel_side = gp.equilibrium(feed_fuel + perm_oxygen, t, p)
|
||||||
|
air_side = gp.equilibrium(feed_air - perm_oxygen, t, p)
|
||||||
|
```
|
||||||
|
|
||||||
|
Plot compositions of the fuel and air side:
|
||||||
|
```python
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.set_xlabel("Conversion")
|
||||||
|
ax.set_ylabel("Molar fraction")
|
||||||
|
ax.plot(conversion, fuel_side.get_x(), '-')
|
||||||
|
ax.legend(fuel_side.species)
|
||||||
|
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.set_xlabel("Conversion")
|
||||||
|
ax.set_ylabel("Molar fraction")
|
||||||
|
ax.plot(conversion, air_side.get_x(), '-')
|
||||||
|
ax.legend(air_side.species)
|
||||||
|
```
|
||||||
|
|
||||||
|
Calculation of the oxygen partial pressures:
|
||||||
|
```python
|
||||||
|
o2_fuel_side = gp.oxygen_partial_pressure(fuel_side, t, p)
|
||||||
|
o2_air_side = air_side.get_x('O2') * p
|
||||||
|
```
|
||||||
|
|
||||||
|
Plot oxygen partial pressures:
|
||||||
|
```python
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.set_xlabel("Conversion")
|
||||||
|
ax.set_ylabel("Oxygen partial pressure / Pa")
|
||||||
|
ax.set_yscale('log')
|
||||||
|
ax.plot(conversion, np.stack([o2_fuel_side, o2_air_side], axis=1), '-')
|
||||||
|
ax.legend(['o2_fuel_side', 'o2_air_side'])
|
||||||
|
```
|
||||||
|
|
||||||
|
Calculation of the local nernst potential between fuel and air side:
|
||||||
|
```python
|
||||||
|
z_O2 = 4
|
||||||
|
nernst_voltage = R*t / (z_O2*F) * np.log(o2_air_side/o2_fuel_side)
|
||||||
|
```
|
||||||
|
|
||||||
|
#Plot nernst potential:
|
||||||
|
```python
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.set_xlabel("Conversion")
|
||||||
|
ax.set_ylabel("Voltage / V")
|
||||||
|
ax.plot(conversion, nernst_voltage, '-')
|
||||||
|
print(np.min(nernst_voltage))
|
||||||
|
```
|
||||||
|
|
||||||
|
The model uses between each node a constant conversion. Because
|
||||||
|
current density depends strongly on the position along the cell
|
||||||
|
the constant conversion does not relate to a constant distance.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
To calculate the local current density (**node_current**) as well
|
||||||
|
as the total cell current (**terminal_current**) the (relative)
|
||||||
|
physical distance between the nodes (**dz**) must be calculated:
|
||||||
|
```python
|
||||||
|
cell_voltage = 0.77 #V
|
||||||
|
ASR = 0.2 #Ohm*cm²
|
||||||
|
|
||||||
|
node_current = (nernst_voltage - cell_voltage) / ASR # mA/cm² (Current density at each node)
|
||||||
|
|
||||||
|
current = (node_current[1:] + node_current[:-1]) / 2 # mA/cm² (Average current density between the nodes)
|
||||||
|
|
||||||
|
dz = 1/current / np.sum(1/current) # Relative distance between each node
|
||||||
|
|
||||||
|
terminal_current = np.sum(current * dz) # mA/cm² (Total cell current per cell area)
|
||||||
|
|
||||||
|
print(f'Terminal current: {terminal_current:.2f} A/cm²')
|
||||||
|
```
|
||||||
|
|
||||||
|
Plot the local current density:
|
||||||
|
```python
|
||||||
|
z_position = np.concatenate([[0], np.cumsum(dz)]) # Relative position of each node
|
||||||
|
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.set_xlabel("Relative cell position")
|
||||||
|
ax.set_ylabel("Current density / A/cm²")
|
||||||
|
ax.plot(z_position, node_current, '-')
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Sulfur Oxygen Equilibrium
|
||||||
|
|
||||||
|
This example shows equilibrium calculations for sulfur/oxygen mixtures.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import gaspype as gp
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
```
|
||||||
|
|
||||||
|
List possible sulfur/oxygen species:
|
||||||
|
|
||||||
|
```python
|
||||||
|
gp.species(element_names = 'S, O')
|
||||||
|
```
|
||||||
|
|
||||||
|
Or more specific by using regular expressions:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
gp.species('S?[2-3]?O?[2-5]?', use_regex=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
Calculation of the molar equilibrium fractions for sulfur and oxygen depending on the oxygen to sulfur ratio:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
fs = gp.fluid_system(['S2', 'S2O', 'SO2', 'SO3', 'O2'])
|
||||||
|
|
||||||
|
oxygen_ratio = np.linspace(0.5, 3, num=128)
|
||||||
|
el = gp.elements({'S': 1}, fs) + oxygen_ratio * gp.elements({'O': 1}, fs)
|
||||||
|
|
||||||
|
composition = gp.equilibrium(el, 800+273.15, 1e4)
|
||||||
|
|
||||||
|
plt.plot(oxygen_ratio, composition.get_x())
|
||||||
|
plt.legend(composition.species)
|
||||||
|
```
|
||||||
|
|
||||||
|
Calculation of the molar equilibrium fractions for sulfur and oxygen depending on temperature in °C:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
fs = gp.fluid_system(['S2', 'S2O', 'SO2', 'SO3', 'O2'])
|
||||||
|
|
||||||
|
el = gp.elements({'S': 1, 'O':2.5}, fs)
|
||||||
|
|
||||||
|
t_range = np.linspace(500, 1300, num=32)
|
||||||
|
composition = gp.equilibrium(el, t_range+273.15, 1e4)
|
||||||
|
|
||||||
|
plt.plot(t_range, composition.get_x())
|
||||||
|
plt.legend(composition.species)
|
||||||
|
```
|
|
@ -41,7 +41,20 @@ dev = [
|
||||||
"cantera",
|
"cantera",
|
||||||
"pyyaml>=6.0.1",
|
"pyyaml>=6.0.1",
|
||||||
"types-PyYAML",
|
"types-PyYAML",
|
||||||
"scipy-stubs"
|
"scipy-stubs",
|
||||||
|
"matplotlib"
|
||||||
|
]
|
||||||
|
doc_build = [
|
||||||
|
"sphinx",
|
||||||
|
"pydata_sphinx_theme",
|
||||||
|
"sphinx-autodoc-typehints",
|
||||||
|
"myst-parser",
|
||||||
|
"pandas",
|
||||||
|
"matplotlib",
|
||||||
|
"ipykernel",
|
||||||
|
"jupyter",
|
||||||
|
"nbconvert",
|
||||||
|
"notedown"
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
|
|
|
@ -29,7 +29,7 @@ p_atm = 101325 # Pa
|
||||||
_epsy = 1e-18
|
_epsy = 1e-18
|
||||||
|
|
||||||
|
|
||||||
def lookup(prop_array: FloatArray,
|
def _lookup(prop_array: FloatArray,
|
||||||
temperature: FloatArray | float,
|
temperature: FloatArray | float,
|
||||||
t_offset: float) -> FloatArray:
|
t_offset: float) -> FloatArray:
|
||||||
"""linear interpolates values from the given prop_array
|
"""linear interpolates values from the given prop_array
|
||||||
|
@ -58,8 +58,8 @@ def species(pattern: str = '*', element_names: str | list[str] = [], use_regex:
|
||||||
Args:
|
Args:
|
||||||
pattern: Optional filter for specific molecules
|
pattern: Optional filter for specific molecules
|
||||||
Placeholder characters:
|
Placeholder characters:
|
||||||
# A number including non written ones: 'C#H#' matches 'CH4'
|
# A number including non written ones: 'C#H#' matches 'CH4';
|
||||||
$ Arbitrary element name
|
$ Arbitrary element name;
|
||||||
* Any sequence of characters
|
* Any sequence of characters
|
||||||
element_names:
|
element_names:
|
||||||
restrict results to species that contain only the specified elements.
|
restrict results to species that contain only the specified elements.
|
||||||
|
@ -95,7 +95,17 @@ def species(pattern: str = '*', element_names: str | list[str] = [], use_regex:
|
||||||
|
|
||||||
|
|
||||||
def set_solver(solver: Literal['gibs minimization', 'system of equations']) -> None:
|
def set_solver(solver: Literal['gibs minimization', 'system of equations']) -> None:
|
||||||
"""Select a solver for chemical equilibrium.
|
"""
|
||||||
|
Select a solver for chemical equilibrium.
|
||||||
|
|
||||||
|
Solvers:
|
||||||
|
- **system of equations** (default): Finds the root for a system of
|
||||||
|
equations covering a minimal set of equilibrium equations and elemental balance.
|
||||||
|
The minimal set of equilibrium equations is derived by SVD using the null_space
|
||||||
|
implementation of scipy.
|
||||||
|
|
||||||
|
- **gibs minimization**: Minimizes the total Gibbs Enthalpy while keeping
|
||||||
|
the elemental composition constant using the SLSQP implementation of scipy
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
solver: Name of the solver
|
solver: Name of the solver
|
||||||
|
@ -127,10 +137,10 @@ class fluid_system:
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
species_names (list[str]): List of selected species in the fluid_system
|
species_names (list[str]): List of selected species in the fluid_system
|
||||||
array_molar_mass: Array of the molar masses of the species in the fluid_system
|
array_molar_mass (FloatArray): Array of the molar masses of the species in the fluid_system
|
||||||
array_element_composition: Array of the element composition of the species in the fluid_system.
|
array_element_composition (FloatArray): Array of the element composition of the species in the fluid_system.
|
||||||
Dimension is: (number of species, number of elements)
|
Dimension is: (number of species, number of elements)
|
||||||
array_atomic_mass: Array of the atomic masses of the elements in the fluid_system
|
array_atomic_mass (FloatArray): Array of the atomic masses of the elements in the fluid_system
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, species: list[str] | str, t_min: int = 250, t_max: int = 2000):
|
def __init__(self, species: list[str] | str, t_min: int = 250, t_max: int = 2000):
|
||||||
|
@ -202,7 +212,7 @@ class fluid_system:
|
||||||
Returns:
|
Returns:
|
||||||
Array with the enthalpies of each specie in J/mol
|
Array with the enthalpies of each specie in J/mol
|
||||||
"""
|
"""
|
||||||
return lookup(self._h_array, t, self._t_offset)
|
return _lookup(self._h_array, t, self._t_offset)
|
||||||
|
|
||||||
def get_species_s(self, t: float | FloatArray) -> FloatArray:
|
def get_species_s(self, t: float | FloatArray) -> FloatArray:
|
||||||
"""Get the molar entropies for all species in the fluid system
|
"""Get the molar entropies for all species in the fluid system
|
||||||
|
@ -213,7 +223,7 @@ class fluid_system:
|
||||||
Returns:
|
Returns:
|
||||||
Array with the entropies of each specie in J/mol/K
|
Array with the entropies of each specie in J/mol/K
|
||||||
"""
|
"""
|
||||||
return lookup(self._s_array, t, self._t_offset)
|
return _lookup(self._s_array, t, self._t_offset)
|
||||||
|
|
||||||
def get_species_cp(self, t: float | FloatArray) -> FloatArray:
|
def get_species_cp(self, t: float | FloatArray) -> FloatArray:
|
||||||
"""Get the isobaric molar heat capacity for all species in the fluid system
|
"""Get the isobaric molar heat capacity for all species in the fluid system
|
||||||
|
@ -224,7 +234,7 @@ class fluid_system:
|
||||||
Returns:
|
Returns:
|
||||||
Array with the heat capacities of each specie in J/mol/K
|
Array with the heat capacities of each specie in J/mol/K
|
||||||
"""
|
"""
|
||||||
return lookup(self._cp_array, t, self._t_offset)
|
return _lookup(self._cp_array, t, self._t_offset)
|
||||||
|
|
||||||
# def get_species_g(self, t: float | NDArray[_Float]) -> NDArray[_Float]:
|
# def get_species_g(self, t: float | NDArray[_Float]) -> NDArray[_Float]:
|
||||||
# return lookup(self._g_array, t, self._t_offset)
|
# return lookup(self._g_array, t, self._t_offset)
|
||||||
|
@ -239,7 +249,7 @@ class fluid_system:
|
||||||
Returns:
|
Returns:
|
||||||
Array of gibbs free energy divided by RT (dimensionless)
|
Array of gibbs free energy divided by RT (dimensionless)
|
||||||
"""
|
"""
|
||||||
return lookup(self._g_rt_array, t, self._t_offset)
|
return _lookup(self._g_rt_array, t, self._t_offset)
|
||||||
|
|
||||||
def get_species_references(self) -> str:
|
def get_species_references(self) -> str:
|
||||||
"""Get a string with the references for all fluids of the fluid system
|
"""Get a string with the references for all fluids of the fluid system
|
||||||
|
@ -263,13 +273,12 @@ class fluid:
|
||||||
one or more species.
|
one or more species.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
fs: Reference to the fluid_system used for this fluid
|
species (list[str]): List of species names in the associated fluid_system
|
||||||
species: List of species names in the associated fluid_system
|
array_composition (FloatArray): Array of the molar amounts of the species in the fluid
|
||||||
array_composition: Array of the molar amounts of the species in the fluid
|
array_element_composition (FloatArray): Array of the element composition in the fluid
|
||||||
array_element_composition: Array of the element composition in the fluid
|
array_fractions (FloatArray): Array of the molar fractions of the species in the fluid
|
||||||
array_fractions: Array of the molar fractions of the species in the fluid
|
total (FloatArray | float): Array of the sums of the molar amount of all species
|
||||||
total: Array of the sums of the molar amount of all species
|
fs (fluid_system): Reference to the fluid_system used for this fluid
|
||||||
fs: Reference to the fluid_system used for this fluid
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__array_priority__ = 100
|
__array_priority__ = 100
|
||||||
|
@ -606,7 +615,7 @@ class elements:
|
||||||
"""Represent a fluid by composition of elements.
|
"""Represent a fluid by composition of elements.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
array_element_composition: Array of the element composition
|
array_element_composition (FloatArray): Array of the element composition
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__array_priority__ = 100
|
__array_priority__ = 100
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
import re
|
||||||
|
from typing import Generator, Iterable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class markdown_segment:
|
||||||
|
code_block: bool
|
||||||
|
language: str
|
||||||
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to(target_format: str, md_filename: str, out_filename: str, language: str = 'python'):
|
||||||
|
with open(md_filename, "r") as f_in, open(out_filename, "w") as f_out:
|
||||||
|
segments = segment_markdown(f_in)
|
||||||
|
|
||||||
|
if target_format == 'test':
|
||||||
|
f_out.write('\n'.join(segments_to_test(segments, language)))
|
||||||
|
elif target_format == 'script':
|
||||||
|
f_out.write('\n'.join(segments_to_script(segments, language)))
|
||||||
|
elif target_format == 'striped_markdown':
|
||||||
|
f_out.write('\n'.join(segments_to_striped_markdown(segments, language)))
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown target format')
|
||||||
|
|
||||||
|
|
||||||
|
def segment_markdown(markdown_file: Iterable[str]) -> Generator[markdown_segment, None, None]:
|
||||||
|
regex = re.compile(r"(?:^```\s*(?P<language>(?:\w|-)*)$)", re.MULTILINE)
|
||||||
|
|
||||||
|
block_language: str = ''
|
||||||
|
code_block = False
|
||||||
|
line_buffer: list[str] = []
|
||||||
|
|
||||||
|
for line in markdown_file:
|
||||||
|
match = regex.match(line)
|
||||||
|
if match:
|
||||||
|
if line_buffer:
|
||||||
|
yield markdown_segment(code_block, block_language, ''.join(line_buffer))
|
||||||
|
line_buffer.clear()
|
||||||
|
block_language = match.group('language')
|
||||||
|
code_block = not code_block
|
||||||
|
else:
|
||||||
|
line_buffer.append(line)
|
||||||
|
|
||||||
|
if line_buffer:
|
||||||
|
yield markdown_segment(code_block, block_language, '\n'.join(line_buffer))
|
||||||
|
|
||||||
|
|
||||||
|
def segments_to_script(segments: Iterable[markdown_segment], test_language: str = "python") -> Generator[str, None, None]:
|
||||||
|
for segment in segments:
|
||||||
|
if segment.code_block:
|
||||||
|
if segment.language == test_language:
|
||||||
|
yield segment.text
|
||||||
|
|
||||||
|
else:
|
||||||
|
for line in segment.text.splitlines():
|
||||||
|
yield '# | ' + line
|
||||||
|
yield ''
|
||||||
|
|
||||||
|
else:
|
||||||
|
for line in segment.text.strip(' \n').splitlines():
|
||||||
|
yield '# ' + line
|
||||||
|
yield ''
|
||||||
|
|
||||||
|
|
||||||
|
def segments_to_striped_markdown(segments: Iterable[markdown_segment], test_language: str = "python") -> Generator[str, None, None]:
|
||||||
|
for segment in segments:
|
||||||
|
if segment.code_block:
|
||||||
|
if segment.language == test_language:
|
||||||
|
yield "``` " + test_language
|
||||||
|
yield segment.text
|
||||||
|
yield "```"
|
||||||
|
|
||||||
|
elif segment.language:
|
||||||
|
for line in segment.text.splitlines():
|
||||||
|
yield '# | ' + line
|
||||||
|
yield ''
|
||||||
|
|
||||||
|
else:
|
||||||
|
for line in segment.text.strip(' \n').splitlines():
|
||||||
|
yield '# ' + line
|
||||||
|
yield ''
|
||||||
|
|
||||||
|
|
||||||
|
def segments_to_test(segments: Iterable[markdown_segment], script_language: str = "python") -> Generator[str, None, None]:
|
||||||
|
|
||||||
|
ret_block_flag = False
|
||||||
|
|
||||||
|
yield 'def run_test():'
|
||||||
|
|
||||||
|
for segment in segments:
|
||||||
|
if segment.code_block:
|
||||||
|
if segment.language == script_language:
|
||||||
|
lines = [line for line in segment.text.splitlines() if line.strip()]
|
||||||
|
ret_block_flag = lines[-1] if (not re.match(r'^[^(]*=', lines[-1]) and
|
||||||
|
not lines[-1].startswith('import ') and
|
||||||
|
not lines[-1].startswith('from ') and
|
||||||
|
not lines[-1].startswith(' ')) else None
|
||||||
|
# print('Last line: ', ret_block_flag, '-----------', lines[-1])
|
||||||
|
|
||||||
|
yield ''
|
||||||
|
yield ' print("---------------------------------------------------------")'
|
||||||
|
yield ''
|
||||||
|
if ret_block_flag:
|
||||||
|
yield from [' ' + str(line) for line in segment.text.splitlines()[:-1]]
|
||||||
|
yield f' print("-- Result (({ret_block_flag})):")'
|
||||||
|
yield f' print(({ret_block_flag}).__repr__().strip())'
|
||||||
|
else:
|
||||||
|
yield from [' ' + str(line) for line in segment.text.splitlines()]
|
||||||
|
|
||||||
|
elif ret_block_flag:
|
||||||
|
yield ' ref_str = r"""'
|
||||||
|
yield from [str(line) for line in segment.text.splitlines()]
|
||||||
|
yield '"""'
|
||||||
|
yield f' print("-- Reference (({ret_block_flag})):")'
|
||||||
|
yield ' print(ref_str.strip())'
|
||||||
|
yield f' assert ({ret_block_flag}).__repr__().strip() == ref_str.strip()'
|
||||||
|
ret_block_flag = False
|
||||||
|
|
||||||
|
yield '\nif __name__ == "__main__":'
|
||||||
|
yield ' run_test()'
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
format = sys.argv[1]
|
||||||
|
assert format in ['test', 'script']
|
||||||
|
convert_to(sys.argv[1], sys.argv[2], sys.argv[3])
|
|
@ -1,53 +1,28 @@
|
||||||
import re
|
import md_to_code
|
||||||
from typing import Generator
|
from glob import glob
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
def convert_markdown_file(md_filename: str, out_filename: str):
|
|
||||||
with open(md_filename, "r") as f_in:
|
|
||||||
with open(out_filename, "w") as f_out:
|
|
||||||
f_out.write('def run_test():\n')
|
|
||||||
for block in markdown_to_code([line for line in f_in]):
|
|
||||||
f_out.write(block + '\n')
|
|
||||||
|
|
||||||
|
|
||||||
def markdown_to_code(lines: list[str], language: str = "python") -> Generator[str, None, None]:
|
|
||||||
regex = re.compile(
|
|
||||||
r"(?P<start>^```\s*(?P<block_language>(\w|-)*)\n)(?P<code>.*?\n)(?P<end>```)",
|
|
||||||
re.DOTALL | re.MULTILINE,
|
|
||||||
)
|
|
||||||
blocks = [
|
|
||||||
(match.group("block_language"), match.group("code"))
|
|
||||||
for match in regex.finditer("".join(lines))
|
|
||||||
]
|
|
||||||
|
|
||||||
ret_block_flag = False
|
|
||||||
|
|
||||||
for block_language, block in blocks:
|
|
||||||
if block_language == language:
|
|
||||||
lines = [line for line in block.splitlines() if line.strip()]
|
|
||||||
ret_block_flag = lines[-1] if '=' not in lines[-1] else None
|
|
||||||
|
|
||||||
yield ''
|
|
||||||
yield ' print("---------------------------------------------------------")'
|
|
||||||
yield ''
|
|
||||||
if ret_block_flag:
|
|
||||||
yield from [' ' + str(line) for line in block.splitlines()[:-1]]
|
|
||||||
else:
|
|
||||||
yield from [' ' + str(line) for line in block.splitlines()]
|
|
||||||
yield f' print("-- Result (({ret_block_flag})):")'
|
|
||||||
yield f' print(({ret_block_flag}).__repr__().strip())'
|
|
||||||
|
|
||||||
elif ret_block_flag:
|
|
||||||
yield ' ref_str = r"""'
|
|
||||||
yield from [str(line) for line in block.splitlines()]
|
|
||||||
yield '"""'
|
|
||||||
yield f' print("-- Reference (({ret_block_flag})):")'
|
|
||||||
yield ' print(ref_str.strip())'
|
|
||||||
yield f' assert ({ret_block_flag}).__repr__().strip() == ref_str.strip()'
|
|
||||||
ret_block_flag = False
|
|
||||||
|
|
||||||
|
|
||||||
def test_readme():
|
def test_readme():
|
||||||
convert_markdown_file('README.md', 'tests/autogenerated_readme.py')
|
md_to_code.convert_to('test', 'README.md', 'tests/autogenerated_readme.py')
|
||||||
import autogenerated_readme
|
import autogenerated_readme
|
||||||
autogenerated_readme.run_test()
|
autogenerated_readme.run_test()
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_code():
|
||||||
|
filter = 'examples/*.md'
|
||||||
|
|
||||||
|
files = glob(filter)
|
||||||
|
for path in files:
|
||||||
|
file_name = '.'.join(os.path.basename(path).split('.')[:-1])
|
||||||
|
if not file_name.lower() == 'readme':
|
||||||
|
print(f"> Test Example {file_name} ...")
|
||||||
|
md_to_code.convert_to('test', path, f'tests/autogenerated_{file_name}.py')
|
||||||
|
mod = importlib.import_module(f'autogenerated_{file_name}')
|
||||||
|
mod.run_test()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_readme()
|
||||||
|
test_example_code()
|
||||||
|
|
Loading…
Reference in New Issue