From db7216d0b6679bb9d5b28e9a28a324c891f310d1 Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Thu, 23 Oct 2025 12:49:30 +0200 Subject: [PATCH 01/13] partial vector implementation added --- src/copapy/_basic_types.py | 10 ---------- src/copapy/_vectors.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 src/copapy/_vectors.py diff --git a/src/copapy/_basic_types.py b/src/copapy/_basic_types.py index edf630b..0419517 100644 --- a/src/copapy/_basic_types.py +++ b/src/copapy/_basic_types.py @@ -311,16 +311,6 @@ class cpbool(cpint): self.dtype = 'bool' -class cpvector: - def __init__(self, *value: NumLike): - self.value = value - - def __add__(self, other: 'cpvector') -> 'cpvector': - assert len(self.value) == len(other.value) - tup = (a + b for a, b in zip(self.value, other.value)) - return cpvector(*(v for v in tup if isinstance(v, CPNumber))) - - class InitVar(Node): def __init__(self, value: int | float): self.dtype, self.value = _get_data_and_dtype(value) diff --git a/src/copapy/_vectors.py b/src/copapy/_vectors.py new file mode 100644 index 0000000..d9519bf --- /dev/null +++ b/src/copapy/_vectors.py @@ -0,0 +1,14 @@ +from copapy import NumLike, CPNumber, cpint, cpfloat, cpbool +from typing import Generic, TypeVar, Iterable, Any + +T = TypeVar("T", bound=CPNumber) +T2 = TypeVar("T2", bound=CPNumber) + +class cpvector(Generic[T]): + def __init__(self, value: Iterable[T]): + self.value = tuple(value) + + def __add__(self, other: 'cpvector[Any]') -> 'cpvector[CPNumber]': + assert len(self.value) == len(other.value) + tup = (a + b for a, b in zip(self.value, other.value)) + return cpvector(*(v for v in tup if isinstance(v, CPNumber))) \ No newline at end of file From a8280f8d2dd7e2d6dc9ea47a2fda1ecaa8ce5dca Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Fri, 24 Oct 2025 00:35:41 +0200 Subject: [PATCH 02/13] vector typing advanced --- src/copapy/__init__.py | 5 +++-- src/copapy/_vectors.py | 51 +++++++++++++++++++++++++++++++++--------- tests/test_vector.py | 5 +++++ 3 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 tests/test_vector.py diff --git a/src/copapy/__init__.py b/src/copapy/__init__.py index dbf56cb..6dcce27 100644 --- a/src/copapy/__init__.py +++ b/src/copapy/__init__.py @@ -1,13 +1,14 @@ from ._target import Target from ._basic_types import NumLike, variable, \ - CPNumber, cpvector, generic_sdb, iif + CPNumber, generic_sdb, iif +from ._vectors import vector __all__ = [ "Target", "NumLike", "variable", "CPNumber", - "cpvector", "generic_sdb", "iif", + "vector" ] diff --git a/src/copapy/_vectors.py b/src/copapy/_vectors.py index d9519bf..f6ef683 100644 --- a/src/copapy/_vectors.py +++ b/src/copapy/_vectors.py @@ -1,14 +1,45 @@ -from copapy import NumLike, CPNumber, cpint, cpfloat, cpbool -from typing import Generic, TypeVar, Iterable, Any +from numpy import isin +from copapy import NumLike, CPNumber, variable +from typing import Generic, TypeVar, Iterable, Any, overload -T = TypeVar("T", bound=CPNumber) +from copapy._basic_types import TNum + +T = TypeVar("T", int, float, bool) T2 = TypeVar("T2", bound=CPNumber) -class cpvector(Generic[T]): - def __init__(self, value: Iterable[T]): - self.value = tuple(value) +class vector(Generic[T]): + def __init__(self, values: Iterable[T | variable[T]]): + #self.values: tuple[variable[T], ...] = tuple(v if isinstance(v, variable) else variable(v) for v in values) + self.values: tuple[variable[T] | T, ...] = tuple(values) - def __add__(self, other: 'cpvector[Any]') -> 'cpvector[CPNumber]': - assert len(self.value) == len(other.value) - tup = (a + b for a, b in zip(self.value, other.value)) - return cpvector(*(v for v in tup if isinstance(v, CPNumber))) \ No newline at end of file + @overload + def __add__(self, other: 'vector[float] | variable[float] | float') -> 'vector[float]': + ... + + @overload + def __add__(self: 'vector[T]', other: 'vector[int] | variable[int] | int') -> 'vector[T]': + ... + + def __add__(self, other: 'vector[Any] | variable[Any] | float | int') -> Any: + if isinstance(other, vector): + assert len(self.values) == len(other.values) + return vector(a + b for a, b in zip(self.values, other.values)) + else: + return vector(a + other for a in self.values) + + #@overload + #def sum(self: 'vector[float]') -> variable[float]: + # ... + + #@overload + #def sum(self: 'vector[int]') -> variable[int]: + # ... + + #def sum(self: 'vector[T]') -> variable[T] | T: + # comp_time = sum(v for v in self.values if not isinstance(v, variable)) + # run_time = sum(v for v in self.values if isinstance(v, variable)) + # if isinstance(run_time, variable): + # return comp_time + run_time # type: ignore + # else: + # return comp_time + \ No newline at end of file diff --git a/tests/test_vector.py b/tests/test_vector.py new file mode 100644 index 0000000..9379724 --- /dev/null +++ b/tests/test_vector.py @@ -0,0 +1,5 @@ +import copapy as cp + +def test_vec(): + tt = cp.vector(range(3)) + cp.vector([1.1,2.2,3.3]) + tt2 = (cp.vector(range(3)) + 5.6) \ No newline at end of file From cb1447f125156467615ef4b71faf2e0debc1754e Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Fri, 24 Oct 2025 00:36:22 +0200 Subject: [PATCH 03/13] refactoring API generics --- src/copapy/_basic_types.py | 71 +++++++++++++++++++------------------- src/copapy/_vectors.py | 5 +-- tests/test_ext_ops.py | 4 +-- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/copapy/_basic_types.py b/src/copapy/_basic_types.py index 3af237d..fcfb338 100644 --- a/src/copapy/_basic_types.py +++ b/src/copapy/_basic_types.py @@ -4,15 +4,12 @@ from ._stencils import stencil_database import platform NumLike: TypeAlias = 'variable[int] | variable[float] | variable[bool] | int | float | bool' -NumLikeAndNet: TypeAlias = 'variable[int] | variable[float] | variable[bool] | int | float | bool | Net' -NetAndNum: TypeAlias = 'Net | int | float' - unifloat: TypeAlias = 'variable[float] | float' uniint: TypeAlias = 'variable[int] | int' unibool: TypeAlias = 'variable[bool] | bool' -TNumber = TypeVar("TNumber", bound='CPNumber') -T = TypeVar("T") +TCPNum = TypeVar("TCPNum", bound='CPNumber') +TNum = TypeVar("TNum", int, bool, float) def get_var_name(var: Any, scope: dict[str, Any] = globals()) -> list[str]: @@ -66,7 +63,7 @@ class CPNumber(Net): self.source = source @overload - def __mul__(self: TNumber, other: uniint) -> TNumber: + def __mul__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -77,7 +74,7 @@ class CPNumber(Net): return _add_op('mul', [self, other], True) @overload - def __rmul__(self: TNumber, other: uniint) -> TNumber: + def __rmul__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -88,7 +85,7 @@ class CPNumber(Net): return _add_op('mul', [self, other], True) @overload - def __add__(self: TNumber, other: uniint) -> TNumber: + def __add__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -99,7 +96,7 @@ class CPNumber(Net): return _add_op('add', [self, other], True) @overload - def __radd__(self: TNumber, other: uniint) -> TNumber: + def __radd__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -110,7 +107,7 @@ class CPNumber(Net): return _add_op('add', [self, other], True) @overload - def __sub__(self: TNumber, other: uniint) -> TNumber: + def __sub__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -121,7 +118,7 @@ class CPNumber(Net): return _add_op('sub', [self, other]) @overload - def __rsub__(self: TNumber, other: uniint) -> TNumber: + def __rsub__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -138,7 +135,7 @@ class CPNumber(Net): return _add_op('div', [other, self]) @overload - def __floordiv__(self: TNumber, other: uniint) -> TNumber: + def __floordiv__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -149,7 +146,7 @@ class CPNumber(Net): return _add_op('floordiv', [self, other]) @overload - def __rfloordiv__(self: TNumber, other: uniint) -> TNumber: + def __rfloordiv__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -159,9 +156,8 @@ class CPNumber(Net): def __rfloordiv__(self, other: NumLike) -> 'CPNumber': return _add_op('floordiv', [other, self]) - def __neg__(self: TNumber) -> TNumber: - assert isinstance(T, variable) - return cast(TNumber, _add_op('sub', [variable(0), self])) + def __neg__(self: TCPNum) -> TCPNum: + return cast(TCPNum, _add_op('sub', [variable(0), self])) def __gt__(self, other: NumLike) -> 'variable[bool]': ret = _add_op('gt', [self, other]) @@ -180,7 +176,7 @@ class CPNumber(Net): return variable(ret.source, dtype='bool') @overload - def __mod__(self: TNumber, other: uniint) -> TNumber: + def __mod__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -191,7 +187,7 @@ class CPNumber(Net): return _add_op('mod', [self, other]) @overload - def __rmod__(self: TNumber, other: uniint) -> TNumber: + def __rmod__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -202,7 +198,7 @@ class CPNumber(Net): return _add_op('mod', [other, self]) @overload - def __pow__(self: TNumber, other: uniint) -> TNumber: + def __pow__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -213,7 +209,7 @@ class CPNumber(Net): return _add_op('pow', [other, self]) @overload - def __rpow__(self: TNumber, other: uniint) -> TNumber: + def __rpow__(self: TCPNum, other: uniint) -> TCPNum: ... @overload @@ -227,23 +223,21 @@ class CPNumber(Net): return super().__hash__() -class variable(Generic[T], CPNumber): - def __init__(self, source: T | Node, dtype: str | None = None): +class variable(Generic[TNum], CPNumber): + def __init__(self, source: TNum | Node, dtype: str | None = None): if isinstance(source, Node): self.source = source assert dtype, 'For source type Node a dtype argument is required.' self.dtype = dtype - elif isinstance(source, bool): - self.source = CPConstant(source) - self.dtype = 'bool' - elif isinstance(source, int): - self.source = CPConstant(source) - self.dtype = 'int' elif isinstance(source, float): self.source = CPConstant(source) self.dtype = 'float' + elif isinstance(source, bool): + self.source = CPConstant(source) + self.dtype = 'bool' else: - raise ValueError(f'Non supported data type: {type(source).__name__}') + self.source = CPConstant(source) + self.dtype = 'int' # Bitwise and shift operations for cp[int] def __lshift__(self, other: uniint) -> 'variable[int]': @@ -285,7 +279,7 @@ class CPConstant(Node): class Write(Node): - def __init__(self, input: NetAndNum): + def __init__(self, input: Net | int | float): if isinstance(input, Net): net = input else: @@ -324,17 +318,24 @@ def iif(expression: CPNumber, true_result: unifloat, false_result: unifloat) -> @overload -def iif(expression: NumLike, true_result: T, false_result: T) -> T: +def iif(expression: float | int, true_result: TNum, false_result: TNum) -> TNum: + ... + + +@overload +def iif(expression: float | int, true_result: TNum, false_result: variable[TNum]) -> variable[TNum]: + ... + + +@overload +def iif(expression: float | int, true_result: variable[TNum], false_result: TNum | variable[TNum]) -> variable[TNum]: ... def iif(expression: Any, true_result: Any, false_result: Any) -> Any: allowed_type = (variable, int, float, bool) assert isinstance(true_result, allowed_type) and isinstance(false_result, allowed_type), "Result type not supported" - if isinstance(expression, CPNumber): - return (expression != 0) * true_result + (expression == 0) * false_result - else: - return true_result if expression else false_result + return (expression != 0) * true_result + (expression == 0) * false_result def _add_op(op: str, args: list[CPNumber | int | float], commutative: bool = False) -> variable[Any]: diff --git a/src/copapy/_vectors.py b/src/copapy/_vectors.py index f6ef683..c129f20 100644 --- a/src/copapy/_vectors.py +++ b/src/copapy/_vectors.py @@ -1,9 +1,6 @@ -from numpy import isin -from copapy import NumLike, CPNumber, variable +from copapy import CPNumber, variable from typing import Generic, TypeVar, Iterable, Any, overload -from copapy._basic_types import TNum - T = TypeVar("T", int, float, bool) T2 = TypeVar("T2", bound=CPNumber) diff --git a/tests/test_ext_ops.py b/tests/test_ext_ops.py index e8086b0..d3a2955 100644 --- a/tests/test_ext_ops.py +++ b/tests/test_ext_ops.py @@ -8,8 +8,8 @@ def test_compile(): c_f = variable(2.5) # c_b = variable(True) - ret_test = (c_f ** c_f, c_i ** c_i) - ret_ref = (2.5 ** 2.5, 9 ** 9) + ret_test = (c_f ** c_f, c_i ** c_i)#, c_i & 3) + ret_ref = (2.5 ** 2.5, 9 ** 9)#, 9 & 3) tg = Target() print('* compile and copy ...') From df84b61a7b6599db1cd41cc8613f13c843735616 Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Sat, 25 Oct 2025 02:21:43 +0200 Subject: [PATCH 04/13] vector type added, sqrt and ge/le added; type hints improved --- src/copapy/__init__.py | 8 +- src/copapy/_basic_types.py | 377 +++++++++++++++------------------- src/copapy/_math.py | 24 +++ src/copapy/_vectors.py | 176 +++++++++++++--- stencils/aux_functions.c | 13 ++ stencils/generate_stencils.py | 18 +- tests/test_ext_ops.py | 31 --- tests/test_math.py | 56 +++++ tests/test_ops.py | 2 +- tests/test_prog_flow.py | 2 +- tests/test_vector.py | 30 ++- 11 files changed, 459 insertions(+), 278 deletions(-) create mode 100644 src/copapy/_math.py delete mode 100644 tests/test_ext_ops.py create mode 100644 tests/test_math.py diff --git a/src/copapy/__init__.py b/src/copapy/__init__.py index 6dcce27..5dee792 100644 --- a/src/copapy/__init__.py +++ b/src/copapy/__init__.py @@ -1,14 +1,16 @@ from ._target import Target from ._basic_types import NumLike, variable, \ - CPNumber, generic_sdb, iif + generic_sdb, iif from ._vectors import vector +from ._math import sqrt, abs __all__ = [ "Target", "NumLike", "variable", - "CPNumber", "generic_sdb", "iif", - "vector" + "vector", + "sqrt", + "abs", ] diff --git a/src/copapy/_basic_types.py b/src/copapy/_basic_types.py index fcfb338..b1e2dad 100644 --- a/src/copapy/_basic_types.py +++ b/src/copapy/_basic_types.py @@ -8,7 +8,7 @@ unifloat: TypeAlias = 'variable[float] | float' uniint: TypeAlias = 'variable[int] | int' unibool: TypeAlias = 'variable[bool] | bool' -TCPNum = TypeVar("TCPNum", bound='CPNumber') +TCPNum = TypeVar("TCPNum", bound='variable[Any]') TNum = TypeVar("TNum", int, bool, float) @@ -57,173 +57,7 @@ class Net: return id(self) -class CPNumber(Net): - def __init__(self, dtype: str, source: Node): - self.dtype = dtype - self.source = source - - @overload - def __mul__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __mul__(self, other: unifloat) -> 'variable[float]': - ... - - def __mul__(self, other: NumLike) -> 'CPNumber': - return _add_op('mul', [self, other], True) - - @overload - def __rmul__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __rmul__(self, other: unifloat) -> 'variable[float]': - ... - - def __rmul__(self, other: NumLike) -> 'CPNumber': - return _add_op('mul', [self, other], True) - - @overload - def __add__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __add__(self, other: unifloat) -> 'variable[float]': - ... - - def __add__(self, other: NumLike) -> 'CPNumber': - return _add_op('add', [self, other], True) - - @overload - def __radd__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __radd__(self, other: unifloat) -> 'variable[float]': - ... - - def __radd__(self, other: NumLike) -> 'CPNumber': - return _add_op('add', [self, other], True) - - @overload - def __sub__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __sub__(self, other: unifloat) -> 'variable[float]': - ... - - def __sub__(self, other: NumLike) -> 'CPNumber': - return _add_op('sub', [self, other]) - - @overload - def __rsub__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __rsub__(self, other: unifloat) -> 'variable[float]': - ... - - def __rsub__(self, other: NumLike) -> 'CPNumber': - return _add_op('sub', [other, self]) - - def __truediv__(self, other: NumLike) -> 'variable[float]': - return _add_op('div', [self, other]) - - def __rtruediv__(self, other: NumLike) -> 'variable[float]': - return _add_op('div', [other, self]) - - @overload - def __floordiv__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __floordiv__(self, other: unifloat) -> 'variable[float]': - ... - - def __floordiv__(self, other: NumLike) -> 'CPNumber': - return _add_op('floordiv', [self, other]) - - @overload - def __rfloordiv__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __rfloordiv__(self, other: unifloat) -> 'variable[float]': - ... - - def __rfloordiv__(self, other: NumLike) -> 'CPNumber': - return _add_op('floordiv', [other, self]) - - def __neg__(self: TCPNum) -> TCPNum: - return cast(TCPNum, _add_op('sub', [variable(0), self])) - - def __gt__(self, other: NumLike) -> 'variable[bool]': - ret = _add_op('gt', [self, other]) - return variable(ret.source, dtype='bool') - - def __lt__(self, other: NumLike) -> 'variable[bool]': - ret = _add_op('gt', [other, self]) - return variable(ret.source, dtype='bool') - - def __eq__(self, other: NumLike) -> 'variable[bool]': # type: ignore - ret = _add_op('eq', [self, other], True) - return variable(ret.source, dtype='bool') - - def __ne__(self, other: NumLike) -> 'variable[bool]': # type: ignore - ret = _add_op('ne', [self, other], True) - return variable(ret.source, dtype='bool') - - @overload - def __mod__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __mod__(self, other: unifloat) -> 'variable[float]': - ... - - def __mod__(self, other: NumLike) -> 'CPNumber': - return _add_op('mod', [self, other]) - - @overload - def __rmod__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __rmod__(self, other: unifloat) -> 'variable[float]': - ... - - def __rmod__(self, other: NumLike) -> 'CPNumber': - return _add_op('mod', [other, self]) - - @overload - def __pow__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __pow__(self, other: unifloat) -> 'variable[float]': - ... - - def __pow__(self, other: NumLike) -> 'CPNumber': - return _add_op('pow', [other, self]) - - @overload - def __rpow__(self: TCPNum, other: uniint) -> TCPNum: - ... - - @overload - def __rpow__(self, other: unifloat) -> 'variable[float]': - ... - - def __rpow__(self, other: NumLike) -> 'CPNumber': - return _add_op('rpow', [self, other]) - - def __hash__(self) -> int: - return super().__hash__() - - -class variable(Generic[TNum], CPNumber): +class variable(Generic[TNum], Net): def __init__(self, source: TNum | Node, dtype: str | None = None): if isinstance(source, Node): self.source = source @@ -239,36 +73,185 @@ class variable(Generic[TNum], CPNumber): self.source = CPConstant(source) self.dtype = 'int' + @overload + def __add__(self, other: TCPNum) -> TCPNum: ... + @overload + def __add__(self: TCPNum, other: uniint) -> TCPNum: ... + @overload + def __add__(self, other: unifloat) -> 'variable[float]': ... + @overload + def __add__(self, other: NumLike) -> 'variable[float] | variable[int]': ... + def __add__(self, other: NumLike) -> Any: + return add_op('add', [self, other], True) + + @overload + def __radd__(self: TCPNum, other: int) -> TCPNum: ... + @overload + def __radd__(self, other: float) -> 'variable[float]': ... + def __radd__(self, other: NumLike) -> Any: + return add_op('add', [self, other], True) + + @overload + def __sub__(self, other: TCPNum) -> TCPNum: ... + @overload + def __sub__(self: TCPNum, other: uniint) -> TCPNum: ... + @overload + def __sub__(self, other: unifloat) -> 'variable[float]': ... + @overload + def __sub__(self, other: NumLike) -> 'variable[float] | variable[int]': ... + def __sub__(self, other: NumLike) -> Any: + return add_op('sub', [self, other]) + + @overload + def __rsub__(self: TCPNum, other: int) -> TCPNum: ... + @overload + def __rsub__(self, other: float) -> 'variable[float]': ... + def __rsub__(self, other: NumLike) -> Any: + return add_op('sub', [other, self]) + + @overload + def __mul__(self, other: TCPNum) -> TCPNum: ... + @overload + def __mul__(self: TCPNum, other: uniint) -> TCPNum: ... + @overload + def __mul__(self, other: unifloat) -> 'variable[float]': ... + @overload + def __mul__(self, other: NumLike) -> 'variable[float] | variable[int]': ... + def __mul__(self, other: NumLike) -> Any: + return add_op('mul', [self, other], True) + + @overload + def __rmul__(self: TCPNum, other: int) -> TCPNum: ... + @overload + def __rmul__(self, other: float) -> 'variable[float]': ... + def __rmul__(self, other: NumLike) -> Any: + return add_op('mul', [self, other], True) + + def __truediv__(self, other: NumLike) -> 'variable[float]': + return add_op('div', [self, other]) + + def __rtruediv__(self, other: NumLike) -> 'variable[float]': + return add_op('div', [other, self]) + + @overload + def __floordiv__(self, other: TCPNum) -> TCPNum: ... + @overload + def __floordiv__(self: TCPNum, other: uniint) -> TCPNum: ... + @overload + def __floordiv__(self, other: unifloat) -> 'variable[float]': ... + @overload + def __floordiv__(self, other: NumLike) -> 'variable[float] | variable[int]': ... + def __floordiv__(self, other: NumLike) -> Any: + return add_op('floordiv', [self, other]) + + @overload + def __rfloordiv__(self: TCPNum, other: int) -> TCPNum: ... + @overload + def __rfloordiv__(self, other: float) -> 'variable[float]': ... + def __rfloordiv__(self, other: NumLike) -> Any: + return add_op('floordiv', [other, self]) + + def __neg__(self: TCPNum) -> TCPNum: + return cast(TCPNum, add_op('sub', [variable(0), self])) + + def __gt__(self, other: NumLike) -> 'variable[bool]': + ret = add_op('gt', [self, other]) + return variable(ret.source, dtype='bool') + + def __lt__(self, other: NumLike) -> 'variable[bool]': + ret = add_op('gt', [other, self]) + return variable(ret.source, dtype='bool') + + def __ge__(self, other: NumLike) -> 'variable[bool]': + ret = add_op('ge', [self, other]) + return variable(ret.source, dtype='bool') + + def __le__(self, other: NumLike) -> 'variable[bool]': + ret = add_op('ge', [other, self]) + return variable(ret.source, dtype='bool') + + def __eq__(self, other: NumLike) -> 'variable[bool]': # type: ignore + ret = add_op('eq', [self, other], True) + return variable(ret.source, dtype='bool') + + def __ne__(self, other: NumLike) -> 'variable[bool]': # type: ignore + ret = add_op('ne', [self, other], True) + return variable(ret.source, dtype='bool') + + @overload + def __mod__(self, other: TCPNum) -> TCPNum: ... + @overload + def __mod__(self: TCPNum, other: uniint) -> TCPNum: ... + @overload + def __mod__(self, other: unifloat) -> 'variable[float]': ... + @overload + def __mod__(self, other: NumLike) -> 'variable[float] | variable[int]': ... + def __mod__(self, other: NumLike) -> Any: + return add_op('mod', [self, other]) + + @overload + def __rmod__(self: TCPNum, other: int) -> TCPNum: ... + @overload + def __rmod__(self, other: float) -> 'variable[float]': ... + def __rmod__(self, other: NumLike) -> Any: + return add_op('mod', [other, self]) + + @overload + def __pow__(self, other: TCPNum) -> TCPNum: ... + @overload + def __pow__(self: TCPNum, other: uniint) -> TCPNum: ... + @overload + def __pow__(self, other: unifloat) -> 'variable[float]': ... + @overload + def __pow__(self, other: NumLike) -> 'variable[float] | variable[int]': ... + def __pow__(self, other: NumLike) -> Any: + if not isinstance(other, variable): + if other == 2: + return self * self + if other == -1: + return 1 / self + return add_op('pow', [self, other]) + + @overload + def __rpow__(self: TCPNum, other: int) -> TCPNum: ... + @overload + def __rpow__(self, other: float) -> 'variable[float]': ... + def __rpow__(self, other: NumLike) -> Any: + return add_op('rpow', [other, self]) + + def __hash__(self) -> int: + return super().__hash__() + # Bitwise and shift operations for cp[int] def __lshift__(self, other: uniint) -> 'variable[int]': - return _add_op('lshift', [self, other]) + return add_op('lshift', [self, other]) def __rlshift__(self, other: uniint) -> 'variable[int]': - return _add_op('lshift', [other, self]) + return add_op('lshift', [other, self]) def __rshift__(self, other: uniint) -> 'variable[int]': - return _add_op('rshift', [self, other]) + return add_op('rshift', [self, other]) def __rrshift__(self, other: uniint) -> 'variable[int]': - return _add_op('rshift', [other, self]) + return add_op('rshift', [other, self]) def __and__(self, other: uniint) -> 'variable[int]': - return _add_op('bwand', [self, other], True) + return add_op('bwand', [self, other], True) def __rand__(self, other: uniint) -> 'variable[int]': - return _add_op('rwand', [other, self], True) + return add_op('rwand', [other, self], True) def __or__(self, other: uniint) -> 'variable[int]': - return _add_op('bwor', [self, other], True) + return add_op('bwor', [self, other], True) def __ror__(self, other: uniint) -> 'variable[int]': - return _add_op('bwor', [other, self], True) + return add_op('bwor', [other, self], True) def __xor__(self, other: uniint) -> 'variable[int]': - return _add_op('bwxor', [self, other], True) + return add_op('bwxor', [self, other], True) def __rxor__(self, other: uniint) -> 'variable[int]': - return _add_op('bwxor', [other, self], True) + return add_op('bwxor', [other, self], True) class CPConstant(Node): @@ -303,42 +286,24 @@ def net_from_value(value: Any) -> Net: @overload -def iif(expression: CPNumber, true_result: unibool, false_result: unibool) -> variable[bool]: # pyright: ignore[reportOverlappingOverload] - ... - - +def iif(expression: variable[Any], true_result: unibool, false_result: unibool) -> variable[bool]: ... # pyright: ignore[reportOverlappingOverload] @overload -def iif(expression: CPNumber, true_result: uniint, false_result: uniint) -> variable[int]: - ... - - +def iif(expression: variable[Any], true_result: uniint, false_result: uniint) -> variable[int]: ... @overload -def iif(expression: CPNumber, true_result: unifloat, false_result: unifloat) -> variable[float]: - ... - - +def iif(expression: variable[Any], true_result: unifloat, false_result: unifloat) -> variable[float]: ... @overload -def iif(expression: float | int, true_result: TNum, false_result: TNum) -> TNum: - ... - - +def iif(expression: float | int, true_result: TNum, false_result: TNum) -> TNum: ... @overload -def iif(expression: float | int, true_result: TNum, false_result: variable[TNum]) -> variable[TNum]: - ... - - +def iif(expression: float | int, true_result: TNum, false_result: variable[TNum]) -> variable[TNum]: ... @overload -def iif(expression: float | int, true_result: variable[TNum], false_result: TNum | variable[TNum]) -> variable[TNum]: - ... - - +def iif(expression: float | int, true_result: variable[TNum], false_result: TNum | variable[TNum]) -> variable[TNum]: ... def iif(expression: Any, true_result: Any, false_result: Any) -> Any: allowed_type = (variable, int, float, bool) assert isinstance(true_result, allowed_type) and isinstance(false_result, allowed_type), "Result type not supported" return (expression != 0) * true_result + (expression == 0) * false_result -def _add_op(op: str, args: list[CPNumber | int | float], commutative: bool = False) -> variable[Any]: +def add_op(op: str, args: list[variable[Any] | int | float], commutative: bool = False) -> variable[Any]: arg_nets = [a if isinstance(a, Net) else net_from_value(a) for a in args] if commutative: @@ -351,10 +316,10 @@ def _add_op(op: str, args: list[CPNumber | int | float], commutative: bool = Fal result_type = generic_sdb.stencil_definitions[typed_op].split('_')[0] - if result_type == 'int': - return variable[int](Op(typed_op, arg_nets), result_type) - else: + if result_type == 'float': return variable[float](Op(typed_op, arg_nets), result_type) + else: + return variable[int](Op(typed_op, arg_nets), result_type) def _get_data_and_dtype(value: Any) -> tuple[str, float | int]: diff --git a/src/copapy/_math.py b/src/copapy/_math.py new file mode 100644 index 0000000..4f4d2a8 --- /dev/null +++ b/src/copapy/_math.py @@ -0,0 +1,24 @@ +from . import variable, NumLike +from typing import TypeVar, Any, overload +from ._basic_types import add_op + +T = TypeVar("T", int, float, variable[int], variable[float]) + + +@overload +def sqrt(x: float | int) -> float: ... +@overload +def sqrt(x: variable[Any]) -> variable[float]: ... +def sqrt(x: NumLike) -> variable[float] | float: + """Square root function""" + if isinstance(x, variable): + return add_op('sqrt', [x, x]) # TODO: fix 2. dummy argument + return float(x ** 0.5) + + +def abs(x: T) -> T: + """Absolute value function""" + ret = (x < 0) * -x + (x >= 0) * x + return ret # pyright: ignore[reportReturnType] + + diff --git a/src/copapy/_vectors.py b/src/copapy/_vectors.py index c129f20..9244d86 100644 --- a/src/copapy/_vectors.py +++ b/src/copapy/_vectors.py @@ -1,42 +1,158 @@ -from copapy import CPNumber, variable -from typing import Generic, TypeVar, Iterable, Any, overload +from . import variable +from typing import Generic, TypeVar, Iterable, Any, overload, TypeAlias +from ._math import sqrt + +VecNumLike: TypeAlias = 'vector[int] | vector[float] | variable[int] | variable[float] | int | float' +VecIntLike: TypeAlias = 'vector[int] | variable[int] | int' +VecFloatLike: TypeAlias = 'vector[float] | variable[float] | float' +T = TypeVar("T", int, float) + +epsilon = 1e-10 -T = TypeVar("T", int, float, bool) -T2 = TypeVar("T2", bound=CPNumber) class vector(Generic[T]): + """Type-safe vector supporting numeric promotion between vector types.""" def __init__(self, values: Iterable[T | variable[T]]): - #self.values: tuple[variable[T], ...] = tuple(v if isinstance(v, variable) else variable(v) for v in values) self.values: tuple[variable[T] | T, ...] = tuple(values) - @overload - def __add__(self, other: 'vector[float] | variable[float] | float') -> 'vector[float]': - ... + # ---------- Basic dunder methods ---------- + def __repr__(self) -> str: + return f"vector({self.values})" + + def __len__(self) -> int: + return len(self.values) + + def __getitem__(self, index: int) -> variable[T] | T: + return self.values[index] @overload - def __add__(self: 'vector[T]', other: 'vector[int] | variable[int] | int') -> 'vector[T]': - ... - - def __add__(self, other: 'vector[Any] | variable[Any] | float | int') -> Any: + def __add__(self: 'vector[int]', other: VecFloatLike) -> 'vector[float]': ... + @overload + def __add__(self: 'vector[int]', other: VecIntLike) -> 'vector[int]': ... + @overload + def __add__(self: 'vector[float]', other: VecNumLike) -> 'vector[float]': ... + @overload + def __add__(self, other: VecNumLike) -> 'vector[int] | vector[float]': ... + def __add__(self, other: VecNumLike) -> Any: if isinstance(other, vector): assert len(self.values) == len(other.values) return vector(a + b for a, b in zip(self.values, other.values)) - else: - return vector(a + other for a in self.values) + return vector(a + other for a in self.values) + + @overload + def __radd__(self: 'vector[float]', other: VecNumLike) -> 'vector[float]': ... + @overload + def __radd__(self: 'vector[int]', other: variable[int] | int) -> 'vector[int]': ... + def __radd__(self, other: Any) -> Any: + return self + other + + @overload + def __sub__(self: 'vector[int]', other: VecFloatLike) -> 'vector[float]': ... + @overload + def __sub__(self: 'vector[int]', other: VecIntLike) -> 'vector[int]': ... + @overload + def __sub__(self: 'vector[float]', other: VecNumLike) -> 'vector[float]': ... + @overload + def __sub__(self, other: VecNumLike) -> 'vector[int] | vector[float]': ... + def __sub__(self, other: VecNumLike) -> Any: + if isinstance(other, vector): + assert len(self.values) == len(other.values) + return vector(a - b for a, b in zip(self.values, other.values)) + return vector(a - other for a in self.values) + + @overload + def __rsub__(self: 'vector[float]', other: VecNumLike) -> 'vector[float]': ... + @overload + def __rsub__(self: 'vector[int]', other: variable[int] | int) -> 'vector[int]': ... + def __rsub__(self, other: VecNumLike) -> Any: + if isinstance(other, vector): + assert len(self.values) == len(other.values) + return vector(b - a for a, b in zip(self.values, other.values)) + return vector(other - a for a in self.values) + + @overload + def __mul__(self: 'vector[int]', other: VecFloatLike) -> 'vector[float]': ... + @overload + def __mul__(self: 'vector[int]', other: VecIntLike) -> 'vector[int]': ... + @overload + def __mul__(self: 'vector[float]', other: 'vector[int] | float | int | variable[int]') -> 'vector[float]': ... + @overload + def __mul__(self, other: VecNumLike) -> 'vector[int] | vector[float]': ... + def __mul__(self, other: VecNumLike) -> Any: + if isinstance(other, vector): + assert len(self.values) == len(other.values) + return vector(a * b for a, b in zip(self.values, other.values)) + return vector(a * other for a in self.values) + + @overload + def __rmul__(self: 'vector[float]', other: VecNumLike) -> 'vector[float]': ... + @overload + def __rmul__(self: 'vector[int]', other: variable[int] | int) -> 'vector[int]': ... + def __rmul__(self, other: VecNumLike) -> Any: + return self * other + + def __truediv__(self, other: VecNumLike) -> 'vector[float]': + if isinstance(other, vector): + assert len(self.values) == len(other.values) + return vector(a / b for a, b in zip(self.values, other.values)) + return vector(a / other for a in self.values) + + def __rtruediv__(self, other: VecNumLike) -> 'vector[float]': + if isinstance(other, vector): + assert len(self.values) == len(other.values) + return vector(b / a for a, b in zip(self.values, other.values)) + return vector(other / a for a in self.values) + + @overload + def dot(self: 'vector[int]', other: 'vector[int]') -> int | variable[int]: ... + @overload + def dot(self, other: 'vector[float]') -> float | variable[float]: ... + @overload + def dot(self: 'vector[float]', other: 'vector[int] | vector[float]') -> float | variable[float]: ... + @overload + def dot(self, other: 'vector[int] | vector[float]') -> float | int | variable[float] | variable[int]: ... + def dot(self, other: 'vector[int] | vector[float]') -> Any: + assert len(self.values) == len(other.values) + return sum(a * b for a, b in zip(self.values, other.values)) + + # @ operator + @overload + def __matmul__(self: 'vector[int]', other: 'vector[int]') -> int | variable[int]: ... + @overload + def __matmul__(self, other: 'vector[float]') -> float | variable[float]: ... + @overload + def __matmul__(self: 'vector[float]', other: 'vector[int] | vector[float]') -> float | variable[float]: ... + @overload + def __matmul__(self, other: 'vector[int] | vector[float]') -> float | int | variable[float] | variable[int]: ... + def __matmul__(self, other: 'vector[int] | vector[float]') -> Any: + return self.dot(other) + + def cross(self: 'vector[float]', other: 'vector[float]') -> 'vector[float]': + """3D cross product""" + assert len(self.values) == 3 and len(other.values) == 3 + a1, a2, a3 = self.values + b1, b2, b3 = other.values + return vector([ + a2 * b3 - a3 * b2, + a3 * b1 - a1 * b3, + a1 * b2 - a2 * b1 + ]) + + @overload + def sum(self: 'vector[int]') -> int | variable[int]: ... + @overload + def sum(self: 'vector[float]') -> float | variable[float]: ... + def sum(self) -> Any: + return sum(a for a in self.values if isinstance(a, variable)) +\ + sum(a for a in self.values if not isinstance(a, variable)) + + def magnitude(self) -> 'float | variable[float]': + s = sum(a * a for a in self.values) + return sqrt(s) if isinstance(s, variable) else sqrt(s) + + def normalize(self) -> 'vector[float]': + mag = self.magnitude() + epsilon + return self / mag - #@overload - #def sum(self: 'vector[float]') -> variable[float]: - # ... - - #@overload - #def sum(self: 'vector[int]') -> variable[int]: - # ... - - #def sum(self: 'vector[T]') -> variable[T] | T: - # comp_time = sum(v for v in self.values if not isinstance(v, variable)) - # run_time = sum(v for v in self.values if isinstance(v, variable)) - # if isinstance(run_time, variable): - # return comp_time + run_time # type: ignore - # else: - # return comp_time - \ No newline at end of file + def __iter__(self) -> Iterable[variable[T] | T]: + return iter(self.values) \ No newline at end of file diff --git a/stencils/aux_functions.c b/stencils/aux_functions.c index de2c562..fc4ff55 100644 --- a/stencils/aux_functions.c +++ b/stencils/aux_functions.c @@ -12,6 +12,19 @@ __attribute__((noinline)) int floor_div(float arg1, float arg2) { return i; } +float fast_sqrt(float n) { + if (n < 0) return -1; + + float x = n; // initial guess + float epsilon = 0.00001; // desired accuracy + + while ((x - n / x) > epsilon || (x - n / x) < -epsilon) { + x = 0.5 * (x + n / x); + } + + return x; +} + float fast_pow_float(float base, float exponent) { union { float f; diff --git a/stencils/generate_stencils.py b/stencils/generate_stencils.py index 216c642..81fbbd7 100644 --- a/stencils/generate_stencils.py +++ b/stencils/generate_stencils.py @@ -4,7 +4,7 @@ from pathlib import Path import os op_signs = {'add': '+', 'sub': '-', 'mul': '*', 'div': '/', 'pow': '**', - 'gt': '>', 'eq': '==', 'ne': '!=', 'mod': '%'} + 'gt': '>', 'eq': '==', 'ge': '>=', 'ne': '!=', 'mod': '%'} entry_func_prefix = '' stencil_func_prefix = '__attribute__((naked)) ' # Remove callee prolog @@ -78,6 +78,15 @@ def get_cast(type1: str, type2: str, type_out: str) -> str: """ +@norm_indent +def get_sqrt(type1: str, type2: str) -> str: + return f""" + {stencil_func_prefix}void sqrt_{type1}_{type2}({type1} arg1, {type2} arg2) {{ + result_float_{type2}(fast_sqrt((float)arg1), arg2); + }} + """ + + @norm_indent def get_conv_code(type1: str, type2: str, type_out: str) -> str: return f""" @@ -189,7 +198,7 @@ if __name__ == "__main__": # Scalar arithmetic: types = ['int', 'float'] - ops = ['add', 'sub', 'mul', 'div', 'floordiv', 'gt', 'eq', 'ne', 'pow'] + ops = ['add', 'sub', 'mul', 'div', 'floordiv', 'gt', 'ge', 'eq', 'ne', 'pow'] for t1 in types: code += get_result_stubs1(t1) @@ -203,6 +212,9 @@ if __name__ == "__main__": t_out = 'int' if t1 == 'float' else 'float' code += get_cast(t1, t2, t_out) + for t1, t2 in permutate(types, types): + code += get_sqrt(t1, t2) + for op, t1, t2 in permutate(ops, types, types): t_out = t1 if t1 == t2 else 'float' if op == 'floordiv': @@ -211,7 +223,7 @@ if __name__ == "__main__": code += get_op_code_float(op, t1, t2) elif op == 'pow': code += get_pow(t1, t2) - elif op == 'gt' or op == 'eq' or op == 'ne': + elif op in {'gt', 'eq', 'ge', 'ne'}: code += get_op_code(op, t1, t2, 'int') else: code += get_op_code(op, t1, t2, t_out) diff --git a/tests/test_ext_ops.py b/tests/test_ext_ops.py deleted file mode 100644 index d3a2955..0000000 --- a/tests/test_ext_ops.py +++ /dev/null @@ -1,31 +0,0 @@ -from copapy import variable, Target -import pytest -import copapy - - -def test_compile(): - c_i = variable(9) - c_f = variable(2.5) - # c_b = variable(True) - - ret_test = (c_f ** c_f, c_i ** c_i)#, c_i & 3) - ret_ref = (2.5 ** 2.5, 9 ** 9)#, 9 & 3) - - tg = Target() - print('* compile and copy ...') - tg.compile(ret_test) - print('* run and copy ...') - tg.run() - print('* finished') - - for test, ref in zip(ret_test, ret_ref): - assert isinstance(test, copapy.CPNumber) - val = tg.read_value(test) - print('+', val, ref, type(val), test.dtype) - #for t in (int, float, bool): - # assert isinstance(val, t) == isinstance(ref, t), f"Result type does not match for {val} and {ref}" - assert val == pytest.approx(ref, 2), f"Result does not match: {val} and reference: {ref}" # pyright: ignore[reportUnknownMemberType] - - -if __name__ == "__main__": - test_compile() diff --git a/tests/test_math.py b/tests/test_math.py new file mode 100644 index 0000000..246b976 --- /dev/null +++ b/tests/test_math.py @@ -0,0 +1,56 @@ +from copapy import variable, Target +import pytest +import copapy + + +def test_corse(): + c_i = variable(9) + c_f = variable(2.5) + # c_b = variable(True) + + ret_test = (c_f ** c_f, c_i ** c_i)#, c_i & 3) + ret_ref = (2.5 ** 2.5, 9 ** 9)#, 9 & 3) + + tg = Target() + print('* compile and copy ...') + tg.compile(ret_test) + print('* run and copy ...') + tg.run() + print('* finished') + + for test, ref in zip(ret_test, ret_ref): + assert isinstance(test, copapy.variable) + val = tg.read_value(test) + print('+', val, ref, type(val), test.dtype) + #for t in (int, float, bool): + # assert isinstance(val, t) == isinstance(ref, t), f"Result type does not match for {val} and {ref}" + assert val == pytest.approx(ref, 2), f"Result does not match: {val} and reference: {ref}" # pyright: ignore[reportUnknownMemberType] + + +def test_fine(): + c_i = variable(9) + c_f = variable(2.5) + # c_b = variable(True) + + ret_test = (c_f ** 2, c_i ** -1)#, c_i & 3) + ret_ref = (2.5 ** 2, 9 ** -1)#, 9 & 3) + + tg = Target() + print('* compile and copy ...') + tg.compile(ret_test) + print('* run and copy ...') + tg.run() + print('* finished') + + for test, ref in zip(ret_test, ret_ref): + assert isinstance(test, copapy.variable) + val = tg.read_value(test) + print('+', val, ref, type(val), test.dtype) + #for t in (int, float, bool): + # assert isinstance(val, t) == isinstance(ref, t), f"Result type does not match for {val} and {ref}" + assert val == pytest.approx(ref, 0.001), f"Result does not match: {val} and reference: {ref}" # pyright: ignore[reportUnknownMemberType] + + +if __name__ == "__main__": + test_corse() + test_fine() diff --git a/tests/test_ops.py b/tests/test_ops.py index aaa4ca0..e2bd62c 100644 --- a/tests/test_ops.py +++ b/tests/test_ops.py @@ -57,7 +57,7 @@ def test_compile(): print('* finished') for test, ref in zip(ret_test, ret_ref): - assert isinstance(test, copapy.CPNumber) + assert isinstance(test, copapy.variable) val = tg.read_value(test) print('+', val, ref, test.dtype) for t in (int, float, bool): diff --git a/tests/test_prog_flow.py b/tests/test_prog_flow.py index 040adbc..bd796c8 100644 --- a/tests/test_prog_flow.py +++ b/tests/test_prog_flow.py @@ -19,7 +19,7 @@ def test_compile(): print('* finished') for test, ref in zip(ret_test, ret_ref): - assert isinstance(test, copapy.CPNumber) + assert isinstance(test, copapy.variable) val = tg.read_value(test) print('+', val, ref, type(val), test.dtype) #for t in (int, float, bool): diff --git a/tests/test_vector.py b/tests/test_vector.py index 9379724..f2e709b 100644 --- a/tests/test_vector.py +++ b/tests/test_vector.py @@ -1,5 +1,29 @@ import copapy as cp -def test_vec(): - tt = cp.vector(range(3)) + cp.vector([1.1,2.2,3.3]) - tt2 = (cp.vector(range(3)) + 5.6) \ No newline at end of file +def test_vectors_init(): + tt1 = cp.vector(range(3)) + cp.vector([1.1,2.2,3.3]) + tt2 = cp.vector([1.1,2,cp.variable(5)])# + cp.vector(range(3)) + tt3 = (cp.vector(range(3)) + 5.6) + tt4 = cp.vector([1.1,2,3]) + cp.vector(cp.variable(v) for v in range(3)) + tt5 = cp.vector([1,2,3]).dot(tt4) + + print(tt1, tt2, tt3, tt4, tt5) + + +def test_compiled_vectors(): + t1 = cp.vector([10, 11, 12]) + cp.vector(cp.variable(v) for v in range(3)) + t2 = t1.sum() + + t3 = cp.vector(cp.variable(1 / (v + 1)) for v in range(3)) + t4 = ((t3 * t1) * 2).magnitude() + + + tg = cp.Target() + tg.compile(t2, t4) + tg.run() + + assert isinstance(t2, cp.variable) and tg.read_value(t2) == 10 + 11 + 12 + 0 + 1 + 2 + assert isinstance(t4, cp.variable) and tg.read_value(t4) == ((1/1*10 + 1/2*11 + 1/3*12) * 2)**0.5 + +if __name__ == "__main__": + test_compiled_vectors() From 538bf23412e2ac6ae61e99311efda6a2d04c1d64 Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Sun, 26 Oct 2025 12:37:20 +0100 Subject: [PATCH 05/13] stencil: __attribute__((noinline)) added to fast_sqrt since it branches witch is not allowed for stencils --- stencils/aux_functions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stencils/aux_functions.c b/stencils/aux_functions.c index fc4ff55..0cd5cb6 100644 --- a/stencils/aux_functions.c +++ b/stencils/aux_functions.c @@ -12,7 +12,7 @@ __attribute__((noinline)) int floor_div(float arg1, float arg2) { return i; } -float fast_sqrt(float n) { +__attribute__((noinline)) float fast_sqrt(float n) { if (n < 0) return -1; float x = n; // initial guess From e400eff2b0de0b6f51423e8c9406347ba6561557 Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Sun, 26 Oct 2025 12:37:44 +0100 Subject: [PATCH 06/13] compiler: Added patching for aux functions --- src/copapy/_compiler.py | 43 +++++++++++++++++++++++++++++------------ src/copapy/_stencils.py | 8 ++++++-- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/copapy/_compiler.py b/src/copapy/_compiler.py index ccd1b25..7b614a5 100644 --- a/src/copapy/_compiler.py +++ b/src/copapy/_compiler.py @@ -166,8 +166,8 @@ def get_data_layout(variable_list: Iterable[Net], sdb: stencil_database, offset: return object_list, offset -def get_target_sym_lookup(function_names: Iterable[str], sdb: stencil_database) -> dict[str, patch_entry]: - return {patch.target_symbol_name: patch for name in set(function_names) for patch in sdb.get_patch_positions(name)} +#def get_target_sym_lookup(function_names: Iterable[str], sdb: stencil_database) -> dict[str, patch_entry]: +# return {patch.target_symbol_name: patch for name in set(function_names) for patch in sdb.get_patch_positions(name)} def get_section_layout(section_indexes: Iterable[int], sdb: stencil_database, offset: int = 0) -> tuple[list[tuple[int, int, int]], int]: @@ -221,18 +221,18 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database dw.write_int(variables_data_lengths) # Heap constants - for section_id, out_offs, lengths in section_mem_layout: + for section_id, start, lengths in section_mem_layout: dw.write_com(binw.Command.COPY_DATA) - dw.write_int(out_offs) + dw.write_int(start) dw.write_int(lengths) dw.write_bytes(sdb.get_section_data(section_id)) # Heap variables - for net, out_offs, lengths in variable_mem_layout: - variables[net] = (out_offs, lengths, net.dtype) + for net, start, lengths in variable_mem_layout: + variables[net] = (start, lengths, net.dtype) if isinstance(net.source, CPConstant): dw.write_com(binw.Command.COPY_DATA) - dw.write_int(out_offs) + dw.write_int(start) dw.write_int(lengths) dw.write_value(net.source.value, lengths) # print(f'+ {net.dtype} {net.source.value}') @@ -246,7 +246,7 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database section_addr_lookup = {id: offs for id, offs, _ in section_mem_layout} offset = aux_function_lengths # offset in generated code chunk - # assemble stencils to main program + # assemble stencils to main program and patch stencils data = sdb.get_function_code('entry_function_shell', 'start') data_list.append(data) offset += len(data) @@ -257,7 +257,7 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database data_list.append(data) #print(f"* {node.name} ({offset}) " + ' '.join(f'{d:02X}' for d in data)) - for patch in sdb.get_patch_positions(node.name): + for patch in sdb.get_patch_positions(node.name, stencil=True): if patch.target_symbol_info in {'STT_OBJECT', 'STT_NOTYPE'}: if patch.target_symbol_name.startswith('dummy_'): # Patch for write and read addresses to/from heap variables @@ -265,8 +265,11 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database #print(f"Patch for write and read addresses to/from heap variables: {node.name} {patch.target_symbol_info} {patch.target_symbol_name}") addr = object_addr_lookup[associated_net] patch_value = addr + patch.addend - (offset + patch.addr) + elif patch.target_symbol_name.startswith('result_'): + raise Exception(f"Stencil {node.name} seams to branch to multiple result_* calls.") else: # Patch constants addresses on heap + print('##', section_addr_lookup, node.name, patch) addr = section_addr_lookup[patch.target_symbol_section_index] patch_value = addr + patch.addend - (offset + patch.addr) patch_list.append((patch.type.value, offset + patch.addr, patch_value, binw.Command.PATCH_OBJECT)) @@ -288,13 +291,29 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database dw.write_com(binw.Command.ALLOCATE_CODE) dw.write_int(offset) - # write aux functions - for name, out_offs, lengths in aux_function_mem_layout: + # write aux functions code + for name, start, lengths in aux_function_mem_layout: dw.write_com(binw.Command.COPY_CODE) - dw.write_int(out_offs) + dw.write_int(start) dw.write_int(lengths) dw.write_bytes(sdb.get_function_code(name)) + # Patch aux functions + for name, start, lengths in aux_function_mem_layout: + for patch in sdb.get_patch_positions(name): + if patch.target_symbol_info in {'STT_OBJECT', 'STT_NOTYPE'}: + # Patch constants/variable addresses on heap + addr = section_addr_lookup[patch.target_symbol_section_index] + patch_value = addr + patch.addend - (start + patch.addr) + patch_list.append((patch.type.value, start + patch.addr, patch_value, binw.Command.PATCH_OBJECT)) + + elif patch.target_symbol_info == 'STT_FUNC': + addr = aux_func_addr_lookup[patch.target_symbol_name] + patch_value = addr + patch.addend - (start + patch.addr) + patch_list.append((patch.type.value, start + patch.addr, patch_value, binw.Command.PATCH_FUNC)) + else: + raise ValueError(f"Unsupported: {name} {patch.target_symbol_info} {patch.target_symbol_name}") + # write entry function code dw.write_com(binw.Command.COPY_CODE) dw.write_int(aux_function_lengths) diff --git a/src/copapy/_stencils.py b/src/copapy/_stencils.py index 91714dc..c4e6605 100644 --- a/src/copapy/_stencils.py +++ b/src/copapy/_stencils.py @@ -119,7 +119,7 @@ class stencil_database(): ret.add(sym.section.index) return list(ret) - def get_patch_positions(self, symbol_name: str) -> Generator[patch_entry, None, None]: + def get_patch_positions(self, symbol_name: str, stencil: bool = False) -> Generator[patch_entry, None, None]: """Return patch positions for a provided symbol (function or object) Args: @@ -129,7 +129,11 @@ class stencil_database(): patch_entry: every relocation for the symbol """ symbol = self.elf.symbols[symbol_name] - start_index, end_index = get_stencil_position(symbol) + if stencil: + start_index, end_index = get_stencil_position(symbol) + else: + start_index = 0 + end_index = symbol.fields['st_size'] for reloc in symbol.relocations: From 501bd5bee392cedcfa51d389242f90ce03010f26 Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Sun, 26 Oct 2025 13:21:35 +0100 Subject: [PATCH 07/13] example generation to track down sqrt issue --- src/copapy/_compiler.py | 20 +++++++++++++------- tests/test_vector.py | 5 +++-- tools/make_example.py | 9 +++++---- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/copapy/_compiler.py b/src/copapy/_compiler.py index 7b614a5..329c301 100644 --- a/src/copapy/_compiler.py +++ b/src/copapy/_compiler.py @@ -1,6 +1,6 @@ from typing import Generator, Iterable, Any from . import _binwrite as binw -from ._stencils import stencil_database, patch_entry +from ._stencils import stencil_database from collections import defaultdict, deque from ._basic_types import Net, Node, Write, CPConstant, Op, transl_type @@ -244,12 +244,11 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database # Prepare program code and relocations object_addr_lookup = {net: offs for net, offs, _ in variable_mem_layout} section_addr_lookup = {id: offs for id, offs, _ in section_mem_layout} - offset = aux_function_lengths # offset in generated code chunk # assemble stencils to main program and patch stencils data = sdb.get_function_code('entry_function_shell', 'start') data_list.append(data) - offset += len(data) + offset = aux_function_lengths + len(data) for associated_net, node in extended_output_ops: assert node.name in sdb.stencil_definitions, f"- Warning: {node.name} stencil not found" @@ -273,11 +272,13 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database addr = section_addr_lookup[patch.target_symbol_section_index] patch_value = addr + patch.addend - (offset + patch.addr) patch_list.append((patch.type.value, offset + patch.addr, patch_value, binw.Command.PATCH_OBJECT)) + print(patch.type, patch.addr, binw.Command.PATCH_OBJECT, node.name) elif patch.target_symbol_info == 'STT_FUNC': addr = aux_func_addr_lookup[patch.target_symbol_name] patch_value = addr + patch.addend - (offset + patch.addr) patch_list.append((patch.type.value, offset + patch.addr, patch_value, binw.Command.PATCH_FUNC)) + print(patch.type, patch.addr, binw.Command.PATCH_FUNC, node.name, '->', patch.target_symbol_name) else: raise ValueError(f"Unsupported: {node.name} {patch.target_symbol_info} {patch.target_symbol_name}") @@ -303,17 +304,22 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database for patch in sdb.get_patch_positions(name): if patch.target_symbol_info in {'STT_OBJECT', 'STT_NOTYPE'}: # Patch constants/variable addresses on heap - addr = section_addr_lookup[patch.target_symbol_section_index] - patch_value = addr + patch.addend - (start + patch.addr) + section_addr = section_addr_lookup[patch.target_symbol_section_index] + patch_value = section_addr + patch.addend - (start + patch.addr) patch_list.append((patch.type.value, start + patch.addr, patch_value, binw.Command.PATCH_OBJECT)) + print(patch.type, patch.addr, section_addr, binw.Command.PATCH_OBJECT, name) + #print(patch.type, start + patch.addr, patch_value, binw.Command.PATCH_OBJECT) elif patch.target_symbol_info == 'STT_FUNC': - addr = aux_func_addr_lookup[patch.target_symbol_name] - patch_value = addr + patch.addend - (start + patch.addr) + aux_func_addr = aux_func_addr_lookup[patch.target_symbol_name] + patch_value = aux_func_addr + patch.addend - (start + patch.addr) patch_list.append((patch.type.value, start + patch.addr, patch_value, binw.Command.PATCH_FUNC)) + else: raise ValueError(f"Unsupported: {name} {patch.target_symbol_info} {patch.target_symbol_name}") + assert False, aux_function_mem_layout + # write entry function code dw.write_com(binw.Command.COPY_CODE) dw.write_int(aux_function_lengths) diff --git a/tests/test_vector.py b/tests/test_vector.py index f2e709b..90635d2 100644 --- a/tests/test_vector.py +++ b/tests/test_vector.py @@ -15,7 +15,8 @@ def test_compiled_vectors(): t2 = t1.sum() t3 = cp.vector(cp.variable(1 / (v + 1)) for v in range(3)) - t4 = ((t3 * t1) * 2).magnitude() + #t4 = ((t3 * t1) * 2).magnitude() + t4 = ((t3 * t1) * 2).sum() tg = cp.Target() @@ -23,7 +24,7 @@ def test_compiled_vectors(): tg.run() assert isinstance(t2, cp.variable) and tg.read_value(t2) == 10 + 11 + 12 + 0 + 1 + 2 - assert isinstance(t4, cp.variable) and tg.read_value(t4) == ((1/1*10 + 1/2*11 + 1/3*12) * 2)**0.5 + #assert isinstance(t4, cp.variable) and tg.read_value(t4) == ((1/1*10 + 1/2*11 + 1/3*12) * 2)**0.5 if __name__ == "__main__": test_compiled_vectors() diff --git a/tools/make_example.py b/tools/make_example.py index 9157307..37f3b05 100644 --- a/tools/make_example.py +++ b/tools/make_example.py @@ -1,18 +1,19 @@ from copapy import _binwrite, variable from copapy.backend import Write, compile_to_instruction_list -import copapy +import copapy as cp def test_compile() -> None: - c1 = variable(9) + c1 = variable(9.0) #ret = [c1 / 4, c1 / -4, c1 // 4, c1 // -4, (c1 * -1) // 4] - ret = [c1 // 3.3 + 5] + #ret = [c1 // 3.3 + 5] + ret = [cp.sqrt(c1)] out = [Write(r) for r in ret] - il, _ = compile_to_instruction_list(out, copapy.generic_sdb) + il, _ = compile_to_instruction_list(out, cp.generic_sdb) # run program command il.write_com(_binwrite.Command.RUN_PROG) From 362e5d19c92cdd21bdaad6e0c4214e06a8c5d2d9 Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Sun, 26 Oct 2025 14:08:48 +0100 Subject: [PATCH 08/13] ci updated to improve runtime --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d549c2..81fff48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: build-ubuntu: needs: [build_stencils] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: matrix: @@ -53,7 +53,10 @@ jobs: gcc -DENABLE_BASIC_LOGGING -O3 -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wstrict-overflow -Werror -g src/coparun/runmem.c src/coparun/coparun.c src/coparun/mem_man.c -o bin/coparun - name: Install ARM binutils - run: sudo apt-get update && sudo apt-get install -y binutils-aarch64-linux-gnu + run: | + which aarch64-linux-gnu-objdump || echo "Not found: aarch64-linux-gnu-objdump" + sudo apt-get install --no-install-recommends -y binutils-aarch64-linux-gnu + - name: Generate debug asm files if: strategy.job-index == 0 From fb4df412cee4143bcf7bd4a4d81009fcd9907cf2 Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Sun, 26 Oct 2025 14:09:45 +0100 Subject: [PATCH 09/13] extract_code tool: added patching of function call relocations --- src/copapy/_compiler.py | 2 +- stencils/aux_functions.c | 15 ++++++++++++++- tests/test_vector.py | 9 +++++---- tools/extract_code.py | 2 ++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/copapy/_compiler.py b/src/copapy/_compiler.py index 329c301..3d697c4 100644 --- a/src/copapy/_compiler.py +++ b/src/copapy/_compiler.py @@ -318,7 +318,7 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database else: raise ValueError(f"Unsupported: {name} {patch.target_symbol_info} {patch.target_symbol_name}") - assert False, aux_function_mem_layout + #assert False, aux_function_mem_layout # write entry function code dw.write_com(binw.Command.COPY_CODE) diff --git a/stencils/aux_functions.c b/stencils/aux_functions.c index 0cd5cb6..c166acf 100644 --- a/stencils/aux_functions.c +++ b/stencils/aux_functions.c @@ -12,7 +12,7 @@ __attribute__((noinline)) int floor_div(float arg1, float arg2) { return i; } -__attribute__((noinline)) float fast_sqrt(float n) { +__attribute__((noinline)) float fast_sqrt2(float n) { if (n < 0) return -1; float x = n; // initial guess @@ -25,6 +25,10 @@ __attribute__((noinline)) float fast_sqrt(float n) { return x; } +__attribute__((noinline)) float fast_sqrt(float n) { + return n * 3.5 + 4.5; +} + float fast_pow_float(float base, float exponent) { union { float f; @@ -36,4 +40,13 @@ float fast_pow_float(float base, float exponent) { int32_t y = (int32_t)(exponent * (x - 1072632447) + 1072632447); u.i = (uint32_t)y; return u.f; +} + +int main() { + // Test aux functions + float a = 16.0f; + float sqrt_a = fast_sqrt(a); + float pow_a = fast_pow_float(a, 0.5f); + float sqrt2_a = fast_sqrt2(a); + return 0; } \ No newline at end of file diff --git a/tests/test_vector.py b/tests/test_vector.py index 90635d2..51ba3a9 100644 --- a/tests/test_vector.py +++ b/tests/test_vector.py @@ -15,16 +15,17 @@ def test_compiled_vectors(): t2 = t1.sum() t3 = cp.vector(cp.variable(1 / (v + 1)) for v in range(3)) - #t4 = ((t3 * t1) * 2).magnitude() - t4 = ((t3 * t1) * 2).sum() - + t4 = ((t3 * t1) * 2).magnitude() + #t4 = ((t3 * t1) * 2).sum() + t5 = cp.sqrt(cp.variable(8.0)) tg = cp.Target() - tg.compile(t2, t4) + tg.compile(t2, t4, t5) tg.run() assert isinstance(t2, cp.variable) and tg.read_value(t2) == 10 + 11 + 12 + 0 + 1 + 2 #assert isinstance(t4, cp.variable) and tg.read_value(t4) == ((1/1*10 + 1/2*11 + 1/3*12) * 2)**0.5 + assert isinstance(t5, cp.variable) and tg.read_value(t5) == 8.0 * 3.5 + 4.5 if __name__ == "__main__": test_compiled_vectors() diff --git a/tools/extract_code.py b/tools/extract_code.py index 71646f7..eaca3bf 100644 --- a/tools/extract_code.py +++ b/tools/extract_code.py @@ -47,6 +47,8 @@ if __name__ == "__main__": offs = dr.read_int() reloc_type = dr.read_int() value = dr.read_int(signed=True) + assert reloc_type == RelocationType.RELOC_RELATIVE_32.value + program_data[offs:offs + 4] = value.to_bytes(4, byteorder, signed=True) print(f"PATCH_FUNC patch_offs={offs} reloc_type={reloc_type} value={value}") elif com == Command.PATCH_OBJECT: offs = dr.read_int() From ac6854ff9be7602e068427a734e38bb91668a846 Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Sun, 26 Oct 2025 16:08:33 +0100 Subject: [PATCH 10/13] test stencils and aux functions added, including test --- src/copapy/_math.py | 16 ++++++++++++++++ stencils/aux_functions.c | 10 +++++++--- stencils/generate_stencils.py | 10 ++++++---- tests/test_vector.py | 12 +++++++----- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/copapy/_math.py b/src/copapy/_math.py index 4f4d2a8..2fe33d8 100644 --- a/src/copapy/_math.py +++ b/src/copapy/_math.py @@ -16,6 +16,22 @@ def sqrt(x: NumLike) -> variable[float] | float: return float(x ** 0.5) +@overload +def sqrt2(x: float | int) -> float: ... +@overload +def sqrt2(x: variable[Any]) -> variable[float]: ... +def sqrt2(x: NumLike) -> variable[float] | float: + """Square root function""" + if isinstance(x, variable): + return add_op('sqrt2', [x, x]) # TODO: fix 2. dummy argument + return float(x ** 0.5) + + +def get_42() -> variable[float]: + """Returns the variable representing the constant 42""" + return add_op('get_42', [0.0, 0.0]) + + def abs(x: T) -> T: """Absolute value function""" ret = (x < 0) * -x + (x >= 0) * x diff --git a/stencils/aux_functions.c b/stencils/aux_functions.c index c166acf..97b28eb 100644 --- a/stencils/aux_functions.c +++ b/stencils/aux_functions.c @@ -12,7 +12,7 @@ __attribute__((noinline)) int floor_div(float arg1, float arg2) { return i; } -__attribute__((noinline)) float fast_sqrt2(float n) { +__attribute__((noinline)) float sqrt(float n) { if (n < 0) return -1; float x = n; // initial guess @@ -25,8 +25,12 @@ __attribute__((noinline)) float fast_sqrt2(float n) { return x; } -__attribute__((noinline)) float fast_sqrt(float n) { - return n * 3.5 + 4.5; +__attribute__((noinline)) float sqrt2(float n) { + return n * 20.5 + 4.5; +} + +__attribute__((noinline)) float get_42(float n) { + return n * + 42.0; } float fast_pow_float(float base, float exponent) { diff --git a/stencils/generate_stencils.py b/stencils/generate_stencils.py index 81fbbd7..d26ee88 100644 --- a/stencils/generate_stencils.py +++ b/stencils/generate_stencils.py @@ -79,10 +79,10 @@ def get_cast(type1: str, type2: str, type_out: str) -> str: @norm_indent -def get_sqrt(type1: str, type2: str) -> str: +def get_func2(func_name: str, type1: str, type2: str) -> str: return f""" - {stencil_func_prefix}void sqrt_{type1}_{type2}({type1} arg1, {type2} arg2) {{ - result_float_{type2}(fast_sqrt((float)arg1), arg2); + {stencil_func_prefix}void {func_name}_{type1}_{type2}({type1} arg1, {type2} arg2) {{ + result_float_{type2}({func_name}((float)arg1), arg2); }} """ @@ -213,7 +213,9 @@ if __name__ == "__main__": code += get_cast(t1, t2, t_out) for t1, t2 in permutate(types, types): - code += get_sqrt(t1, t2) + code += get_func2('sqrt', t1, t2) + code += get_func2('sqrt2', t1, t2) + code += get_func2('get_42', t1, t2) for op, t1, t2 in permutate(ops, types, types): t_out = t1 if t1 == t2 else 'float' diff --git a/tests/test_vector.py b/tests/test_vector.py index 51ba3a9..cbae5a2 100644 --- a/tests/test_vector.py +++ b/tests/test_vector.py @@ -15,17 +15,19 @@ def test_compiled_vectors(): t2 = t1.sum() t3 = cp.vector(cp.variable(1 / (v + 1)) for v in range(3)) - t4 = ((t3 * t1) * 2).magnitude() - #t4 = ((t3 * t1) * 2).sum() - t5 = cp.sqrt(cp.variable(8.0)) + #t4 = ((t3 * t1) * 2).magnitude() + t4 = ((t3 * t1) * 2).sum() + t5 = cp._math.sqrt2(cp.variable(8.0)) + t6 = cp._math.get_42() tg = cp.Target() - tg.compile(t2, t4, t5) + tg.compile(t2, t4, t5, t6) tg.run() assert isinstance(t2, cp.variable) and tg.read_value(t2) == 10 + 11 + 12 + 0 + 1 + 2 #assert isinstance(t4, cp.variable) and tg.read_value(t4) == ((1/1*10 + 1/2*11 + 1/3*12) * 2)**0.5 - assert isinstance(t5, cp.variable) and tg.read_value(t5) == 8.0 * 3.5 + 4.5 + assert isinstance(t5, cp.variable) and tg.read_value(t5) == 8.0 * 20.5 + 4.5 + assert tg.read_value(t6) == 42.0 if __name__ == "__main__": test_compiled_vectors() From 82c324b1a64dcfdfc6b87ce6c86530a1cbc7e54b Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Sun, 26 Oct 2025 16:09:02 +0100 Subject: [PATCH 11/13] test for aux function added --- .github/workflows/ci.yml | 3 +++ stencils/aux_functions.c | 13 +++++++------ stencils/generate_stencils.py | 2 +- tools/test_stencil_aux.sh | 19 +++++++++++++++++++ 4 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 tools/test_stencil_aux.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81fff48..1e2800a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Build & test aux functions + run: bash tools/test_stencil_aux.sh + - name: Build object files run: bash tools/crosscompile.sh diff --git a/stencils/aux_functions.c b/stencils/aux_functions.c index 97b28eb..3301687 100644 --- a/stencils/aux_functions.c +++ b/stencils/aux_functions.c @@ -12,7 +12,7 @@ __attribute__((noinline)) int floor_div(float arg1, float arg2) { return i; } -__attribute__((noinline)) float sqrt(float n) { +__attribute__((noinline)) float aux_sqrt(float n) { if (n < 0) return -1; float x = n; // initial guess @@ -25,12 +25,12 @@ __attribute__((noinline)) float sqrt(float n) { return x; } -__attribute__((noinline)) float sqrt2(float n) { +__attribute__((noinline)) float aux_sqrt2(float n) { return n * 20.5 + 4.5; } -__attribute__((noinline)) float get_42(float n) { - return n * + 42.0; +__attribute__((noinline)) float aux_get_42(float n) { + return n + 42.0; } float fast_pow_float(float base, float exponent) { @@ -49,8 +49,9 @@ float fast_pow_float(float base, float exponent) { int main() { // Test aux functions float a = 16.0f; - float sqrt_a = fast_sqrt(a); + float sqrt_a = aux_sqrt(a); float pow_a = fast_pow_float(a, 0.5f); - float sqrt2_a = fast_sqrt2(a); + float sqrt2_a = aux_sqrt2(a); + float g42 = aux_get_42(0.0f); return 0; } \ No newline at end of file diff --git a/stencils/generate_stencils.py b/stencils/generate_stencils.py index d26ee88..cf597bf 100644 --- a/stencils/generate_stencils.py +++ b/stencils/generate_stencils.py @@ -82,7 +82,7 @@ def get_cast(type1: str, type2: str, type_out: str) -> str: def get_func2(func_name: str, type1: str, type2: str) -> str: return f""" {stencil_func_prefix}void {func_name}_{type1}_{type2}({type1} arg1, {type2} arg2) {{ - result_float_{type2}({func_name}((float)arg1), arg2); + result_float_{type2}(aux_{func_name}((float)arg1), arg2); }} """ diff --git a/tools/test_stencil_aux.sh b/tools/test_stencil_aux.sh new file mode 100644 index 0000000..b4172b8 --- /dev/null +++ b/tools/test_stencil_aux.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e +set -v + +mkdir -p bin +FILE=aux_functions +SRC=stencils/$FILE.c +DEST=bin +OPT=O3 + +mkdir -p $DEST + +# Compile native x86_64 +gcc -g -$OPT $SRC -o $DEST/$FILE +chmod +x $DEST/$FILE + +# Run +$DEST/$FILE \ No newline at end of file From 6445ac972493ebc16c5371dc87be0a3c47539f55 Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Sun, 26 Oct 2025 22:26:12 +0100 Subject: [PATCH 12/13] relocation patching for constants is fixed, tests added --- src/copapy/_compiler.py | 35 ++++++++++++++++++----------------- src/copapy/_stencils.py | 10 ++++++---- src/copapy/_vectors.py | 6 +++--- tests/test_math.py | 30 +++++++++++++++++------------- tests/test_vector.py | 29 +++++++++++++++++------------ 5 files changed, 61 insertions(+), 49 deletions(-) diff --git a/src/copapy/_compiler.py b/src/copapy/_compiler.py index 3d697c4..3e46f94 100644 --- a/src/copapy/_compiler.py +++ b/src/copapy/_compiler.py @@ -192,8 +192,8 @@ def get_aux_function_mem_layout(function_names: Iterable[str], sdb: stencil_data return function_list, offset -def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database) -> tuple[binw.data_writer, dict[Net, tuple[int, int, str]]]: - variables: dict[Net, tuple[int, int, str]] = dict() +def compile_to_dag(node_list: Iterable[Node], sdb: stencil_database) -> tuple[binw.data_writer, dict[Net, tuple[int, int, str]]]: + variables: dict[Net, tuple[int, int, str]] = {} data_list: list[bytes] = [] patch_list: list[tuple[int, int, int, binw.Command]] = [] @@ -263,22 +263,23 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database assert associated_net, f"Relocation found but no net defined for operation {node.name}" #print(f"Patch for write and read addresses to/from heap variables: {node.name} {patch.target_symbol_info} {patch.target_symbol_name}") addr = object_addr_lookup[associated_net] - patch_value = addr + patch.addend - (offset + patch.addr) + patch_value = addr + patch.addend - (offset + patch.patch_address) elif patch.target_symbol_name.startswith('result_'): raise Exception(f"Stencil {node.name} seams to branch to multiple result_* calls.") else: # Patch constants addresses on heap - print('##', section_addr_lookup, node.name, patch) - addr = section_addr_lookup[patch.target_symbol_section_index] - patch_value = addr + patch.addend - (offset + patch.addr) - patch_list.append((patch.type.value, offset + patch.addr, patch_value, binw.Command.PATCH_OBJECT)) - print(patch.type, patch.addr, binw.Command.PATCH_OBJECT, node.name) + section_addr = section_addr_lookup[patch.target_symbol_section_index] + obj_addr = section_addr + patch.target_symbol_address + patch_value = obj_addr + patch.addend - (offset + patch.patch_address) + #print('* constants stancils', patch.type, patch.patch_address, binw.Command.PATCH_OBJECT, node.name) + patch_list.append((patch.type.value, offset + patch.patch_address, patch_value, binw.Command.PATCH_OBJECT)) + #print(patch.type, patch.addr, binw.Command.PATCH_OBJECT, node.name) elif patch.target_symbol_info == 'STT_FUNC': addr = aux_func_addr_lookup[patch.target_symbol_name] - patch_value = addr + patch.addend - (offset + patch.addr) - patch_list.append((patch.type.value, offset + patch.addr, patch_value, binw.Command.PATCH_FUNC)) - print(patch.type, patch.addr, binw.Command.PATCH_FUNC, node.name, '->', patch.target_symbol_name) + patch_value = addr + patch.addend - (offset + patch.patch_address) + patch_list.append((patch.type.value, offset + patch.patch_address, patch_value, binw.Command.PATCH_FUNC)) + #print(patch.type, patch.addr, binw.Command.PATCH_FUNC, node.name, '->', patch.target_symbol_name) else: raise ValueError(f"Unsupported: {node.name} {patch.target_symbol_info} {patch.target_symbol_name}") @@ -305,15 +306,15 @@ def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database if patch.target_symbol_info in {'STT_OBJECT', 'STT_NOTYPE'}: # Patch constants/variable addresses on heap section_addr = section_addr_lookup[patch.target_symbol_section_index] - patch_value = section_addr + patch.addend - (start + patch.addr) - patch_list.append((patch.type.value, start + patch.addr, patch_value, binw.Command.PATCH_OBJECT)) - print(patch.type, patch.addr, section_addr, binw.Command.PATCH_OBJECT, name) - #print(patch.type, start + patch.addr, patch_value, binw.Command.PATCH_OBJECT) + obj_addr = section_addr + patch.target_symbol_address + patch_value = obj_addr + patch.addend - (start + patch.patch_address) + patch_list.append((patch.type.value, start + patch.patch_address, patch_value, binw.Command.PATCH_OBJECT)) + #print('* constants aux', patch.type, patch.patch_address, obj_addr, binw.Command.PATCH_OBJECT, name) elif patch.target_symbol_info == 'STT_FUNC': aux_func_addr = aux_func_addr_lookup[patch.target_symbol_name] - patch_value = aux_func_addr + patch.addend - (start + patch.addr) - patch_list.append((patch.type.value, start + patch.addr, patch_value, binw.Command.PATCH_FUNC)) + patch_value = aux_func_addr + patch.addend - (start + patch.patch_address) + patch_list.append((patch.type.value, start + patch.patch_address, patch_value, binw.Command.PATCH_FUNC)) else: raise ValueError(f"Unsupported: {name} {patch.target_symbol_info} {patch.target_symbol_name}") diff --git a/src/copapy/_stencils.py b/src/copapy/_stencils.py index c4e6605..9e904d4 100644 --- a/src/copapy/_stencils.py +++ b/src/copapy/_stencils.py @@ -21,11 +21,12 @@ class patch_entry: type (RelocationType): relocation type""" type: RelocationType - addr: int + patch_address: int addend: int target_symbol_name: str target_symbol_info: str target_symbol_section_index: int + target_symbol_address: int def translate_relocation(relocation_addr: int, reloc_type: str, bits: int, r_addend: int) -> RelocationType: @@ -150,10 +151,11 @@ class stencil_database(): reloc.fields['r_addend'], reloc.symbol.name, reloc.symbol.info, - reloc.symbol.fields['st_shndx']) + reloc.symbol.fields['st_shndx'], + reloc.symbol.fields['st_value']) # Exclude the call to the result_* function - if patch.addr < end_index - start_index: + if patch.patch_address < end_index - start_index: yield patch def get_stencil_code(self, name: str) -> bytes: @@ -189,7 +191,7 @@ class stencil_database(): return self.elf.sections[id].data def get_function_code(self, name: str, part: Literal['full', 'start', 'end'] = 'full') -> bytes: - """Returns machine code for a specified function name""" + """Returns machine code for a specified function name.""" func = self.elf.symbols[name] assert func.info == 'STT_FUNC', f"{name} is not a function" diff --git a/src/copapy/_vectors.py b/src/copapy/_vectors.py index 9244d86..16d4cf1 100644 --- a/src/copapy/_vectors.py +++ b/src/copapy/_vectors.py @@ -7,7 +7,7 @@ VecIntLike: TypeAlias = 'vector[int] | variable[int] | int' VecFloatLike: TypeAlias = 'vector[float] | variable[float] | float' T = TypeVar("T", int, float) -epsilon = 1e-10 +epsilon = 1e-20 class vector(Generic[T]): @@ -153,6 +153,6 @@ class vector(Generic[T]): def normalize(self) -> 'vector[float]': mag = self.magnitude() + epsilon return self / mag - + def __iter__(self) -> Iterable[variable[T] | T]: - return iter(self.values) \ No newline at end of file + return iter(self.values) diff --git a/tests/test_math.py b/tests/test_math.py index 246b976..79aa349 100644 --- a/tests/test_math.py +++ b/tests/test_math.py @@ -1,15 +1,17 @@ from copapy import variable, Target import pytest -import copapy +import copapy as cp def test_corse(): - c_i = variable(9) - c_f = variable(2.5) + a_i = 9 + a_f = 2.5 + c_i = variable(a_i) + c_f = variable(a_f) # c_b = variable(True) - ret_test = (c_f ** c_f, c_i ** c_i)#, c_i & 3) - ret_ref = (2.5 ** 2.5, 9 ** 9)#, 9 & 3) + ret_test = (c_f ** c_f, c_i ** c_i) # , c_i & 3) + ret_refe = (a_f ** a_f, a_i ** a_i) # , a_i & 3) tg = Target() print('* compile and copy ...') @@ -18,8 +20,8 @@ def test_corse(): tg.run() print('* finished') - for test, ref in zip(ret_test, ret_ref): - assert isinstance(test, copapy.variable) + for test, ref in zip(ret_test, ret_refe): + assert isinstance(test, cp.variable) val = tg.read_value(test) print('+', val, ref, type(val), test.dtype) #for t in (int, float, bool): @@ -28,12 +30,14 @@ def test_corse(): def test_fine(): - c_i = variable(9) - c_f = variable(2.5) + a_i = 9 + a_f = 2.5 + c_i = variable(a_i) + c_f = variable(a_f) # c_b = variable(True) - ret_test = (c_f ** 2, c_i ** -1)#, c_i & 3) - ret_ref = (2.5 ** 2, 9 ** -1)#, 9 & 3) + ret_test = (c_f ** 2, c_i ** -1, cp.sqrt(c_i), cp.sqrt(c_f)) # , c_i & 3) + ret_refe = (a_f ** 2, a_i ** -1, cp.sqrt(a_i), cp.sqrt(a_f)) # , a_i & 3) tg = Target() print('* compile and copy ...') @@ -42,8 +46,8 @@ def test_fine(): tg.run() print('* finished') - for test, ref in zip(ret_test, ret_ref): - assert isinstance(test, copapy.variable) + for test, ref in zip(ret_test, ret_refe): + assert isinstance(test, cp.variable) val = tg.read_value(test) print('+', val, ref, type(val), test.dtype) #for t in (int, float, bool): diff --git a/tests/test_vector.py b/tests/test_vector.py index cbae5a2..b448417 100644 --- a/tests/test_vector.py +++ b/tests/test_vector.py @@ -1,11 +1,13 @@ import copapy as cp +import pytest + def test_vectors_init(): - tt1 = cp.vector(range(3)) + cp.vector([1.1,2.2,3.3]) - tt2 = cp.vector([1.1,2,cp.variable(5)])# + cp.vector(range(3)) + tt1 = cp.vector(range(3)) + cp.vector([1.1, 2.2, 3.3]) + tt2 = cp.vector([1.1, 2, cp.variable(5)]) + cp.vector(range(3)) tt3 = (cp.vector(range(3)) + 5.6) - tt4 = cp.vector([1.1,2,3]) + cp.vector(cp.variable(v) for v in range(3)) - tt5 = cp.vector([1,2,3]).dot(tt4) + tt4 = cp.vector([1.1, 2, 3]) + cp.vector(cp.variable(v) for v in range(3)) + tt5 = cp.vector([1, 2, 3]).dot(tt4) print(tt1, tt2, tt3, tt4, tt5) @@ -15,19 +17,22 @@ def test_compiled_vectors(): t2 = t1.sum() t3 = cp.vector(cp.variable(1 / (v + 1)) for v in range(3)) - #t4 = ((t3 * t1) * 2).magnitude() t4 = ((t3 * t1) * 2).sum() - t5 = cp._math.sqrt2(cp.variable(8.0)) - t6 = cp._math.get_42() + t5 = ((t3 * t1) * 2).magnitude() tg = cp.Target() - tg.compile(t2, t4, t5, t6) + tg.compile(t2, t4, t5) tg.run() - assert isinstance(t2, cp.variable) and tg.read_value(t2) == 10 + 11 + 12 + 0 + 1 + 2 - #assert isinstance(t4, cp.variable) and tg.read_value(t4) == ((1/1*10 + 1/2*11 + 1/3*12) * 2)**0.5 - assert isinstance(t5, cp.variable) and tg.read_value(t5) == 8.0 * 20.5 + 4.5 - assert tg.read_value(t6) == 42.0 + assert isinstance(t2, cp.variable) + assert tg.read_value(t2) == 10 + 11 + 12 + 0 + 1 + 2 + + assert isinstance(t4, cp.variable) + assert tg.read_value(t4) == pytest.approx(((10/1*2) + (12/2*2) + (14/3*2)), 0.001) # pyright: ignore[reportUnknownMemberType] + + assert isinstance(t5, cp.variable) + assert tg.read_value(t5) == pytest.approx(((10/1*2)**2 + (12/2*2)**2 + (14/3*2)**2) ** 0.5, 0.001) # pyright: ignore[reportUnknownMemberType] + if __name__ == "__main__": test_compiled_vectors() From a971b98f2db12f4c4d2962cf4c5741231fe8659d Mon Sep 17 00:00:00 2001 From: Nicolas Kruse Date: Sun, 26 Oct 2025 22:30:38 +0100 Subject: [PATCH 13/13] ruff config added for replacing flake8, code style und naming changes --- .flake8 | 24 ----------------------- pyproject.toml | 23 ++++++++++++++++++++-- src/copapy/_basic_types.py | 4 ++-- src/copapy/_binwrite.py | 2 +- src/copapy/_math.py | 1 - src/copapy/_target.py | 6 +++--- src/copapy/backend.py | 4 ++-- tests/test_compile.py | 4 ++-- tests/test_compile_div.py | 4 ++-- tests/test_coparun_module2.py | 4 ++-- tests/test_crash_win.py | 4 ++-- tools/make_example.py | 36 ++++++++++++++++++++++------------- 12 files changed, 60 insertions(+), 56 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index b8aba99..0000000 --- a/.flake8 +++ /dev/null @@ -1,24 +0,0 @@ -[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, - .venv - -# 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, E712 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index c992751..d9b30fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ copapy = ["obj/*.o", "py.typed"] [project.optional-dependencies] dev = [ - "flake8", + "ruff", "mypy", "pytest" ] @@ -50,4 +50,23 @@ show_error_codes = true minversion = "6.0" addopts = "-ra -q" testpaths = ["tests"] -pythonpath = ["src"] \ No newline at end of file +pythonpath = ["src"] + +[tool.ruff] +lint.ignore = ["E501", "E226", "E265"] + +# Equivalent to Flake8's "exclude" +exclude = [ + ".git", + "__pycache__", + "build", + "dist", + ".conda", + ".venv", +] + +# "D" for dockstrings +lint.select = ["C", "E", "F", "W"] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["D", "E712"] diff --git a/src/copapy/_basic_types.py b/src/copapy/_basic_types.py index b1e2dad..c9132cc 100644 --- a/src/copapy/_basic_types.py +++ b/src/copapy/_basic_types.py @@ -161,11 +161,11 @@ class variable(Generic[TNum], Net): def __lt__(self, other: NumLike) -> 'variable[bool]': ret = add_op('gt', [other, self]) return variable(ret.source, dtype='bool') - + def __ge__(self, other: NumLike) -> 'variable[bool]': ret = add_op('ge', [self, other]) return variable(ret.source, dtype='bool') - + def __le__(self, other: NumLike) -> 'variable[bool]': ret = add_op('ge', [other, self]) return variable(ret.source, dtype='bool') diff --git a/src/copapy/_binwrite.py b/src/copapy/_binwrite.py index 627b3ae..294d0d3 100644 --- a/src/copapy/_binwrite.py +++ b/src/copapy/_binwrite.py @@ -14,7 +14,7 @@ COMMAND_SIZE = 4 class data_writer(): def __init__(self, byteorder: ByteOrder): - self._data: list[tuple[str, bytes, int]] = list() + self._data: list[tuple[str, bytes, int]] = [] self.byteorder: ByteOrder = byteorder def write_int(self, value: int, num_bytes: int = 4, signed: bool = False) -> None: diff --git a/src/copapy/_math.py b/src/copapy/_math.py index 2fe33d8..1adec2a 100644 --- a/src/copapy/_math.py +++ b/src/copapy/_math.py @@ -37,4 +37,3 @@ def abs(x: T) -> T: ret = (x < 0) * -x + (x >= 0) * x return ret # pyright: ignore[reportReturnType] - diff --git a/src/copapy/_target.py b/src/copapy/_target.py index 3b9c63d..2668641 100644 --- a/src/copapy/_target.py +++ b/src/copapy/_target.py @@ -4,7 +4,7 @@ from coparun_module import coparun, read_data_mem import struct from ._basic_types import stencil_db_from_package from ._basic_types import variable, Net, Node, Write, NumLike -from ._compiler import compile_to_instruction_list +from ._compiler import compile_to_dag def add_read_command(dw: binw.data_writer, variables: dict[Net, tuple[int, int, str]], net: Net) -> None: @@ -18,7 +18,7 @@ def add_read_command(dw: binw.data_writer, variables: dict[Net, tuple[int, int, class Target(): def __init__(self, arch: str = 'native', optimization: str = 'O3') -> None: self.sdb = stencil_db_from_package(arch, optimization) - self._variables: dict[Net, tuple[int, int, str]] = dict() + self._variables: dict[Net, tuple[int, int, str]] = {} def compile(self, *variables: int | float | variable[int] | variable[float] | variable[bool] | Iterable[int | float | variable[int] | variable[float] | variable[bool]]) -> None: nodes: list[Node] = [] @@ -30,7 +30,7 @@ class Target(): else: nodes.append(Write(s)) - dw, self._variables = compile_to_instruction_list(nodes, self.sdb) + dw, self._variables = compile_to_dag(nodes, self.sdb) dw.write_com(binw.Command.END_COM) assert coparun(dw.get_data()) > 0 diff --git a/src/copapy/backend.py b/src/copapy/backend.py index 6d4d9e9..6b02056 100644 --- a/src/copapy/backend.py +++ b/src/copapy/backend.py @@ -1,6 +1,6 @@ from ._target import add_read_command from ._basic_types import Net, Op, Node, CPConstant, Write -from ._compiler import compile_to_instruction_list, \ +from ._compiler import compile_to_dag, \ stable_toposort, get_const_nets, get_all_dag_edges, add_read_ops, \ add_write_ops @@ -11,7 +11,7 @@ __all__ = [ "Node", "CPConstant", "Write", - "compile_to_instruction_list", + "compile_to_dag", "stable_toposort", "get_const_nets", "get_all_dag_edges", diff --git a/tests/test_compile.py b/tests/test_compile.py index 8e3a018..8adeb84 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -1,5 +1,5 @@ from copapy import variable, NumLike -from copapy.backend import Write, compile_to_instruction_list, add_read_command +from copapy.backend import Write, compile_to_dag, add_read_command import copapy import subprocess import struct @@ -49,7 +49,7 @@ def test_compile(): out = [Write(r) for r in ret] - il, variables = compile_to_instruction_list(out, copapy.generic_sdb) + il, variables = compile_to_dag(out, copapy.generic_sdb) # run program command il.write_com(_binwrite.Command.RUN_PROG) diff --git a/tests/test_compile_div.py b/tests/test_compile_div.py index 06eaa5f..e199917 100644 --- a/tests/test_compile_div.py +++ b/tests/test_compile_div.py @@ -1,5 +1,5 @@ from copapy import variable, NumLike -from copapy.backend import Write, compile_to_instruction_list +from copapy.backend import Write, compile_to_dag import copapy import subprocess from copapy import _binwrite @@ -26,7 +26,7 @@ def test_compile(): out = [Write(r) for r in ret] - il, _ = compile_to_instruction_list(out, copapy.generic_sdb) + il, _ = compile_to_dag(out, copapy.generic_sdb) # run program command il.write_com(_binwrite.Command.RUN_PROG) diff --git a/tests/test_coparun_module2.py b/tests/test_coparun_module2.py index 77594ad..aa4ef3a 100644 --- a/tests/test_coparun_module2.py +++ b/tests/test_coparun_module2.py @@ -1,6 +1,6 @@ from coparun_module import coparun from copapy import variable -from copapy.backend import Write, compile_to_instruction_list, add_read_command +from copapy.backend import Write, compile_to_dag, add_read_command import copapy from copapy import _binwrite @@ -15,7 +15,7 @@ def test_compile(): r2 = i1 + 9 out = [Write(r1), Write(r2), Write(c2)] - il, variables = compile_to_instruction_list(out, copapy.generic_sdb) + il, variables = compile_to_dag(out, copapy.generic_sdb) # run program command il.write_com(_binwrite.Command.RUN_PROG) diff --git a/tests/test_crash_win.py b/tests/test_crash_win.py index 144549c..aab0a68 100644 --- a/tests/test_crash_win.py +++ b/tests/test_crash_win.py @@ -1,5 +1,5 @@ from copapy import NumLike, variable -from copapy.backend import Write, Net, compile_to_instruction_list, add_read_command +from copapy.backend import Write, Net, compile_to_dag, add_read_command import copapy import subprocess from copapy import _binwrite @@ -29,7 +29,7 @@ def test_compile(): ret = function(c1, c2) - dw, variable_list = compile_to_instruction_list([Write(net) for net in ret], copapy.generic_sdb) + dw, variable_list = compile_to_dag([Write(net) for net in ret], copapy.generic_sdb) # run program command dw.write_com(_binwrite.Command.RUN_PROG) diff --git a/tools/make_example.py b/tools/make_example.py index 37f3b05..6b47378 100644 --- a/tools/make_example.py +++ b/tools/make_example.py @@ -1,33 +1,43 @@ -from copapy import _binwrite, variable -from copapy.backend import Write, compile_to_instruction_list +from copapy import variable +from copapy.backend import Write, compile_to_dag import copapy as cp +from copapy._binwrite import Command def test_compile() -> None: - + """Test compilation of a simple program.""" c1 = variable(9.0) #ret = [c1 / 4, c1 / -4, c1 // 4, c1 // -4, (c1 * -1) // 4] - #ret = [c1 // 3.3 + 5] - ret = [cp.sqrt(c1)] + ret = [c1 // 3.3 + 5] + #ret = [cp.sqrt(c1)] + #c2 = cp._math.get_42() + #ret = [c2] out = [Write(r) for r in ret] - il, _ = compile_to_instruction_list(out, cp.generic_sdb) + dw, vars = compile_to_dag(out, cp.generic_sdb) # run program command - il.write_com(_binwrite.Command.RUN_PROG) + dw.write_com(Command.RUN_PROG) - il.write_com(_binwrite.Command.READ_DATA) - il.write_int(0) - il.write_int(36) + # read first 32 byte + dw.write_com(Command.READ_DATA) + dw.write_int(0) + dw.write_int(32) - il.write_com(_binwrite.Command.END_COM) + # read variables + for addr, lengths, _ in vars.values(): + dw.write_com(Command.READ_DATA) + dw.write_int(addr) + dw.write_int(lengths) + + dw.write_com(Command.END_COM) print('* Data to runner:') - il.print() + dw.print() - il.to_file('bin/test.copapy') + dw.to_file('bin/test.copapy') if __name__ == "__main__":