mirror of https://github.com/Nonannet/copapy.git
Compare commits
No commits in common. "4bb1bb56b610ac609cbcc241139807281ed7b66a" and "4fa3445a72098056cdc4d37e08d45f0ee8a01761" have entirely different histories.
4bb1bb56b6
...
4fa3445a72
16
README.md
16
README.md
|
|
@ -1,10 +1,6 @@
|
||||||
<div align="center">
|
|
||||||
<img src="docs/source/media/logo.svg" alt="Copapy logo" width="160" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
# Copapy
|
# Copapy
|
||||||
|
|
||||||
Copapy is a Python framework for deterministic, low-latency real-time computation with automatic differentiation support, targeting hardware applications - for example in the fields of robotics, aerospace, SDR, embedded systems and control systems in general.
|
Copapy is a Python framework for deterministic, low-latency realtime computation with automatic differentiation support, targeting hardware applications - for example in the fields of robotics, aerospace, SDR, embedded systems and control systems in general.
|
||||||
|
|
||||||
GPU frameworks like PyTorch, JAX and TensorFlow jump-started the development in the field of AI. With the right balance of flexibility and performance, they allow for fast iteration of new ideas while still being performant enough to test or even use them in production.
|
GPU frameworks like PyTorch, JAX and TensorFlow jump-started the development in the field of AI. With the right balance of flexibility and performance, they allow for fast iteration of new ideas while still being performant enough to test or even use them in production.
|
||||||
|
|
||||||
|
|
@ -27,7 +23,7 @@ Execution of the compiled code is managed by a runner application. The runner is
|
||||||
|
|
||||||
The design targets either an architecture with a realtime-patched Linux kernel - where the runner uses the same CPU and memory as Linux but executes in a realtime thread - or a setup where even higher determinism is required. In such cases, the runner can be executed on a separate crossover MCU running on bare metal or a RTOS.
|
The design targets either an architecture with a realtime-patched Linux kernel - where the runner uses the same CPU and memory as Linux but executes in a realtime thread - or a setup where even higher determinism is required. In such cases, the runner can be executed on a separate crossover MCU running on bare metal or a RTOS.
|
||||||
|
|
||||||
The Copapy framework also includes a runner as a Python module built from the same C code. This allows frictionless testing of code and might be valuable for using Copapy in conventional application development.
|
The Copapy framework also includes a runner as Python module build from the same C code. This allows frictionless testing of code and might be valuable for using Copapy in conventional application development.
|
||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
|
|
||||||
|
|
@ -41,11 +37,11 @@ Despite missing SIMD-optimization, benchmark performance shows promising numbers
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
For the benchmark (`tests/benchmark.py`) timings for 30,000 iterations of calculating the term `sum((v1 + i) @ v2 for i in range(10))` were measured on a Ryzen 5 3400G. The vectors `v1` and `v2` both have lengths of `v_size`, which was varied from 10 to 500 according to the chart. For the NumPy case the `i in range(10)` loop was vectorized like this: `np.sum((v1 + i) @ v2)` with `i` being an `NDArray` of shape `[10, 1]`. The number of calculated scalar operations is the same for both implementations. Copapy benefits from lower overhead by calling a single function from Python per iteration, whereas the NumPy variant requires three. Interestingly, the chart shows no indication that for increasing `v_size` the calling overhead for NumPy will be compensated by faster SIMD instructions. Note that in this benchmark the Copapy case does not move any data between Python and the compiled code.
|
For the benchmark (`tests/benchmark.py`) the timing of 30000 iterations for calculating the therm `sum((v1 + i) @ v2 for i in range(10))` where measured on an Ryzen 5 3400G. Where the vectors `v1` and `v2` both have a lengths of `v_size` which was varied according to the chart from 10 to 500. For the NumPy case the "i in range(10)" loop was vectorized like this: `np.sum((v1 + i) @ v2)` with i being here a `NDArray` with a dimension of `[10, 1]`. The number of calculated scalar operations is the same for both contenders. Obviously Copapy profits from less overheat by calling a single function from python per iteration, where the NumPy variant requires 3. Interestingly there is no indication visible in the chart that for increasing `v_size` the calling overhead for NumPy will be compensated by using faster SIMD instructions. It is to note that in this benchmark the Copapy case does not move any data between python and the compiled code.
|
||||||
|
|
||||||
Furthermore, for many applications Copapy performance will benefit by reducing the actual number of operations significantly compared to a NumPy implementation, by precomputing constant values known at compile time and benefiting from sparsity. Multiplying by zero (e.g., in a diagonal matrix) eliminates a whole branch in the computation graph. Operations with no effect, like multiplications by 1 or additions with zero, get eliminated at compile time.
|
Furthermore for many applications Copapy performance will benefit by reducing the actual number of operations significantly compared to a NumPy implementation, by precompute constant values know at compile time and benefiting from sparcity. Multiplying by zero (e.g. in a diagonal matrix) eliminate a hole branch in the computation graph. Operations without effect, like multiplications by 1 oder additions with zero gets eliminated at compile time.
|
||||||
|
|
||||||
For testing and using Copapy to speed up computations in conventional Python programs there is also the `@cp.jit` decorator available, to compile functions on first use and cache the compiled version for later calls:
|
For Testing and using Copapy to speed up computations in conventional Python programs there is also the `@cp.jit` decorator available, to compile functions on first use and cache the compiled version for later calls:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import copapy as cp
|
import copapy as cp
|
||||||
|
|
@ -61,7 +57,7 @@ result1 = calculation(2.5, 1.2)
|
||||||
result2 = calculation(3.1, 4.7)
|
result2 = calculation(3.1, 4.7)
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that `cp.jit` is not currently highly optimized for data transfer between Python and the compiled code.
|
It is to note that `cp.jit` is not optimized very much at the moment concerning transfer data between Python and the compiled code back and forth.
|
||||||
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ example_asm
|
||||||
```{include} ../build/compiler.md
|
```{include} ../build/compiler.md
|
||||||
```
|
```
|
||||||
|
|
||||||
A full listing of all stencils with machine code for all architectures from latest build is here available: [Stencil overview](stencil_doc.md). The compiler output for a full example program from latest compiler build is here available: [Example program](example_asm).
|
A full listing of all stencils with machine code for all architectures from latest build is here available: [Stencil overview](stencil_doc.md). The compiler output for a full example program from latest compiler build is here available: [Example program](example_asm).
|
||||||
|
|
@ -25,17 +25,13 @@ exclude_patterns = []
|
||||||
# -- Options for HTML output -------------------------------------------------
|
# -- Options for HTML output -------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||||
|
|
||||||
|
# html_theme = 'alabaster'
|
||||||
html_theme = 'pydata_sphinx_theme'
|
html_theme = 'pydata_sphinx_theme'
|
||||||
html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
html_title = "Copapy, a Python framework for deterministic, realtime computation"
|
|
||||||
html_short_title = "Copapy"
|
|
||||||
html_logo = 'media/logo.svg'
|
|
||||||
html_favicon = 'media/logo.svg'
|
|
||||||
html_css_files = ['custom.css']
|
html_css_files = ['custom.css']
|
||||||
html_theme_options = {
|
html_theme_options = {
|
||||||
"secondary_sidebar_items": ["page-toc"],
|
"secondary_sidebar_items": ["page-toc"],
|
||||||
"footer_start": ["copyright"],
|
"footer_start": ["copyright"]
|
||||||
"logo": {"text": "Copapy"}
|
|
||||||
}
|
}
|
||||||
html_theme_options["footer_end"] = []
|
html_theme_options["footer_end"] = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
# Examples
|
|
||||||
|
|
||||||
```{include} ../build/examples.md
|
|
||||||
```
|
|
||||||
|
|
@ -2,32 +2,11 @@ import re
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
def extract_sections(md_text: str) -> dict[str, str]:
|
||||||
def strip_leading_html_tag(text: str) -> str:
|
|
||||||
"""Remove a leading HTML element if the first non-space character is '<'."""
|
|
||||||
trimmed = text.lstrip()
|
|
||||||
#if not trimmed.startswith('<'):
|
|
||||||
# return text
|
|
||||||
|
|
||||||
match = re.match(
|
|
||||||
r'^\s*<\s*(?P<tag>[A-Za-z][A-Za-z0-9:-]*)(?:\s+[^<>]*)?>'
|
|
||||||
r'(?P<body>.*?)(?:</\s*(?P=tag)\s*>)?',
|
|
||||||
trimmed,
|
|
||||||
re.DOTALL,
|
|
||||||
)
|
|
||||||
if not match:
|
|
||||||
return text
|
|
||||||
|
|
||||||
return trimmed[match.end():]
|
|
||||||
|
|
||||||
|
|
||||||
def extract_sections(md_text: str) -> dict[str, tuple[str, str]]:
|
|
||||||
"""
|
"""
|
||||||
Extracts sections based on headings (#...).
|
Extracts sections based on headings (#...).
|
||||||
Returns {heading_text: section_content}
|
Returns {heading_text: section_content}
|
||||||
Works for simple Markdown, not fully strict.
|
Works for simple Markdown, not fully strict.
|
||||||
|
|
||||||
If strip_first_heading is True, omit the first heading/section from the output.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# regex captures: heading marks (###...), heading text, and the following content
|
# regex captures: heading marks (###...), heading text, and the following content
|
||||||
|
|
@ -38,17 +17,13 @@ def extract_sections(md_text: str) -> dict[str, tuple[str, str]]:
|
||||||
re.MULTILINE | re.DOTALL
|
re.MULTILINE | re.DOTALL
|
||||||
)
|
)
|
||||||
|
|
||||||
sections: dict[str, tuple[str, str]] = {}
|
sections: dict[str, str] = {}
|
||||||
matched = list(pattern.findall(md_text))
|
for _, title, content in pattern.findall(md_text):
|
||||||
|
|
||||||
for prefix, title, content in matched:
|
|
||||||
assert isinstance(content, str)
|
assert isinstance(content, str)
|
||||||
content = strip_leading_html_tag(content)
|
sections[title] = content.strip().replace('](docs/source/media/', '](media/')
|
||||||
sections[title] = (prefix + ' ' + title, content.strip().replace('](docs/source/media/', '](media/'))
|
|
||||||
|
|
||||||
return sections
|
return sections
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser(description='Extract sections from README.md and generate documentation files')
|
parser = argparse.ArgumentParser(description='Extract sections from README.md and generate documentation files')
|
||||||
parser.add_argument('--readme', type=str, default='README.md', help='README.md path')
|
parser.add_argument('--readme', type=str, default='README.md', help='README.md path')
|
||||||
|
|
@ -62,12 +37,9 @@ if __name__ == '__main__':
|
||||||
readme = extract_sections(f.read())
|
readme = extract_sections(f.read())
|
||||||
|
|
||||||
with open(os.path.join(build_dir, 'start.md'), 'wt') as f:
|
with open(os.path.join(build_dir, 'start.md'), 'wt') as f:
|
||||||
f.write('# Introduction\n' + '\n'.join('\n'.join(readme[s]) if s != 'Copapy' else readme[s][1] for s in [
|
f.write('\n'.join(f"{s}\n" + readme[s.strip(' #')] for s in [
|
||||||
'Copapy', 'Current state', 'Install', 'License']))
|
'# Copapy', '## Current state', '## Install', '## Examples',
|
||||||
|
'### Basic example', '### Inverse kinematics', '## License']))
|
||||||
with open(os.path.join(build_dir, 'examples.md'), 'wt') as f:
|
|
||||||
f.write('\n'.join(readme[s][1] for s in ['Examples',
|
|
||||||
'Basic example', 'Inverse kinematics']))
|
|
||||||
|
|
||||||
with open(os.path.join(build_dir, 'compiler.md'), 'wt') as f:
|
with open(os.path.join(build_dir, 'compiler.md'), 'wt') as f:
|
||||||
f.write('\n'.join(readme[s][1] for s in ['How it works']))
|
f.write('\n'.join(readme[s] for s in ['How it works']))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
```{toctree}
|
```{toctree}
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
:hidden:
|
:hidden:
|
||||||
examples
|
|
||||||
compiler
|
compiler
|
||||||
api/index
|
api/index
|
||||||
api/backend
|
api/backend
|
||||||
|
|
@ -9,4 +8,4 @@ repo
|
||||||
```
|
```
|
||||||
|
|
||||||
```{include} ../build/start.md
|
```{include} ../build/start.md
|
||||||
```
|
```
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="50mm"
|
|
||||||
height="50mm"
|
|
||||||
viewBox="0 0 50 50"
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<defs
|
|
||||||
id="defs1">
|
|
||||||
<radialGradient
|
|
||||||
xlink:href="#linearGradient4"
|
|
||||||
id="radialGradient5"
|
|
||||||
cx="107.11821"
|
|
||||||
cy="235.82867"
|
|
||||||
fx="107.11821"
|
|
||||||
fy="235.82867"
|
|
||||||
r="24.039047"
|
|
||||||
gradientTransform="matrix(1,0,0,0.95918092,-67.935504,-192.92731)"
|
|
||||||
gradientUnits="userSpaceOnUse" />
|
|
||||||
<linearGradient
|
|
||||||
id="linearGradient4">
|
|
||||||
<stop
|
|
||||||
style="stop-color:#2ec8b5;stop-opacity:1;"
|
|
||||||
offset="0"
|
|
||||||
id="stop4" />
|
|
||||||
<stop
|
|
||||||
style="stop-color:#13bc30;stop-opacity:1;"
|
|
||||||
offset="0.81555557"
|
|
||||||
id="stop5" />
|
|
||||||
<stop
|
|
||||||
style="stop-color:#049f40;stop-opacity:1;"
|
|
||||||
offset="1"
|
|
||||||
id="stop8" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<g
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-13.750923,-8.9786913)">
|
|
||||||
<path
|
|
||||||
id="text1-8-8-2-3-4"
|
|
||||||
style="font-style:italic;font-size:70.5556px;line-height:1.25;font-family:Cambria;-inkscape-font-specification:'Cambria Italic';text-align:center;text-anchor:middle;fill:url(#radialGradient5);fill-opacity:1;stroke:none;stroke-width:0.264583"
|
|
||||||
d="m 43.128156,27.242126 c 0,0 -9.0193,17.94078 -9.54773,19.30631 -0.52844,1.36553 -1.06402,2.95147 -1.06402,4.16874 0,3.74367 2.06705,5.61568 6.20117,5.61568 3.05465,0 6.0752,-1.21743 9.06095,-3.65197 l 1.43505,-2.86339 c -1.98313,1.16297 -6.22373,2.86339 -7.94628,2.86339 -1.97519,0 -3.12683,-0.80027 -3.12683,-2.82139 0,-0.57419 0.0602,-1.52908 0.57711,-2.96637 0.51695,-1.43729 10.02626,-19.651 10.02626,-19.651 h 12.16102 l 2.3169,-4.51837 c -2.61867,0.19291 -15.87083,0.19404 -20.67909,0.21217 -1.31311,0.005 -4.19846,0.13112 -5.51169,0.27927 -6.05803,0.68345 -10.32667,2.58452 -14.9987,7.97142 -5.87218,6.77069 -10.61497,23.53356 -2.76675,24.90545 2.41712,0.42252 5.25201,-0.58293 9.18033,-6.78407 2.44714,-3.86301 7.28627,-13.37301 10.98693,-20.8859 l -3.01186,0.009 c -3.88681,7.43627 -8.83327,16.45417 -10.16461,18.73719 -0.48019,0.82345 -2.18721,4.0008 -4.3248,4.75991 -4.80166,1.70519 -5.76602,-8.11809 -0.75697,-16.21679 4.16175,-6.72876 11.59127,-8.35206 16.48548,-8.4633 0.37281,-0.001 5.46813,-0.006 5.46813,-0.006 z m -0.59092,-5.45067 c 1.08293,-2.22106 1.29372,-2.48023 1.29372,-2.48023 l 3.24988,0.0286 -1.23803,2.43839 5.69979,-0.006 1.21805,-2.44463 4.48655,0.008 -5.56556,-9.11832 -14.61667,8.79773 3.85328,0.18155 c 0,0 -0.0989,0.18068 -1.35589,2.59494" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
|
@ -42,15 +42,8 @@ from ._math import sqrt, abs, sign, sin, cos, tan, asin, acos, atan, atan2, log,
|
||||||
from ._nn import relu, sigmoid
|
from ._nn import relu, sigmoid
|
||||||
from ._autograd import grad
|
from ._autograd import grad
|
||||||
from ._tensors import tensor as matrix
|
from ._tensors import tensor as matrix
|
||||||
from typing import TYPE_CHECKING
|
from ._version import __version__ # Run "pip install -e ." to generate _version.py
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
__version__: str
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
from ._version import __version__
|
|
||||||
except ImportError:
|
|
||||||
__version__ = "0.0.0" # Run "pip install -e ." to generate _version.py
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"__version__",
|
"__version__",
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import copapy as cp
|
||||||
from ._basic_types import NumLike, value, unifloat, ArrayType
|
from ._basic_types import NumLike, value, unifloat, ArrayType
|
||||||
from ._mixed import mixed_sum
|
from ._mixed import mixed_sum
|
||||||
|
|
||||||
epsilon = 1e-20
|
|
||||||
|
|
||||||
|
|
||||||
class quaternion(ArrayType[float]):
|
class quaternion(ArrayType[float]):
|
||||||
"""Mathematical quaternion class for representing 3D rotations.
|
"""Mathematical quaternion class for representing 3D rotations.
|
||||||
|
|
@ -93,9 +91,11 @@ class quaternion(ArrayType[float]):
|
||||||
"""Normalize the quaternion to unit length.
|
"""Normalize the quaternion to unit length.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A normalized (unit) quaternion.
|
A normalized (unit) quaternion. Returns identity if the norm is zero.
|
||||||
"""
|
"""
|
||||||
n = self.norm() + epsilon
|
n = self.norm()
|
||||||
|
if not isinstance(n, value) and n == 0:
|
||||||
|
return quaternion.identity()
|
||||||
return quaternion(v / n for v in self.values)
|
return quaternion(v / n for v in self.values)
|
||||||
|
|
||||||
def toRotationMatrix(self) -> tensor[float]:
|
def toRotationMatrix(self) -> tensor[float]:
|
||||||
|
|
|
||||||
|
|
@ -129,10 +129,7 @@ def test_compile():
|
||||||
if not check_for_qemu():
|
if not check_for_qemu():
|
||||||
warnings.warn("qemu-armv7 not found, armv7 test skipped!", UserWarning)
|
warnings.warn("qemu-armv7 not found, armv7 test skipped!", UserWarning)
|
||||||
return
|
return
|
||||||
if "wsl" in qemu_command:
|
if not os.path.isfile('build/runner/coparun-armv7'):
|
||||||
warnings.warn("qemu-armv7 seams not work on wsl1, test skipped!", UserWarning)
|
|
||||||
return
|
|
||||||
if not os.path.isfile("build/runner/coparun-armv7"):
|
|
||||||
warnings.warn("armv7 runner not found, armv7 test skipped!", UserWarning)
|
warnings.warn("armv7 runner not found, armv7 test skipped!", UserWarning)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,10 +129,7 @@ def test_compile():
|
||||||
if not check_for_qemu():
|
if not check_for_qemu():
|
||||||
warnings.warn("qemu-armv7 not found, armv7 test skipped!", UserWarning)
|
warnings.warn("qemu-armv7 not found, armv7 test skipped!", UserWarning)
|
||||||
return
|
return
|
||||||
if "wsl" in qemu_command:
|
if not os.path.isfile('build/runner/coparun-armv7'):
|
||||||
warnings.warn("qemu-armv7 seams not work on wsl1, test skipped!", UserWarning)
|
|
||||||
return
|
|
||||||
if not os.path.isfile("build/runner/coparun-armv7"):
|
|
||||||
warnings.warn("armv7 runner not found, armv7 test skipped!", UserWarning)
|
warnings.warn("armv7 runner not found, armv7 test skipped!", UserWarning)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue