md_to_code updated to generate tests and human readable example code from markdown scripts

This commit is contained in:
Nicolas Kruse 2025-06-04 17:53:37 +02:00
parent 14d6f5a7fb
commit 360683a633
3 changed files with 230 additions and 49 deletions

View File

@ -0,0 +1,98 @@
```python
import gaspype as gp
from gaspype import R, F
import numpy as np
import matplotlib.pyplot as plt
```
```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)
```
```python
#Plot compositions on fuel and air side
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)
```
```python
o2_fuel_side = gp.oxygen_partial_pressure(fuel_side, t, p)
o2_air_side = air_side.get_x('O2') * p
```
```python
#Plot oxygen partial pressure
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'])
```
```python
z_O2 = 4
nernst_voltage = R*t / (z_O2*F) * np.log(o2_air_side/o2_fuel_side)
```
```python
#Plot voltage potential
fig, ax = plt.subplots()
ax.set_xlabel("Conversion")
ax.set_ylabel("Voltage / V")
ax.plot(conversion, nernst_voltage, '-')
print(np.min(nernst_voltage))
```
```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²')
```
```python
#Plot current density
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, '-')
```

125
tests/md_to_code.py Normal file
View File

@ -0,0 +1,125 @@
import re
from typing import Generator, Iterable, Literal
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 ') 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])

View File

@ -1,53 +1,11 @@
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
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_soec_example():
md_to_code.convert_to('test', 'docs/source/examples/soec_methane.md', 'tests/autogenerated_soec_example.py')
import autogenerated_soec_example
autogenerated_soec_example.run_test()