mirror of https://github.com/Nonannet/copapy.git
Initial commit
This commit is contained in:
commit
9143bfef5b
|
|
@ -0,0 +1,24 @@
|
||||||
|
[flake8]
|
||||||
|
# Specify the maximum allowed line length
|
||||||
|
max-line-length = 88
|
||||||
|
|
||||||
|
# Ignore specific rules
|
||||||
|
# For example, E501: Line too long, W503: Line break before binary operator
|
||||||
|
ignore = E501, W503, W504, E226, E265
|
||||||
|
|
||||||
|
# Exclude specific files or directories
|
||||||
|
exclude =
|
||||||
|
.git,
|
||||||
|
__pycache__,
|
||||||
|
build,
|
||||||
|
dist,
|
||||||
|
.conda,
|
||||||
|
tests/autogenerated_*
|
||||||
|
|
||||||
|
# Enable specific plugins or options
|
||||||
|
# Example: Enabling flake8-docstrings
|
||||||
|
select = C,E,F,W,D
|
||||||
|
|
||||||
|
# Specify custom error codes to ignore or enable
|
||||||
|
per-file-ignores =
|
||||||
|
tests/*: D
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
backend/index/*
|
||||||
|
__pycache__
|
||||||
|
*.code-workspace
|
||||||
|
*.egg.info
|
||||||
|
/src/*.egg-info/*
|
||||||
|
/dist/*
|
||||||
|
.vscode
|
||||||
|
.mypy_cache
|
||||||
|
.pytest_cache
|
||||||
|
tests/autogenerated_*.py
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
cff-version: 1.2.0
|
||||||
|
title: Gaspype
|
||||||
|
abstract: Gaspype is a performant library for thermodynamic calculations with ideal gases
|
||||||
|
authors:
|
||||||
|
- family-names: Kruse
|
||||||
|
given-names: Nicolas
|
||||||
|
orcid: "https://orcid.org/0000-0001-6758-2269"
|
||||||
|
affiliation: "German Aerospace Center (DLR)"
|
||||||
|
address: "Linder Höhe"
|
||||||
|
city: Köln
|
||||||
|
version: 1.0.1
|
||||||
|
date-released: "2025-05-09"
|
||||||
|
#identifiers:
|
||||||
|
# - description: This is the collection of archived snapshots of all versions of Gaspype
|
||||||
|
# type: doi
|
||||||
|
# value: ""
|
||||||
|
license: MIT License
|
||||||
|
repository-code: "https://github.com/DLR-Institute-of-Future-Fuels/gaspype"
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Nicolas Kruse, German Aerospace Center (DLR)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
# Gaspype
|
||||||
|
The python package provides an performant library for thermodynamic calculations
|
||||||
|
like equilibrium reactions for several hundred gas species and their mixtures -
|
||||||
|
written in Python/Numpy.
|
||||||
|
|
||||||
|
Species are treated as ideal gases. Therefore the application is limited to moderate
|
||||||
|
pressures or high temperature applications.
|
||||||
|
|
||||||
|
Its designed with goal to be portable to Numpy-style GPU frameworks like JAX and PyTorch.
|
||||||
|
|
||||||
|
## Key features
|
||||||
|
- Tensor operations to prevent bottlenecks by the python interpreter
|
||||||
|
- Immutable types
|
||||||
|
- Elegant pythonic interface
|
||||||
|
- Great readable and compact syntax when using this package
|
||||||
|
- Good usability in Jupyter Notebook
|
||||||
|
- High performance for multidimensional fluid arrays
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Installation with pip:
|
||||||
|
``` bash
|
||||||
|
pip install gaspype
|
||||||
|
```
|
||||||
|
|
||||||
|
Installation with conda:
|
||||||
|
``` bash
|
||||||
|
conda install conda-forge::gaspype
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
Gaspype provides two main classes: ```fluid``` and ```elements```.
|
||||||
|
|
||||||
|
### Fluid
|
||||||
|
A fluid class describes a mixture of molecular species and their individual molar amounts.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
import gaspype as gp
|
||||||
|
fl = gp.fluid({'H2O': 1, 'H2': 2})
|
||||||
|
fl
|
||||||
|
```
|
||||||
|
```
|
||||||
|
Total 3.000e+00 mol
|
||||||
|
H2O 33.33 %
|
||||||
|
H2 66.67 %
|
||||||
|
```
|
||||||
|
|
||||||
|
Its' functions provides thermodynamic, mass balance and ideal gas properties of the mixture.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
cp = fl.get_cp(t=800+273.15)
|
||||||
|
mass = fl.get_mass()
|
||||||
|
gas_volume = fl.get_v(t=800+273.15, p=1e5)
|
||||||
|
```
|
||||||
|
|
||||||
|
The arguments can be provided as numpy-arrays:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
import numpy as np
|
||||||
|
t_range = np.linspace(600, 800, 5) + 273.15
|
||||||
|
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:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
fl2 = gp.fluid({'H2O': 1, 'N2': 2}) + \
|
||||||
|
np.linspace(0, 10, 4) * gp.fluid({'H2': 1})
|
||||||
|
fl2
|
||||||
|
```
|
||||||
|
```
|
||||||
|
Total mol:
|
||||||
|
array([ 3. , 6.33333333, 9.66666667, 13. ])
|
||||||
|
Species:
|
||||||
|
H2 H2O N2
|
||||||
|
Molar fractions:
|
||||||
|
array([[0. , 0.33333333, 0.66666667],
|
||||||
|
[0.52631579, 0.15789474, 0.31578947],
|
||||||
|
[0.68965517, 0.10344828, 0.20689655],
|
||||||
|
[0.76923077, 0.07692308, 0.15384615]])
|
||||||
|
```
|
||||||
|
A fluid object can be converted to a pandas dataframe:
|
||||||
|
``` python
|
||||||
|
import pandas as pd
|
||||||
|
pd.DataFrame(list(fl2))
|
||||||
|
```
|
||||||
|
| | H2O | N2 | H2
|
||||||
|
|----|-----|-----|-------
|
||||||
|
|0 | 1.0 | 2.0 | 0.000000
|
||||||
|
|1 | 1.0 | 2.0 | 3.333333
|
||||||
|
|2 | 1.0 | 2.0 | 6.666667
|
||||||
|
|3 | 1.0 | 2.0 | 10.000000
|
||||||
|
|
||||||
|
The broadcasting behavior is not limited to 1D-arrays:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
fl3 = gp.fluid({'H2O': 1}) + \
|
||||||
|
np.linspace(0, 10, 4) * gp.fluid({'H2': 1}) + \
|
||||||
|
np.expand_dims(np.linspace(1, 3, 3), axis=1) * gp.fluid({'N2': 1})
|
||||||
|
fl3
|
||||||
|
```
|
||||||
|
```
|
||||||
|
Total mol:
|
||||||
|
array([[ 2. , 5.33333333, 8.66666667, 12. ],
|
||||||
|
[ 3. , 6.33333333, 9.66666667, 13. ],
|
||||||
|
[ 4. , 7.33333333, 10.66666667, 14. ]])
|
||||||
|
Species:
|
||||||
|
H2 H2O N2
|
||||||
|
Molar fractions:
|
||||||
|
array([[[0. , 0.5 , 0.5 ],
|
||||||
|
[0.625 , 0.1875 , 0.1875 ],
|
||||||
|
[0.76923077, 0.11538462, 0.11538462],
|
||||||
|
[0.83333333, 0.08333333, 0.08333333]],
|
||||||
|
|
||||||
|
[[0. , 0.33333333, 0.66666667],
|
||||||
|
[0.52631579, 0.15789474, 0.31578947],
|
||||||
|
[0.68965517, 0.10344828, 0.20689655],
|
||||||
|
[0.76923077, 0.07692308, 0.15384615]],
|
||||||
|
|
||||||
|
[[0. , 0.25 , 0.75 ],
|
||||||
|
[0.45454545, 0.13636364, 0.40909091],
|
||||||
|
[0.625 , 0.09375 , 0.28125 ],
|
||||||
|
[0.71428571, 0.07142857, 0.21428571]]])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
el = gp.elements({'N': 1, 'Cl': 2})
|
||||||
|
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:
|
||||||
|
``` python
|
||||||
|
el2 = gp.elements(fl) + el - 0.3 * fl
|
||||||
|
el2
|
||||||
|
```
|
||||||
|
```
|
||||||
|
Cl 2.000e+00 mol
|
||||||
|
H 4.200e+00 mol
|
||||||
|
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:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
fs = gp.fluid_system('CH4, H2, CO, CO2, O2')
|
||||||
|
el3 = gp.elements({'C': 1, 'H': 2, 'O':1}, fs)
|
||||||
|
fl3 = gp.equilibrium(el3, t=800)
|
||||||
|
fl3
|
||||||
|
```
|
||||||
|
```
|
||||||
|
Total 1.204e+00 mol
|
||||||
|
CH4 33.07 %
|
||||||
|
H2 16.93 %
|
||||||
|
CO 16.93 %
|
||||||
|
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:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
fl3 + gp.fluid({'CH4': 1}, fs)
|
||||||
|
```
|
||||||
|
```
|
||||||
|
Total 2.204e+00 mol
|
||||||
|
CH4 63.44 %
|
||||||
|
H2 9.24 %
|
||||||
|
CO 9.24 %
|
||||||
|
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:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
fl3 + gp.fluid({'NH3': 1})
|
||||||
|
```
|
||||||
|
```
|
||||||
|
Total 2.204e+00 mol
|
||||||
|
CH4 18.07 %
|
||||||
|
CO 9.24 %
|
||||||
|
CO2 18.07 %
|
||||||
|
H2 9.24 %
|
||||||
|
NH3 45.38 %
|
||||||
|
O2 0.00 %
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developer Guide
|
||||||
|
Contributions are welcome, please open an issue or submit a pull request on GitHub.
|
||||||
|
|
||||||
|
To get started with developing the `gaspype` package, follow these steps.
|
||||||
|
|
||||||
|
First, clone the repository to your local machine using Git:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/DLR-Institute-of-Future-Fuels/gaspype.git
|
||||||
|
cd gaspype
|
||||||
|
```
|
||||||
|
|
||||||
|
It's recommended to setup an venv:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate # On Windows use `venv\Scripts\activate`
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the package and dev-dependencies while keeping the package files
|
||||||
|
in the current directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -e .[dev]
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure that everything is set up correctly by running the tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
[project]
|
||||||
|
name = "copapy"
|
||||||
|
version = "1.0.0"
|
||||||
|
authors = [
|
||||||
|
{ name="Nicolas Kruse", email="nicolas.kruse@nonan.net" },
|
||||||
|
]
|
||||||
|
description = "Copy-Patch Compiler"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
license = "MIT"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"pelfy>=1.0.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/nonannet/copapy"
|
||||||
|
Issues = "https://github.com/nonannet/copapy/issues"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
copapy = ["*.c"]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"flake8",
|
||||||
|
"mypy",
|
||||||
|
"pytest",
|
||||||
|
"pandas",
|
||||||
|
"cantera",
|
||||||
|
"types-PyYAML",
|
||||||
|
"scipy-stubs"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
files = ["src"]
|
||||||
|
strict = true
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
show_error_codes = true
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
minversion = "6.0"
|
||||||
|
addopts = "-ra -q"
|
||||||
|
testpaths = ["tests"]
|
||||||
|
pythonpath = ["src"]
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
import re
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
|
def get_var_name(var, scope=globals()):
|
||||||
|
return [name for name, value in scope.items() if value is var]
|
||||||
|
|
||||||
|
def _get_c_function_definitions(code: str):
|
||||||
|
ret = re.findall(r".*?void\s+([a-z_1-9]*)\s*\([^\)]*?\)[^\}]*?\{[^\}]*?result_([a-z_]*)\(.*?", code, flags=re.S)
|
||||||
|
return {r[0]: r[1] for r in ret}
|
||||||
|
|
||||||
|
_function_definitions = _get_c_function_definitions(pkgutil.get_data(__name__, 'ops.c').decode('utf-8'))
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
def __repr__(self):
|
||||||
|
#return f"Node:{self.name}({', '.join(str(a) for a in self.args) if self.args else self.value})"
|
||||||
|
return f"Node:{self.name}({', '.join(str(a) for a in self.args) if self.args else (self.value if isinstance(self, Const) else '')})"
|
||||||
|
|
||||||
|
class Device():
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Net:
|
||||||
|
def __init__(self, dtype: str, source: Node):
|
||||||
|
self.dtype = dtype
|
||||||
|
self.source = source
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
return _add_op('mul', [self, other], True)
|
||||||
|
|
||||||
|
def __rmul__(self, other):
|
||||||
|
return _add_op('mul', [self, other], True)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return _add_op('add', [self, other], True)
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
return _add_op('add', [self, other], True)
|
||||||
|
|
||||||
|
def __sub__ (self, other):
|
||||||
|
return _add_op('sub', [self, other])
|
||||||
|
|
||||||
|
def __rsub__ (self, other):
|
||||||
|
return _add_op('sub', [other, self])
|
||||||
|
|
||||||
|
def __truediv__ (self, other):
|
||||||
|
return _add_op('div', [self, other])
|
||||||
|
|
||||||
|
def __rtruediv__ (self, other):
|
||||||
|
return _add_op('div', [other, self])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
names = get_var_name(self)
|
||||||
|
return f"{'name:' + names[0] if names else 'id:' + str(id(self))[-5:]}"
|
||||||
|
|
||||||
|
|
||||||
|
class Const(Node):
|
||||||
|
def __init__(self, value: float | int | bool):
|
||||||
|
self.dtype, self.value = _get_data_and_dtype(value)
|
||||||
|
self.name = 'const_' + self.dtype
|
||||||
|
|
||||||
|
#if self.name not in _function_definitions:
|
||||||
|
# raise ValueError(f"Unsupported operand type for a const: {self.dtype}")
|
||||||
|
|
||||||
|
self.args = []
|
||||||
|
|
||||||
|
class Write(Node):
|
||||||
|
def __init__(self, net: Net):
|
||||||
|
self.name = 'write_' + net.dtype
|
||||||
|
self.args = [net]
|
||||||
|
|
||||||
|
#if self.name not in _function_definitions:
|
||||||
|
# raise ValueError(f"Unsupported operand type for write: {net.dtype}")
|
||||||
|
|
||||||
|
class Op(Node):
|
||||||
|
def __init__(self, typed_op_name: str, args: list[Net]):
|
||||||
|
assert not args or any(isinstance(t, Net) for t in args), 'args parameter must be of type list[Net]'
|
||||||
|
self.name = typed_op_name
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def _add_op(op: str, args: list[Net], commutative = False):
|
||||||
|
args = [a if isinstance(a, Net) else const(a) for a in args]
|
||||||
|
|
||||||
|
if commutative:
|
||||||
|
args = sorted(args, key=lambda a: a.dtype)
|
||||||
|
|
||||||
|
typed_op = '_'.join([op] + [a.dtype for a in args])
|
||||||
|
|
||||||
|
if typed_op not in _function_definitions:
|
||||||
|
raise ValueError(f"Unsupported operand type(s) for {op}: {' and '.join([a.dtype for a in args])}")
|
||||||
|
|
||||||
|
result_type = _function_definitions[typed_op]
|
||||||
|
|
||||||
|
result_net = Net(result_type, Op(typed_op, args))
|
||||||
|
|
||||||
|
return result_net
|
||||||
|
|
||||||
|
#def read_input(hw: Device, test_value: float):
|
||||||
|
# return Net(type(value))
|
||||||
|
|
||||||
|
def const(value: float | int | bool):
|
||||||
|
new_const = Const(value)
|
||||||
|
return Net(new_const.dtype, new_const)
|
||||||
|
|
||||||
|
def _get_data_and_dtype(value):
|
||||||
|
if isinstance(value, int):
|
||||||
|
return ('int', int(value))
|
||||||
|
elif isinstance(value, float):
|
||||||
|
return ('float', float(value))
|
||||||
|
elif isinstance(value, bool):
|
||||||
|
return ('bool', int(value))
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Non supported data type: {type(value).__name__}')
|
||||||
|
|
||||||
|
def const_vector3d(x: float, y: float, z: float):
|
||||||
|
return vec3d((const(x), const(y), const(z)))
|
||||||
|
|
||||||
|
class vec3d:
|
||||||
|
def __init__(self, value: tuple[float, float, float]):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return vec3d(tuple(a+b for a,b in zip(self.value, other.value)))
|
||||||
|
|
||||||
|
|
||||||
|
def get_multiuse_nets(root: list[Op]):
|
||||||
|
"""
|
||||||
|
Finds all nets that get accessed more than one time. Therefore
|
||||||
|
storage on the heap might be better.
|
||||||
|
"""
|
||||||
|
known_nets: set[Net] = set()
|
||||||
|
|
||||||
|
def recursiv_node_search(net_list: list[Net]):
|
||||||
|
for net in net_list:
|
||||||
|
#print(net)
|
||||||
|
if net in known_nets:
|
||||||
|
yield net
|
||||||
|
else:
|
||||||
|
known_nets.add(net)
|
||||||
|
yield from recursiv_node_search(net.source.args)
|
||||||
|
|
||||||
|
return set(recursiv_node_search(op.args[0] for op in root))
|
||||||
|
|
||||||
|
|
||||||
|
def get_path_segments(root: list[Op]) -> list[list[Op]]:
|
||||||
|
"""
|
||||||
|
List of all possible paths. Ops in order of execution (output at the end)
|
||||||
|
"""
|
||||||
|
def recursiv_node_search(op_list: list[Op], path: list[Op]) -> list[Op]:
|
||||||
|
for op in op_list:
|
||||||
|
new_path = [op] + path
|
||||||
|
if op.args:
|
||||||
|
yield from recursiv_node_search([net.source for net in op.args], new_path)
|
||||||
|
else:
|
||||||
|
yield new_path
|
||||||
|
|
||||||
|
known_ops: set[Op] = set()
|
||||||
|
sorted_path_list = sorted(recursiv_node_search(root, []), key=lambda x: -len(x))
|
||||||
|
|
||||||
|
for path in sorted_path_list:
|
||||||
|
sflag = False
|
||||||
|
for i, net in enumerate(path):
|
||||||
|
if net in known_ops or i == len(path) - 1:
|
||||||
|
if sflag:
|
||||||
|
if i > 0:
|
||||||
|
yield path[:i+1]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
sflag = True
|
||||||
|
known_ops.add(net)
|
||||||
|
|
||||||
|
|
||||||
|
def get_ordered_ops(path_segments: list[list[Op]]) -> list[Op]:
|
||||||
|
finished_paths: set[int] = set()
|
||||||
|
|
||||||
|
for i, path in enumerate(path_segments):
|
||||||
|
#print(i)
|
||||||
|
if i not in finished_paths:
|
||||||
|
for op in path:
|
||||||
|
for j in range(i + 1, len(path_segments)):
|
||||||
|
path_stub = path_segments[j]
|
||||||
|
if op == path_stub[-1]:
|
||||||
|
for insert_op in path_stub[:-1]:
|
||||||
|
#print('->', insert_op)
|
||||||
|
yield insert_op
|
||||||
|
finished_paths.add(j)
|
||||||
|
#print('- ', op)
|
||||||
|
yield op
|
||||||
|
finished_paths.add(i)
|
||||||
|
|
||||||
|
|
||||||
|
def get_consts(op_list: list[Node]):
|
||||||
|
net_lookup = {net.source: net for op in op_list for net in op.args}
|
||||||
|
return [(n.name, net_lookup[n], n.value) for n in op_list if isinstance(n, Const)]
|
||||||
|
|
||||||
|
|
||||||
|
def add_read_ops(op_list):
|
||||||
|
"""Add read operation before each op where arguments are not allredy possitioned
|
||||||
|
correctly in the registers
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Yields a tuples of a net and a operation. The net is the result net
|
||||||
|
from the retuned operation"""
|
||||||
|
registers = [None] * 16
|
||||||
|
|
||||||
|
net_lookup = {net.source: net for op in op_list for net in op.args}
|
||||||
|
|
||||||
|
for op in op_list:
|
||||||
|
if not op.name.startswith('const_'):
|
||||||
|
for i, net in enumerate(op.args):
|
||||||
|
if net != registers[i]:
|
||||||
|
#if net in registers:
|
||||||
|
# print('x swap registers')
|
||||||
|
new_op = Op('read_reg' + str(i) + '_' + net.dtype, [])
|
||||||
|
yield net, new_op
|
||||||
|
registers[i] = net
|
||||||
|
|
||||||
|
yield net_lookup.get(op), op
|
||||||
|
if op in net_lookup:
|
||||||
|
registers[0] = net_lookup[op]
|
||||||
|
|
||||||
|
|
||||||
|
def add_write_ops(op_list, const_list):
|
||||||
|
"""Add write operation for each new defined net if a read operation is later folowed"""
|
||||||
|
stored_nets = {c[1] for c in const_list}
|
||||||
|
read_back_nets = {net for net, op in op_list if op.name.startswith('read_reg')}
|
||||||
|
|
||||||
|
for net, op in op_list:
|
||||||
|
yield net, op
|
||||||
|
if net in read_back_nets and net not in stored_nets:
|
||||||
|
yield (net, Op('write_' + net.dtype, [net]))
|
||||||
|
stored_nets.add(net)
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
from enum import Enum
|
||||||
|
from pelfy import elf_symbol
|
||||||
|
|
||||||
|
Command = Enum('Command', [('ALLOCATE_DATA', 1), ('COPY_DATA', 2),
|
||||||
|
('ALLOCATE_CODE', 3), ('COPY_CODE', 4),
|
||||||
|
('RELOCATE_FUNC', 5), ('RELOCATE_OBJECT', 6),
|
||||||
|
('SET_ENTR_POINT', 64), ('END_PROG', 255)])
|
||||||
|
|
||||||
|
RelocationType = Enum('RelocationType', [('RELOC_RELATIVE_32', 0)])
|
||||||
|
|
||||||
|
def translate_relocation(new_sym_addr: int, new_patch_addr: int, reloc_type: str, r_addend: int) -> int:
|
||||||
|
if reloc_type in ('R_AMD64_PLT32', 'R_AMD64_PC32'):
|
||||||
|
# S + A - P
|
||||||
|
value = new_sym_addr + r_addend - new_patch_addr
|
||||||
|
return RelocationType.RELOC_RELATIVE_32, value
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unknown: {reloc_type}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_variable_data(symbols: list[elf_symbol]) -> tuple[list[tuple[elf_symbol, int, int]], int]:
|
||||||
|
object_list = []
|
||||||
|
out_offs = 0
|
||||||
|
for sym in symbols:
|
||||||
|
assert sym.info == 'STT_OBJECT'
|
||||||
|
lengths = sym.fields['st_size']
|
||||||
|
object_list.append((sym, out_offs, lengths))
|
||||||
|
out_offs += (lengths + 3) // 4 * 4
|
||||||
|
return object_list, out_offs
|
||||||
|
|
||||||
|
|
||||||
|
def get_function_data(symbols: list[elf_symbol]) -> tuple[list[tuple[elf_symbol, int, int, int]], int]:
|
||||||
|
code_list = []
|
||||||
|
out_offs = 0
|
||||||
|
for sym in symbols:
|
||||||
|
assert sym.info == 'STT_FUNC'
|
||||||
|
lengths = sym.fields['st_size']
|
||||||
|
|
||||||
|
#if strip_function:
|
||||||
|
# assert False, 'Not implemente'
|
||||||
|
# TODO: Strip functions
|
||||||
|
# Symbol, start out_offset in symbol, offset in output file, output lengths
|
||||||
|
# Set in_sym_out_offs and lengths
|
||||||
|
in_sym_offs = 0
|
||||||
|
|
||||||
|
code_list.append((sym, in_sym_offs, out_offs, lengths))
|
||||||
|
# out_offs += (lengths + 3) // 4 * 4
|
||||||
|
out_offs += lengths # should be aligned by default?
|
||||||
|
return code_list, out_offs
|
||||||
|
|
||||||
|
|
||||||
|
def get_function_data_blob(symbols: list[elf_symbol]) -> tuple[list[tuple[elf_symbol, int, int, int]], int]:
|
||||||
|
code_list = []
|
||||||
|
out_offs = 0
|
||||||
|
for sym in symbols:
|
||||||
|
assert sym.info == 'STT_FUNC'
|
||||||
|
lengths = sym.fields['st_size']
|
||||||
|
|
||||||
|
#if strip_function:
|
||||||
|
# assert False, 'Not implemente'
|
||||||
|
# TODO: Strip functions
|
||||||
|
# Symbol, start out_offset in symbol, offset in output file, output lengths
|
||||||
|
# Set in_sym_out_offs and lengths
|
||||||
|
in_sym_offs = 0
|
||||||
|
|
||||||
|
code_list.append((sym, in_sym_offs, out_offs, lengths))
|
||||||
|
out_offs += (lengths + 3) // 4 * 4
|
||||||
|
return code_list, out_offs
|
||||||
|
|
||||||
|
|
||||||
|
class data_writer():
|
||||||
|
def __init__(self, byteorder: str):
|
||||||
|
self._data: list[(str, bytes)] = list()
|
||||||
|
self.byteorder = byteorder
|
||||||
|
|
||||||
|
def write_int(self, value: int, num_bytes: int = 4, signed: bool = False):
|
||||||
|
self._data.append((f"INT {value}", value.to_bytes(length=num_bytes, byteorder=self.byteorder, signed=signed), 0))
|
||||||
|
|
||||||
|
def write_com(self, value: Enum, num_bytes: int = 4):
|
||||||
|
self._data.append((value.name, value.value.to_bytes(length=num_bytes, byteorder=self.byteorder, signed=False), 1))
|
||||||
|
|
||||||
|
def write_byte(self, value: int):
|
||||||
|
self._data.append((f"BYTE {value}", bytes([value]), 0))
|
||||||
|
|
||||||
|
def write_bytes(self, value: bytes):
|
||||||
|
self._data.append((f"BYTES {len(value)}", value, 0))
|
||||||
|
|
||||||
|
def print(self) -> str:
|
||||||
|
for name, dat, flag in self._data:
|
||||||
|
if flag:
|
||||||
|
print('')
|
||||||
|
print(f"{name:18}" + ' '.join(f'{b:02X}' for b in dat))
|
||||||
|
|
||||||
|
def get_data(self) -> bytes:
|
||||||
|
return b''.join(dat for _, dat, _ in self._data)
|
||||||
|
|
||||||
|
def to_file(self, path: str):
|
||||||
|
with open(path, 'wb') as f:
|
||||||
|
f.write(self.get_data())
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
//notes:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Helper functions
|
||||||
|
void result_int(int ret1){
|
||||||
|
asm ("");
|
||||||
|
}
|
||||||
|
|
||||||
|
void result_float(float ret1){
|
||||||
|
asm ("");
|
||||||
|
}
|
||||||
|
|
||||||
|
void result_float_float(float ret1, float ret2){
|
||||||
|
asm ("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Operations
|
||||||
|
void add_int_int(int arg1, int arg2){
|
||||||
|
result_int(arg1 + arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_float_float(float arg1, float arg2){
|
||||||
|
result_float(arg1 + arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_float_int(float arg1, float arg2){
|
||||||
|
result_float(arg1 + arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sub_int_int(int arg1, int arg2){
|
||||||
|
result_int(arg1 - arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sub_float_float(float arg1, float arg2){
|
||||||
|
result_float(arg1 - arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sub_float_int(float arg1, int arg2){
|
||||||
|
result_float(arg1 - arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sub_int_float(int arg1, float arg2){
|
||||||
|
result_float(arg1 - arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mul_int_int(int arg1, int arg2){
|
||||||
|
result_int(arg1 * arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mul_float_float(float arg1, float arg2){
|
||||||
|
result_float(arg1 * arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mul_float_int(float arg1, int arg2){
|
||||||
|
result_float(arg1 + arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void div_int_int(int arg1, int arg2){
|
||||||
|
result_int(arg1 / arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void div_float_float(float arg1, float arg2){
|
||||||
|
result_float(arg1 / arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void div_float_int(float arg1, int arg2){
|
||||||
|
result_float(arg1 / arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void div_int_float(int arg1, float arg2){
|
||||||
|
result_float(arg1 / arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read global variables from heap
|
||||||
|
int read_int_ret = 1337;
|
||||||
|
|
||||||
|
void read_int(){
|
||||||
|
result_int(read_int_ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
float read_float_ret = 1337;
|
||||||
|
|
||||||
|
void read_float(){
|
||||||
|
result_float(read_float_ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
void read_float_2(float arg1){
|
||||||
|
result_float_float(arg1, read_float_ret);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
#from diss_helper import dmprint
|
||||||
|
import pelfy
|
||||||
|
#from IPython.display import Markdown
|
||||||
|
|
||||||
|
from typing import Literal
|
||||||
|
from copapy import Op, Write, const
|
||||||
|
import copapy as rc
|
||||||
|
from copapy.binwrite import data_writer, Command, RelocationType, get_variable_data, get_function_data, translate_relocation
|
||||||
|
|
||||||
|
def test_ast_generation():
|
||||||
|
c1 = const(1.11)
|
||||||
|
c2 = const(2.22)
|
||||||
|
#c3 = const(3.33)
|
||||||
|
|
||||||
|
#i1 = c1 + c2
|
||||||
|
#i2 = c2 * i1
|
||||||
|
#i3 = i2 + 4
|
||||||
|
|
||||||
|
#r1 = i1 + i3
|
||||||
|
#r2 = i3 * i2
|
||||||
|
|
||||||
|
i1 = c1 * 2
|
||||||
|
i2 = i1 + 3
|
||||||
|
|
||||||
|
r1 = i1 + i2
|
||||||
|
r2 = c2 + 4 + c1
|
||||||
|
|
||||||
|
out = [Write(r1), Write(r2)]
|
||||||
|
print(out)
|
||||||
|
print('--')
|
||||||
|
|
||||||
|
path_segments = list(rc.get_path_segments(out))
|
||||||
|
for p in path_segments:
|
||||||
|
print(p)
|
||||||
|
print('--')
|
||||||
|
|
||||||
|
ordered_ops = list(rc.get_ordered_ops(path_segments))
|
||||||
|
for p in path_segments:
|
||||||
|
print(p)
|
||||||
|
print('--')
|
||||||
|
|
||||||
|
const_list = rc.get_consts(ordered_ops)
|
||||||
|
for p in const_list:
|
||||||
|
print(p)
|
||||||
|
print('--')
|
||||||
|
|
||||||
|
output_ops = list(rc.add_read_ops(ordered_ops))
|
||||||
|
for p in output_ops:
|
||||||
|
print(p)
|
||||||
|
print('--')
|
||||||
|
|
||||||
|
extended_output_ops = list(rc.add_write_ops(output_ops, const_list))
|
||||||
|
for p in extended_output_ops:
|
||||||
|
print(p)
|
||||||
|
print('--')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_ast_generation()
|
||||||
Loading…
Reference in New Issue