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,
|
||||
.conda,
|
||||
tests/autogenerated_*,
|
||||
docs/source/_autogenerated
|
||||
.venv,
|
||||
venv
|
||||
|
||||
|
|
|
@ -17,17 +17,20 @@ jobs:
|
|||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install dependencies
|
||||
run: pip install sphinx sphinx_rtd_theme sphinx-autodoc-typehints myst-parser
|
||||
- name: Generate Class List
|
||||
- name: Install gaspype and dependencies
|
||||
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
|
||||
printf 'gapy\x00\x00\x00\x00' > src/gaspype/data/therm_data.bin
|
||||
pip install .
|
||||
python ./docs/source/generate_class_list.py
|
||||
pip install .[doc_build]
|
||||
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
|
||||
run: |
|
||||
cp LICENSE docs/source/LICENSE.md
|
||||
cd docs
|
||||
sphinx-apidoc -o ./source/ ../src/ -M --no-toc
|
||||
rm ./source/*.rst
|
||||
|
|
|
@ -9,7 +9,7 @@ __pycache__
|
|||
.pytest_cache
|
||||
tests/autogenerated_*.py
|
||||
docs/build/
|
||||
docs/source/modules.md
|
||||
docs/source/_autogenerated/
|
||||
venv/
|
||||
.venv/
|
||||
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])
|
||||
```
|
||||
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
|
||||
fl2 = gp.fluid({'H2O': 1, 'N2': 2}) + \
|
||||
|
@ -125,7 +126,8 @@ array([[[0. , 0.5 , 0.5 ],
|
|||
```
|
||||
|
||||
### 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
|
||||
el = gp.elements({'N': 1, 'Cl': 2})
|
||||
|
@ -134,7 +136,9 @@ el.get_mass()
|
|||
```
|
||||
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
|
||||
el2 = gp.elements(fl) + el - 0.3 * fl
|
||||
el2
|
||||
|
@ -146,7 +150,8 @@ N 1.000e+00 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
|
||||
fs = gp.fluid_system('CH4, H2, CO, CO2, O2')
|
||||
|
@ -163,7 +168,13 @@ CO2 33.07 %
|
|||
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
|
||||
fl3 + gp.fluid({'CH4': 1}, fs)
|
||||
|
@ -177,7 +188,9 @@ CO2 18.07 %
|
|||
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
|
||||
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 ---------------------------------------------------
|
||||
# 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']
|
||||
exclude_patterns = []
|
||||
|
@ -26,7 +26,8 @@ exclude_patterns = []
|
|||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
# html_theme = 'alabaster'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme = 'pydata_sphinx_theme'
|
||||
html_static_path = ['_static']
|
||||
|
||||
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 inspect
|
||||
import fnmatch
|
||||
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)
|
||||
|
||||
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__)
|
||||
]
|
||||
|
||||
"""Write the classes to the file."""
|
||||
f.write(f'## {title}\n\n')
|
||||
if description:
|
||||
f.write(f'{description}\n\n')
|
||||
|
||||
write_dochtree(f, title, classes)
|
||||
|
||||
for cls in classes:
|
||||
f.write('```{eval-rst}\n')
|
||||
f.write(f'.. autoclass:: {module_name}.{cls}\n')
|
||||
f.write(' :members:\n')
|
||||
f.write(' :class-doc-from: both\n')
|
||||
f.write(' :undoc-members:\n')
|
||||
f.write(' :show-inheritance:\n')
|
||||
f.write(' :inherited-members:\n')
|
||||
if title != 'Base classes':
|
||||
f.write(' :exclude-members: select\n')
|
||||
f.write('```\n\n')
|
||||
with open(f'docs/source/_autogenerated/{cls}.md', 'w') as f2:
|
||||
f2.write(f'# {module_name}.{cls}\n')
|
||||
f2.write('```{eval-rst}\n')
|
||||
f2.write(f'.. autoclass:: {module_name}.{cls}\n')
|
||||
f2.write(' :members:\n')
|
||||
f2.write(' :undoc-members:\n')
|
||||
f2.write(' :show-inheritance:\n')
|
||||
f2.write(' :inherited-members:\n')
|
||||
f2.write('```\n\n')
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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))
|
||||
]
|
||||
|
||||
"""Write the classes to the file."""
|
||||
f.write(f'## {title}\n\n')
|
||||
if description:
|
||||
f.write(f'{description}\n\n')
|
||||
|
||||
write_dochtree(f, title, functions)
|
||||
|
||||
for func in functions:
|
||||
if not func.startswith('_'):
|
||||
f.write('```{eval-rst}\n')
|
||||
f.write(f'.. autofunction:: {module_name}.{func}\n')
|
||||
with open(f'docs/source/_autogenerated/{func}.md', 'w') as f2:
|
||||
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')
|
||||
|
||||
|
||||
with open('docs/source/modules.md', 'w') as f:
|
||||
f.write('# Functions and classes\n\n')
|
||||
if __name__ == "__main__":
|
||||
with open('docs/source/_autogenerated/index.md', 'w') as f:
|
||||
f.write('# Classes and functions\n\n')
|
||||
|
||||
write_classes(f, ['*'], 'gaspype', title='Classes')
|
||||
|
||||
write_functions(f, ['*'], 'gaspype', title='Functions')
|
||||
|
||||
write_manual(f, ['../ndfloat', '../floatarray'], title='Types')
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
```{toctree}
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
readme
|
||||
modules
|
||||
:maxdepth: 1
|
||||
:hidden:
|
||||
_autogenerated/index
|
||||
_autogenerated/examples
|
||||
```
|
||||
|
||||
```{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",
|
||||
"pyyaml>=6.0.1",
|
||||
"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]
|
||||
|
|
|
@ -29,7 +29,7 @@ p_atm = 101325 # Pa
|
|||
_epsy = 1e-18
|
||||
|
||||
|
||||
def lookup(prop_array: FloatArray,
|
||||
def _lookup(prop_array: FloatArray,
|
||||
temperature: FloatArray | float,
|
||||
t_offset: float) -> FloatArray:
|
||||
"""linear interpolates values from the given prop_array
|
||||
|
@ -58,8 +58,8 @@ def species(pattern: str = '*', element_names: str | list[str] = [], use_regex:
|
|||
Args:
|
||||
pattern: Optional filter for specific molecules
|
||||
Placeholder characters:
|
||||
# A number including non written ones: 'C#H#' matches 'CH4'
|
||||
$ Arbitrary element name
|
||||
# A number including non written ones: 'C#H#' matches 'CH4';
|
||||
$ Arbitrary element name;
|
||||
* Any sequence of characters
|
||||
element_names:
|
||||
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:
|
||||
"""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:
|
||||
solver: Name of the solver
|
||||
|
@ -127,10 +137,10 @@ class fluid_system:
|
|||
|
||||
Attributes:
|
||||
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_element_composition: Array of the element composition 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 (FloatArray): Array of the element composition of the species in the fluid_system.
|
||||
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):
|
||||
|
@ -202,7 +212,7 @@ class fluid_system:
|
|||
Returns:
|
||||
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:
|
||||
"""Get the molar entropies for all species in the fluid system
|
||||
|
@ -213,7 +223,7 @@ class fluid_system:
|
|||
Returns:
|
||||
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:
|
||||
"""Get the isobaric molar heat capacity for all species in the fluid system
|
||||
|
@ -224,7 +234,7 @@ class fluid_system:
|
|||
Returns:
|
||||
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]:
|
||||
# return lookup(self._g_array, t, self._t_offset)
|
||||
|
@ -239,7 +249,7 @@ class fluid_system:
|
|||
Returns:
|
||||
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:
|
||||
"""Get a string with the references for all fluids of the fluid system
|
||||
|
@ -263,13 +273,12 @@ class fluid:
|
|||
one or more species.
|
||||
|
||||
Attributes:
|
||||
fs: Reference to the fluid_system used for this fluid
|
||||
species: List of species names in the associated fluid_system
|
||||
array_composition: Array of the molar amounts of the species in the fluid
|
||||
array_element_composition: Array of the element composition in the fluid
|
||||
array_fractions: Array of the molar fractions of the species in the fluid
|
||||
total: Array of the sums of the molar amount of all species
|
||||
fs: Reference to the fluid_system used for this fluid
|
||||
species (list[str]): List of species names in the associated fluid_system
|
||||
array_composition (FloatArray): Array of the molar amounts of the species in the fluid
|
||||
array_element_composition (FloatArray): Array of the element composition in the fluid
|
||||
array_fractions (FloatArray): 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
|
||||
fs (fluid_system): Reference to the fluid_system used for this fluid
|
||||
"""
|
||||
|
||||
__array_priority__ = 100
|
||||
|
@ -606,7 +615,7 @@ class elements:
|
|||
"""Represent a fluid by composition of elements.
|
||||
|
||||
Attributes:
|
||||
array_element_composition: Array of the element composition
|
||||
array_element_composition (FloatArray): Array of the element composition
|
||||
"""
|
||||
|
||||
__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
|
||||
from typing import Generator
|
||||
|
||||
|
||||
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
|
||||
import md_to_code
|
||||
from glob import glob
|
||||
import importlib
|
||||
import os
|
||||
|
||||
|
||||
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
|
||||
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