commit 71ccbac47c751534d41c8d1b0eb0274763ff7391 Author: altescy Date: Mon Jan 15 01:11:12 2024 +0900 initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..83fdbb7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{json,jsonnet,libsonnet,yml,yaml}] +indent_size = 2 + +[*.tsv] +indent_style = tab + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0ee87a --- /dev/null +++ b/.gitignore @@ -0,0 +1,153 @@ +## +## Python +## + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +## +## Rust +## + +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..309c17f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "xsor" +version = "0.1.0" +edition = "2021" + +[lib] +name = "xsor" +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1.0.79" +num = "0.4.1" +pyo3 = { version = "0.20.2", features = ["extension-module"] } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c50060b --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +PWD := $(shell pwd) +POETRY := poetry +PYTHON := $(POETRY) run python +PYTEST := $(POETRY) run pytest +PYSEN := $(POETRY) run pysen +MODULE := xsor + +.PHONY: all +all: format lint test + +.PHONY: build +build: + $(POETRY) run maturin build --release + +.PHONY: test +test: + PYTHONPATH=$(PWD) $(PYTEST) + +.PHONY: lint +lint: + PYTHONPATH=$(PWD) $(PYSEN) run lint + +.PHONY: format +format: + PYTHONPATH=$(PWD) $(PYSEN) run format + +.PHONY: clean +clean: clean-pyc clean-build + +.PHONY: clean-pyc +clean-pyc: + rm -rf .pytest_cache + rm -rf .mypy_cache + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +.PHONY: clean-build +clean-build: + rm -rf build/ + rm -rf dist/ + rm -rf *.egg-info/ + rm -rf pip-wheel-metadata/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e61b974 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# xsor diff --git a/benchmark.py b/benchmark.py new file mode 100644 index 0000000..8686a93 --- /dev/null +++ b/benchmark.py @@ -0,0 +1,89 @@ +import time + +import numpy + +import xsor + + +def numpy_add() -> None: + a = numpy.array(range(100000), dtype=numpy.float32) + b = numpy.array(range(100000), dtype=numpy.float32) + a + b + + +def numpy_mul() -> None: + a = numpy.array(range(100000), dtype=numpy.float32) + b = numpy.array(range(100000), dtype=numpy.float32) + a * b + + +def numpy_sum() -> None: + a = numpy.array(range(100000), dtype=numpy.float32) + a.sum() + + +def numpy_matmul() -> None: + a = numpy.array(range(100000), dtype=numpy.float32).reshape((100, 1000)) + b = numpy.array(range(100000), dtype=numpy.float32).reshape((1000, 100)) + a @ b + + +def numpy_add_scalar() -> None: + a = numpy.array(range(100000), dtype=numpy.float32) + a + 1.0 + + +def xsor_add() -> None: + a = xsor.tensor(range(100000)) + b = xsor.tensor(range(100000)) + a + b + + +def xsor_mul() -> None: + a = xsor.tensor(range(100000)) + b = xsor.tensor(range(100000)) + a * b + + +def xsor_sum() -> None: + a = xsor.tensor(range(100000)) + a.sum() + + +def xsor_matmul() -> None: + a = xsor.tensor(range(100000), (100, 1000)) + b = xsor.tensor(range(100000), (1000, 100)) + a @ b + + +def xsor_add_scalar() -> None: + a = xsor.tensor(range(100000)) + a + 1.0 + + +N = 1000 + +for title, numpy_fn, xsor_fn in ( + ("ADD", numpy_add, xsor_add), + ("MUL", numpy_mul, xsor_mul), + ("SUM", numpy_sum, xsor_sum), + ("MATMUL", numpy_matmul, xsor_matmul), + ("ADD_SCALAR", numpy_add_scalar, xsor_add_scalar), +): + print(f"[ {title} ]") + + print(" numpy ... ", end="") + start = time.time() + for _ in range(N): + numpy_fn() + end = time.time() + print(f"{end - start:.3f}s") + + print(" xsor ... ", end="") + start = time.time() + for _ in range(N): + xsor_fn() + end = time.time() + print(f"{end - start:.3f}s") + + print() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..1255eb7 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,658 @@ +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. + +[[package]] +name = "black" +version = "23.12.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "colorlog" +version = "4.8.0" +description = "Log formatting with colors!" +optional = false +python-versions = "*" +files = [ + {file = "colorlog-4.8.0-py2.py3-none-any.whl", hash = "sha256:3dd15cb27e8119a24c1a7b5c93f9f3b455855e0f73993b1c25921b2f646f1dcd"}, + {file = "colorlog-4.8.0.tar.gz", hash = "sha256:59b53160c60902c405cdec28d38356e09d40686659048893e026ecbd589516b1"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[[package]] +name = "dacite" +version = "1.8.1" +description = "Simple creation of data classes from dictionaries." +optional = false +python-versions = ">=3.6" +files = [ + {file = "dacite-1.8.1-py3-none-any.whl", hash = "sha256:cc31ad6fdea1f49962ea42db9421772afe01ac5442380d9a99fcf3d188c61afe"}, +] + +[package.extras] +dev = ["black", "coveralls", "mypy", "pre-commit", "pylint", "pytest (>=5)", "pytest-benchmark", "pytest-cov"] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + +[[package]] +name = "gitdb" +version = "4.0.11" +description = "Git Object Database" +optional = false +python-versions = ">=3.7" +files = [ + {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, + {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, +] + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.41" +description = "GitPython is a Python library used to interact with Git repositories" +optional = false +python-versions = ">=3.7" +files = [ + {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, + {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, +] + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[package.extras] +test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jedi" +version = "0.17.2" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"}, + {file = "jedi-0.17.2.tar.gz", hash = "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20"}, +] + +[package.dependencies] +parso = ">=0.7.0,<0.8.0" + +[package.extras] +qa = ["flake8 (==3.7.9)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] + +[[package]] +name = "maturin" +version = "1.4.0" +description = "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "maturin-1.4.0-py3-none-linux_armv6l.whl", hash = "sha256:b84bee85620e1b7b662a7af71289f7f6c23df8269e42c0f76882676dfc9c733f"}, + {file = "maturin-1.4.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:076970a73da7fa3648204a584cd347b899c1ea67f8124b212bccd06728e63ed9"}, + {file = "maturin-1.4.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f8eded83abdb30b2b6ae6d32c80b8192bdd8bcfec0ebfacee6ac02434aa499d6"}, + {file = "maturin-1.4.0-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:ff95a4494d9e57b6e74d4d7f8a9a2ee8ed29bd7f0e61855656ad959a432c0efc"}, + {file = "maturin-1.4.0-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:16239a7648ef17976585353e381840c18e650d352576ed9545abca407d65e534"}, + {file = "maturin-1.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:77428c043d585f038f4b056c4d617e00a8027b49598ab6d065b8f6b9b9b8d144"}, + {file = "maturin-1.4.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:b4b2f006db1e92687c814576029157dcc2d97b5750fd35fd4f3aabb97e36444f"}, + {file = "maturin-1.4.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:ffe4e967080ceb83c156e73a37d3974b30cad01c376a86dc39a76a0c6bccf9b0"}, + {file = "maturin-1.4.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01473dc30aed8f2cee3572b3e99e3ea75bf09c84b028bf6077f7643a189699c8"}, + {file = "maturin-1.4.0-py3-none-win32.whl", hash = "sha256:e669ba5984c15e29b8545b295ba6738974180b44f47f5d9e75569a5ce6b8add5"}, + {file = "maturin-1.4.0-py3-none-win_amd64.whl", hash = "sha256:e2c1b157397ef3721b9c2f3f24d9a5a60bd84322aac13b4dd0704a80448741b0"}, + {file = "maturin-1.4.0-py3-none-win_arm64.whl", hash = "sha256:2979175a7eee837dc3a6931980b37ddc86b9ced54d600856668fc074ca2530ef"}, + {file = "maturin-1.4.0.tar.gz", hash = "sha256:ed12e1768094a7adeafc3a74ebdb8dc2201fa64c4e7e31f14cfc70378bf93790"}, +] + +[package.dependencies] +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +patchelf = ["patchelf"] +zig = ["ziglang (>=0.10.0,<0.11.0)"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mypy" +version = "1.8.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, + {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, + {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, + {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, + {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, + {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, + {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "numpy" +version = "1.26.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"}, + {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"}, + {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"}, + {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"}, + {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"}, + {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"}, + {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"}, + {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"}, + {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"}, + {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"}, + {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"}, + {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"}, + {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"}, + {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"}, + {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"}, + {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"}, + {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"}, + {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"}, + {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"}, + {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"}, + {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"}, + {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"}, + {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"}, + {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"}, + {file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"}, + {file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"}, + {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"}, + {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"}, + {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"}, + {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"}, + {file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"}, + {file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"}, + {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "parso" +version = "0.7.1" +description = "A Python Parser" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"}, + {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"}, +] + +[package.extras] +testing = ["docopt", "pytest (>=3.0.7)"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.1.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, +] + +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + +[[package]] +name = "pysen" +version = "0.10.5" +description = "Python linting made easy. Also a casual yet honorific way to address individuals who have entered an organization prior to you." +optional = false +python-versions = "*" +files = [ + {file = "pysen-0.10.5-py3-none-any.whl", hash = "sha256:4e8a83263f04585807e3754622bb635d4a0ccd88ec1a4f324e8c9efba300237f"}, + {file = "pysen-0.10.5.tar.gz", hash = "sha256:61a6674e0b8a0c6b837b878310bd4117c5d4108dd95db572caa31c5b311d85bb"}, +] + +[package.dependencies] +colorlog = ">=4.0.0,<5.0.0" +dacite = ">=1.1.0,<2.0.0" +GitPython = ">=3.0.0,<4.0.0" +tomlkit = ">=0.5.11,<1.0.0" +unidiff = ">=0.6.0,<1.0.0" + +[package.extras] +lint = ["black (>=19.10b0,<=22.10)", "flake8 (>=3.7,<5)", "flake8-bugbear", "isort (>=4.3,<5.2.0)", "mypy (>=0.770,<0.800)"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-jsonrpc-server" +version = "0.4.0" +description = "JSON RPC 2.0 server library" +optional = false +python-versions = "*" +files = [ + {file = "python-jsonrpc-server-0.4.0.tar.gz", hash = "sha256:62c543e541f101ec5b57dc654efc212d2c2e3ea47ff6f54b2e7dcb36ecf20595"}, + {file = "python_jsonrpc_server-0.4.0-py3-none-any.whl", hash = "sha256:e5a908ff182e620aac07db5f57887eeb0afe33993008f57dc1b85b594cea250c"}, +] + +[package.dependencies] +ujson = ">=3.0.0" + +[package.extras] +test = ["coverage", "mock", "pycodestyle", "pyflakes", "pylint", "pytest", "pytest-cov", "versioneer"] + +[[package]] +name = "python-language-server" +version = "0.36.2" +description = "Python Language Server for the Language Server Protocol" +optional = false +python-versions = "*" +files = [ + {file = "python-language-server-0.36.2.tar.gz", hash = "sha256:9984c84a67ee2c5102c8e703215f407fcfa5e62b0ae86c9572d0ada8c4b417b0"}, + {file = "python_language_server-0.36.2-py2.py3-none-any.whl", hash = "sha256:a0ad0aca03f4a20c1c40f4f230c6773eac82c9b7cdb026cb09ba10237f4815d5"}, +] + +[package.dependencies] +jedi = ">=0.17.2,<0.18.0" +pluggy = "*" +python-jsonrpc-server = ">=0.4.0" +ujson = {version = ">=3.0.0", markers = "python_version > \"3\""} + +[package.extras] +all = ["autopep8", "flake8 (>=3.8.0)", "mccabe (>=0.6.0,<0.7.0)", "pycodestyle (>=2.6.0,<2.7.0)", "pydocstyle (>=2.0.0)", "pyflakes (>=2.2.0,<2.3.0)", "pylint (>=2.5.0)", "rope (>=0.10.5)", "yapf"] +autopep8 = ["autopep8"] +flake8 = ["flake8 (>=3.8.0)"] +mccabe = ["mccabe (>=0.6.0,<0.7.0)"] +pycodestyle = ["pycodestyle (>=2.6.0,<2.7.0)"] +pydocstyle = ["pydocstyle (>=2.0.0)"] +pyflakes = ["pyflakes (>=2.2.0,<2.3.0)"] +pylint = ["pylint (>=2.5.0)"] +rope = ["rope (>0.10.5)"] +test = ["coverage", "flaky", "matplotlib", "mock", "numpy", "pandas", "pylint (>=2.5.0)", "pyqt5", "pytest", "pytest-cov", "versioneer"] +yapf = ["yapf"] + +[[package]] +name = "smmap" +version = "5.0.1" +description = "A pure Python implementation of a sliding window memory map manager" +optional = false +python-versions = ">=3.7" +files = [ + {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, + {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.12.3" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, + {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, +] + +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[[package]] +name = "ujson" +version = "5.9.0" +description = "Ultra fast JSON encoder and decoder for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ujson-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab71bf27b002eaf7d047c54a68e60230fbd5cd9da60de7ca0aa87d0bccead8fa"}, + {file = "ujson-5.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a365eac66f5aa7a7fdf57e5066ada6226700884fc7dce2ba5483538bc16c8c5"}, + {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e015122b337858dba5a3dc3533af2a8fc0410ee9e2374092f6a5b88b182e9fcc"}, + {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:779a2a88c53039bebfbccca934430dabb5c62cc179e09a9c27a322023f363e0d"}, + {file = "ujson-5.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10ca3c41e80509fd9805f7c149068fa8dbee18872bbdc03d7cca928926a358d5"}, + {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a566e465cb2fcfdf040c2447b7dd9718799d0d90134b37a20dff1e27c0e9096"}, + {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f833c529e922577226a05bc25b6a8b3eb6c4fb155b72dd88d33de99d53113124"}, + {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b68a0caab33f359b4cbbc10065c88e3758c9f73a11a65a91f024b2e7a1257106"}, + {file = "ujson-5.9.0-cp310-cp310-win32.whl", hash = "sha256:7cc7e605d2aa6ae6b7321c3ae250d2e050f06082e71ab1a4200b4ae64d25863c"}, + {file = "ujson-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6d3f10eb8ccba4316a6b5465b705ed70a06011c6f82418b59278fbc919bef6f"}, + {file = "ujson-5.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b23bbb46334ce51ddb5dded60c662fbf7bb74a37b8f87221c5b0fec1ec6454b"}, + {file = "ujson-5.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6974b3a7c17bbf829e6c3bfdc5823c67922e44ff169851a755eab79a3dd31ec0"}, + {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5964ea916edfe24af1f4cc68488448fbb1ec27a3ddcddc2b236da575c12c8ae"}, + {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ba7cac47dd65ff88571eceeff48bf30ed5eb9c67b34b88cb22869b7aa19600d"}, + {file = "ujson-5.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bbd91a151a8f3358c29355a491e915eb203f607267a25e6ab10531b3b157c5e"}, + {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:829a69d451a49c0de14a9fecb2a2d544a9b2c884c2b542adb243b683a6f15908"}, + {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a807ae73c46ad5db161a7e883eec0fbe1bebc6a54890152ccc63072c4884823b"}, + {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8fc2aa18b13d97b3c8ccecdf1a3c405f411a6e96adeee94233058c44ff92617d"}, + {file = "ujson-5.9.0-cp311-cp311-win32.whl", hash = "sha256:70e06849dfeb2548be48fdd3ceb53300640bc8100c379d6e19d78045e9c26120"}, + {file = "ujson-5.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:7309d063cd392811acc49b5016728a5e1b46ab9907d321ebbe1c2156bc3c0b99"}, + {file = "ujson-5.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:20509a8c9f775b3a511e308bbe0b72897ba6b800767a7c90c5cca59d20d7c42c"}, + {file = "ujson-5.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b28407cfe315bd1b34f1ebe65d3bd735d6b36d409b334100be8cdffae2177b2f"}, + {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d302bd17989b6bd90d49bade66943c78f9e3670407dbc53ebcf61271cadc399"}, + {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f21315f51e0db8ee245e33a649dd2d9dce0594522de6f278d62f15f998e050e"}, + {file = "ujson-5.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5635b78b636a54a86fdbf6f027e461aa6c6b948363bdf8d4fbb56a42b7388320"}, + {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82b5a56609f1235d72835ee109163c7041b30920d70fe7dac9176c64df87c164"}, + {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5ca35f484622fd208f55041b042d9d94f3b2c9c5add4e9af5ee9946d2d30db01"}, + {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:829b824953ebad76d46e4ae709e940bb229e8999e40881338b3cc94c771b876c"}, + {file = "ujson-5.9.0-cp312-cp312-win32.whl", hash = "sha256:25fa46e4ff0a2deecbcf7100af3a5d70090b461906f2299506485ff31d9ec437"}, + {file = "ujson-5.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:60718f1720a61560618eff3b56fd517d107518d3c0160ca7a5a66ac949c6cf1c"}, + {file = "ujson-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d581db9db9e41d8ea0b2705c90518ba623cbdc74f8d644d7eb0d107be0d85d9c"}, + {file = "ujson-5.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ff741a5b4be2d08fceaab681c9d4bc89abf3c9db600ab435e20b9b6d4dfef12e"}, + {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdcb02cabcb1e44381221840a7af04433c1dc3297af76fde924a50c3054c708c"}, + {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e208d3bf02c6963e6ef7324dadf1d73239fb7008491fdf523208f60be6437402"}, + {file = "ujson-5.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4b3917296630a075e04d3d07601ce2a176479c23af838b6cf90a2d6b39b0d95"}, + {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0c4d6adb2c7bb9eb7c71ad6f6f612e13b264942e841f8cc3314a21a289a76c4e"}, + {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0b159efece9ab5c01f70b9d10bbb77241ce111a45bc8d21a44c219a2aec8ddfd"}, + {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0cb4a7814940ddd6619bdce6be637a4b37a8c4760de9373bac54bb7b229698b"}, + {file = "ujson-5.9.0-cp38-cp38-win32.whl", hash = "sha256:dc80f0f5abf33bd7099f7ac94ab1206730a3c0a2d17549911ed2cb6b7aa36d2d"}, + {file = "ujson-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:506a45e5fcbb2d46f1a51fead991c39529fc3737c0f5d47c9b4a1d762578fc30"}, + {file = "ujson-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0fd2eba664a22447102062814bd13e63c6130540222c0aa620701dd01f4be81"}, + {file = "ujson-5.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bdf7fc21a03bafe4ba208dafa84ae38e04e5d36c0e1c746726edf5392e9f9f36"}, + {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2f909bc08ce01f122fd9c24bc6f9876aa087188dfaf3c4116fe6e4daf7e194f"}, + {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd4ea86c2afd41429751d22a3ccd03311c067bd6aeee2d054f83f97e41e11d8f"}, + {file = "ujson-5.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:63fb2e6599d96fdffdb553af0ed3f76b85fda63281063f1cb5b1141a6fcd0617"}, + {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:32bba5870c8fa2a97f4a68f6401038d3f1922e66c34280d710af00b14a3ca562"}, + {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:37ef92e42535a81bf72179d0e252c9af42a4ed966dc6be6967ebfb929a87bc60"}, + {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f69f16b8f1c69da00e38dc5f2d08a86b0e781d0ad3e4cc6a13ea033a439c4844"}, + {file = "ujson-5.9.0-cp39-cp39-win32.whl", hash = "sha256:3382a3ce0ccc0558b1c1668950008cece9bf463ebb17463ebf6a8bfc060dae34"}, + {file = "ujson-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:6adef377ed583477cf005b58c3025051b5faa6b8cc25876e594afbb772578f21"}, + {file = "ujson-5.9.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ffdfebd819f492e48e4f31c97cb593b9c1a8251933d8f8972e81697f00326ff1"}, + {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4eec2ddc046360d087cf35659c7ba0cbd101f32035e19047013162274e71fcf"}, + {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbb90aa5c23cb3d4b803c12aa220d26778c31b6e4b7a13a1f49971f6c7d088e"}, + {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0823cb70866f0d6a4ad48d998dd338dce7314598721bc1b7986d054d782dfd"}, + {file = "ujson-5.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4e35d7885ed612feb6b3dd1b7de28e89baaba4011ecdf995e88be9ac614765e9"}, + {file = "ujson-5.9.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b048aa93eace8571eedbd67b3766623e7f0acbf08ee291bef7d8106210432427"}, + {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:323279e68c195110ef85cbe5edce885219e3d4a48705448720ad925d88c9f851"}, + {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ac92d86ff34296f881e12aa955f7014d276895e0e4e868ba7fddebbde38e378"}, + {file = "ujson-5.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6eecbd09b316cea1fd929b1e25f70382917542ab11b692cb46ec9b0a26c7427f"}, + {file = "ujson-5.9.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:473fb8dff1d58f49912323d7cb0859df5585cfc932e4b9c053bf8cf7f2d7c5c4"}, + {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f91719c6abafe429c1a144cfe27883eace9fb1c09a9c5ef1bcb3ae80a3076a4e"}, + {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1c0991c4fe256f5fdb19758f7eac7f47caac29a6c57d0de16a19048eb86bad"}, + {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8ea0f55a1396708e564595aaa6696c0d8af532340f477162ff6927ecc46e21"}, + {file = "ujson-5.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:07e0cfdde5fd91f54cd2d7ffb3482c8ff1bf558abf32a8b953a5d169575ae1cd"}, + {file = "ujson-5.9.0.tar.gz", hash = "sha256:89cc92e73d5501b8a7f48575eeb14ad27156ad092c2e9fc7e3cf949f07e75532"}, +] + +[[package]] +name = "unidiff" +version = "0.7.5" +description = "Unified diff parsing/metadata extraction library." +optional = false +python-versions = "*" +files = [ + {file = "unidiff-0.7.5-py2.py3-none-any.whl", hash = "sha256:c93bf2265cc1ba2a520e415ab05da587370bc2a3ae9e0414329f54f0c2fc09e8"}, + {file = "unidiff-0.7.5.tar.gz", hash = "sha256:2e5f0162052248946b9f0970a40e9e124236bf86c82b70821143a6fc1dea2574"}, +] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.10,<4.0" +content-hash = "a344b17318ae47d45ed500dc385ae16ecade1b3f1c125fb50eeb9f02934f7b15" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3b810d1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,67 @@ +[tool.poetry] +name = "xsor" +version = "0.0.1" +description = "" +authors = ["altescy "] + +[[tool.poetry.source]] +name = "pypi" +priority = "primary" + +[tool.poetry.dependencies] +python = ">=3.10,<4.0" + +[tool.poetry.group.dev.dependencies] +python-language-server = "^0.36.2" +pytest = "^7.3.1" +pysen = "^0.10.4" +black = "^23.3.0" +isort = "^5.12.0" +flake8 = "^6.0.0" +mypy = "^1.2.0" +maturin = "^1.4.0" + + +[tool.poetry.group.benchmark.dependencies] +numpy = "*" + +[tool.pysen] +version = "0.10" + +[tool.pysen-cli] +settings_dir = "." + +[tool.pysen.lint] +enable_black = true +enable_flake8 = true +enable_isort = true +enable_mypy = true +mypy_preset = "strict" +line_length = 120 +py_version = "py310" +[[tool.pysen.lint.mypy_targets]] + paths = ["."] + +[tool.pysen.lint.source] + includes = ["."] + excludes = [".venv/"] + + +[tool.black] # automatically generated by pysen +# pysen ignores and overwrites any modifications +line-length = 120 +target-version = ["py310"] + +[tool.isort] # automatically generated by pysen +# pysen ignores and overwrites any modifications +default_section = "THIRDPARTY" +ensure_newline_before_comments = true +force_grid_wrap = 0 +force_single_line = false +include_trailing_comma = true +line_length = 120 +multi_line_output = 3 +use_parentheses = true +[build-system] +requires = ["maturin>=0.12,<0.13"] +build-backend = "maturin" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..d51cb03 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,36 @@ +[flake8] +# automatically generated by pysen +# pysen ignores and overwrites any modifications +# e203: black treats : as a binary operator +# e231: black doesn't put a space after , +# e501: black may exceed the line-length to follow other style rules +# w503 or w504: either one needs to be disabled to select w error codes +ignore = E203,E231,E501,W503 +max-line-length = 120 +select = B,B950,C,E,F,W + +[mypy] +# automatically generated by pysen +# pysen ignores and overwrites any modifications +check_untyped_defs = True +disallow_any_decorated = False +disallow_any_generics = False +disallow_any_unimported = False +disallow_incomplete_defs = True +disallow_subclassing_any = True +disallow_untyped_calls = True +disallow_untyped_decorators = False +disallow_untyped_defs = True +ignore_errors = False +ignore_missing_imports = True +no_implicit_optional = True +python_version = 3.10 +show_error_codes = True +strict_equality = True +strict_optional = True +warn_redundant_casts = True +warn_return_any = True +warn_unreachable = True +warn_unused_configs = True +warn_unused_ignores = False + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..27435cd --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,221 @@ +#![feature(portable_simd)] +#![feature(iter_collect_into)] + +use pyo3::prelude::*; + +mod simd; +mod tensor; +mod util; + +use simd::{ + add_f32_vector_simd, div_f32_tensor_simd, matmul_f32_tensor_simd, mul_f32_tensor_simd, + sub_f32_vector_simd, sum_f32_vector_simd, +}; +use tensor::Tensor; +use util::broadcast_tensors; + +#[pyclass(name = "TensorF32")] +struct PyTensorF32 { + tensor: Tensor, +} + +#[pymethods] +impl PyTensorF32 { + #[new] + fn __new__(data: Vec, shape: Vec) -> Self { + assert_eq!(data.len(), shape.iter().product()); + Self { + tensor: Tensor { data, shape }, + } + } + + #[staticmethod] + fn zeros(shape: Vec) -> Self { + Self { + tensor: Tensor::zeros(&shape), + } + } + + #[staticmethod] + fn ones(shape: Vec) -> Self { + Self { + tensor: Tensor::ones(&shape), + } + } + + #[staticmethod] + fn eye(size: usize) -> Self { + Self { + tensor: Tensor::eye(size), + } + } + + fn __repr__(&self) -> String { + format!("{}", &self.tensor) + } + + fn neg(&self) -> Self { + Self { + tensor: self.tensor.neg(), + } + } + + fn add_tensor(&self, other: &Self) -> Self { + let tensors: Vec>; + let (left, right) = if self.tensor.shape == other.tensor.shape { + (&self.tensor, &other.tensor) + } else { + tensors = broadcast_tensors(&[&self.tensor, &other.tensor]); + (&tensors[0], &tensors[1]) + }; + Self { + tensor: Tensor { + data: add_f32_vector_simd(&left.data, &right.data), + shape: left.shape.clone(), + }, + } + } + + fn sub_tensor(&self, other: &Self) -> Self { + let tensors: Vec>; + let (left, right) = if self.tensor.shape == other.tensor.shape { + (&self.tensor, &other.tensor) + } else { + tensors = broadcast_tensors(&[&self.tensor, &other.tensor]); + (&tensors[0], &tensors[1]) + }; + Self { + tensor: Tensor { + data: sub_f32_vector_simd(&left.data, &right.data), + shape: left.shape.clone(), + }, + } + } + + fn mul_tensor(&self, other: &Self) -> Self { + let tensors: Vec>; + let (left, right) = if self.tensor.shape == other.tensor.shape { + (&self.tensor, &other.tensor) + } else { + tensors = broadcast_tensors(&[&self.tensor, &other.tensor]); + (&tensors[0], &tensors[1]) + }; + Self { + tensor: Tensor { + data: mul_f32_tensor_simd(&left.data, &right.data), + shape: left.shape.clone(), + }, + } + } + + fn div_tensor(&self, other: &Self) -> Self { + let tensors: Vec>; + let (left, right) = if self.tensor.shape == other.tensor.shape { + (&self.tensor, &other.tensor) + } else { + tensors = broadcast_tensors(&[&self.tensor, &other.tensor]); + (&tensors[0], &tensors[1]) + }; + Self { + tensor: Tensor { + data: div_f32_tensor_simd(&left.data, &right.data), + shape: left.shape.clone(), + }, + } + } + + fn add_scalar(&self, scalar: f32) -> Self { + Self { + tensor: self.tensor.add_scalar(scalar), + } + } + + fn sub_scalar(&self, scalar: f32) -> Self { + Self { + tensor: self.tensor.sub_scalar(scalar), + } + } + + fn mul_scalar(&self, scalar: f32) -> Self { + Self { + tensor: self.tensor.mul_scalar(scalar), + } + } + + fn div_scalar(&self, scalar: f32) -> Self { + Self { + tensor: self.tensor.div_scalar(scalar), + } + } + + fn matmul(&self, other: &Self) -> Self { + let (left, right) = (&self.tensor, &other.tensor); + assert_eq!(left.shape.len(), 2, "Left tensor must be 2D"); + assert_eq!(right.shape.len(), 2, "Right tensor must be 2D"); + assert_eq!(left.shape[1], right.shape[0], "Inner dimensions must match"); + Self { + tensor: Tensor { + data: matmul_f32_tensor_simd(&left.data, &right.data, &left.shape, &right.shape), + shape: vec![left.shape[0], right.shape[1]], + }, + } + } + + fn sum(&self) -> Self { + Self { + tensor: Tensor { + data: vec![sum_f32_vector_simd(&self.tensor.data)], + shape: vec![], + }, + } + } + + fn get(&self, index: Vec) -> Self { + let flattened_index = util::get_flattened_indice(&self.tensor.shape, &index); + let value = self.tensor.data[flattened_index]; + Self { + tensor: Tensor { + data: vec![value], + shape: vec![], + }, + } + } + + #[getter] + fn shape(&self) -> Vec { + self.tensor.shape.clone() + } + + #[getter] + fn data(&self) -> Vec { + self.tensor.data.clone() + } + + #[getter] + fn size(&self) -> usize { + self.tensor.data.len() + } + + #[getter] + fn ndim(&self) -> usize { + self.tensor.shape.len() + } + + #[getter] + fn value(&self) -> f32 { + assert_eq!(self.tensor.data.len(), 1); + self.tensor.data[0] + } + + fn reshape(&self, shape: Vec) -> Self { + Self { + tensor: self.tensor.reshape(&shape), + } + } +} + +#[pymodule] +fn xsor(_py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} diff --git a/src/simd.rs b/src/simd.rs new file mode 100644 index 0000000..302204c --- /dev/null +++ b/src/simd.rs @@ -0,0 +1,139 @@ +use std::simd::Simd; + +pub fn sum_f32_vector_simd(a: &[f32]) -> f32 { + let chunk_size = 4; + let chunks = a.len() / chunk_size; + let mut sum = Simd::::splat(0.0); + for i in 0..chunks { + let a = Simd::::from_slice(&a[i * chunk_size..]); + sum += a; + } + let mut result = sum.as_array().iter().sum::(); + for i in (chunks * chunk_size)..a.len() { + result += a[i]; + } + result +} + +pub fn add_f32_vector_simd(a: &[f32], b: &[f32]) -> Vec { + assert_eq!(a.len(), b.len(), "Vectors must be of the same length"); + let mut result = Vec::with_capacity(a.len()); + if a.is_empty() { + return result; + } + let chunk_size = 4; + let chunks = a.len() / chunk_size; + for i in 0..chunks { + let a = Simd::::from_slice(&a[i * chunk_size..]); + let b = Simd::::from_slice(&b[i * chunk_size..]); + let c = a + b; + result.extend_from_slice(c.as_array()); + } + for i in (chunks * chunk_size)..a.len() { + result.push(a[i] + b[i]); + } + result +} + +pub fn sub_f32_vector_simd(a: &[f32], b: &[f32]) -> Vec { + assert_eq!(a.len(), b.len(), "Vectors must be of the same length"); + let mut result = Vec::with_capacity(a.len()); + if a.is_empty() { + return result; + } + let chunk_size = 4; + let chunks = a.len() / chunk_size; + for i in 0..chunks { + let a = Simd::::from_slice(&a[i * chunk_size..]); + let b = Simd::::from_slice(&b[i * chunk_size..]); + let c = a - b; + result.extend_from_slice(c.as_array()); + } + for i in (chunks * chunk_size)..a.len() { + result.push(a[i] - b[i]); + } + result +} + +pub fn mul_f32_tensor_simd(a: &[f32], b: &[f32]) -> Vec { + assert_eq!(a.len(), b.len(), "Vectors must be of the same length"); + let mut result = Vec::with_capacity(a.len()); + if a.is_empty() { + return result; + } + let chunk_size = 4; + let chunks = a.len() / chunk_size; + for i in 0..chunks { + let a = Simd::::from_slice(&a[i * chunk_size..]); + let b = Simd::::from_slice(&b[i * chunk_size..]); + let c = a * b; + result.extend_from_slice(c.as_array()); + } + for i in (chunks * chunk_size)..a.len() { + result.push(a[i] * b[i]); + } + result +} + +pub fn div_f32_tensor_simd(a: &[f32], b: &[f32]) -> Vec { + assert_eq!(a.len(), b.len(), "Vectors must be of the same length"); + let mut result = Vec::with_capacity(a.len()); + if a.is_empty() { + return result; + } + let chunk_size = 4; + let chunks = a.len() / chunk_size; + for i in 0..chunks { + let a = Simd::::from_slice(&a[i * chunk_size..]); + let b = Simd::::from_slice(&b[i * chunk_size..]); + let c = a / b; + result.extend_from_slice(c.as_array()); + } + for i in (chunks * chunk_size)..a.len() { + result.push(a[i] / b[i]); + } + result +} + +pub fn matmul_f32_tensor_simd( + a: &[f32], + b: &[f32], + a_shape: &[usize], + b_shape: &[usize], +) -> Vec { + assert_eq!( + a_shape[1], b_shape[0], + "Shapes do not align for matrix multiplication" + ); + + let m = a_shape[0]; + let n = b_shape[1]; + let k = a_shape[1]; + + let chunk_size = 4; + let chunks = k / chunk_size; + + let mut result = vec![0.0; m * n]; + let mut bcol = vec![0.0; k]; + for j in 0..n { + for (i, bcol) in bcol.iter_mut().enumerate() { + *bcol = b[i * n + j]; + } + for i in 0..m { + let arow = &a[i * k..(i + 1) * k]; + let mut sum = Simd::::splat(0.0); + for chunk in 0..chunks { + let a = Simd::::from_slice(&arow[chunk * chunk_size..]); + let b = Simd::::from_slice(&bcol[chunk * chunk_size..]); + sum += a * b; + } + let mut res = sum.as_array().iter().sum::(); + for chunk in (chunks * chunk_size)..k { + res += arow[chunk] * bcol[chunk]; + } + result[i * n + j] = res; + } + } + + result +} diff --git a/src/tensor.rs b/src/tensor.rs new file mode 100644 index 0000000..1d341ca --- /dev/null +++ b/src/tensor.rs @@ -0,0 +1,179 @@ +use num::Num; +use std::fmt::{self, Display, Formatter}; + +#[derive(Debug, PartialEq)] +pub struct Tensor { + pub data: Vec, + pub shape: Vec, +} + +impl Tensor +where + T: Copy, +{ + pub fn new(data: &[T], shape: &[usize]) -> Self { + assert_eq!(data.len(), shape.iter().product()); + Self { + data: data.to_vec(), + shape: shape.to_vec(), + } + } +} + +impl Clone for Tensor +where + T: Copy, +{ + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + shape: self.shape.clone(), + } + } +} + +impl Tensor +where + T: Copy, +{ + pub fn reshape(&self, shape: &[usize]) -> Self { + assert_eq!(self.data.len(), shape.iter().product()); + Self::new(&self.data, shape) + } +} + +impl Display for Tensor +where + T: Display, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", format_tensor(0, &self.shape, &self.data)) + } +} + +impl Tensor +where + T: Num + Copy, +{ + pub fn zeros(shape: &[usize]) -> Self { + let size = shape.iter().product(); + let data = vec![T::zero(); size]; + Self::new(&data, shape) + } + + pub fn ones(shape: &[usize]) -> Self { + let size = shape.iter().product(); + let data = vec![T::one(); size]; + Self::new(&data, shape) + } + + pub fn eye(size: usize) -> Self { + let mut data = vec![T::zero(); size * size]; + for i in 0..size { + data[i * size + i] = T::one(); + } + Self::new(&data, &[size, size]) + } + + pub fn neg(&self) -> Self { + let data: Vec<_> = self.data.iter().map(|a| T::zero() - *a).collect(); + Self::new(&data, &self.shape) + } + + pub fn add(&self, other: &Self) -> Self { + let data: Vec<_> = self + .data + .iter() + .zip(other.data.iter()) + .map(|(a, b)| *a + *b) + .collect(); + Self::new(&data, &self.shape) + } + + pub fn sub(&self, other: &Self) -> Self { + let data: Vec<_> = self + .data + .iter() + .zip(other.data.iter()) + .map(|(a, b)| *a - *b) + .collect(); + Self::new(&data, &self.shape) + } + + pub fn mul(&self, other: &Self) -> Self { + let data: Vec<_> = self + .data + .iter() + .zip(other.data.iter()) + .map(|(a, b)| *a * *b) + .collect(); + Self::new(&data, &self.shape) + } + + pub fn div(&self, other: &Self) -> Self { + let data: Vec<_> = self + .data + .iter() + .zip(other.data.iter()) + .map(|(a, b)| *a / *b) + .collect(); + Self::new(&data, &self.shape) + } + + pub fn add_scalar(&self, scalar: T) -> Self { + let data: Vec<_> = self.data.iter().map(|a| *a + scalar).collect(); + Self::new(&data, &self.shape) + } + + pub fn sub_scalar(&self, scalar: T) -> Self { + let data: Vec<_> = self.data.iter().map(|a| *a - scalar).collect(); + Self::new(&data, &self.shape) + } + + pub fn mul_scalar(&self, scalar: T) -> Self { + let data: Vec<_> = self.data.iter().map(|a| *a * scalar).collect(); + Self::new(&data, &self.shape) + } + + pub fn div_scalar(&self, scalar: T) -> Self { + let data: Vec<_> = self.data.iter().map(|a| *a / scalar).collect(); + Self::new(&data, &self.shape) + } +} + +fn format_tensor(level: usize, shape: &[usize], data: &[T]) -> String +where + T: Display, +{ + if shape.is_empty() { + return format!("{}", data[0]); + } + let dim = shape[0]; + let extra_size: usize = shape[1..].iter().product(); + let substrings = (0..dim) + .map(|i| { + format_tensor( + level + 1, + &shape[1..], + &data[i * extra_size..(i + 1) * extra_size], + ) + }) + .collect::>(); + if shape.len() == 1 { + return format!("[{}]", substrings.join(", ")); + } + let newline = "\n"; + let indent = " ".repeat(level); + let inner_delimiter = format!(",{}", newline); + let outer_delimiter = format!("{}{}", newline, indent); + format!( + "[{}{}{}]", + newline, + substrings + .iter() + .map(|s| format!("{} {}", &indent, &s)) + .collect::>() + .join(&inner_delimiter), + outer_delimiter, + ) +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..422ba13 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,137 @@ +use std::collections::HashSet; + +use super::tensor::Tensor; + +pub fn get_flattened_indice(shape: &[usize], index: &[usize]) -> usize { + assert_eq!(shape.len(), index.len()); + index.iter().enumerate().fold(0, |acc, (i, &v)| { + acc + v * shape[i + 1..].iter().product::() + }) +} + +pub fn get_nested_indice(shape: &[usize], index: usize) -> Vec { + let mut index = index; + let mut nested_index = vec![0; shape.len()]; + for i in (0..shape.len()).rev() { + let size = shape[i]; + nested_index[i] = index % size; + index /= size; + } + nested_index +} + +pub fn broadcast_shapes(shapes: &[&[usize]]) -> Vec { + let max_ndim = shapes.iter().map(|s| s.len()).max().unwrap(); + let shapes: Vec> = shapes + .iter() + .map(|s| { + let mut s = s.to_vec(); + while s.len() < max_ndim { + s.insert(0, 1); + } + s + }) + .collect(); + let mut shape = vec![1; max_ndim]; + for dim in 0..max_ndim { + let sizes: HashSet = shapes.iter().map(|s| s[dim]).collect(); + assert!( + sizes.len() <= 2 || (sizes.len() == 2 && sizes.contains(&1)), + "Shapes are not broadcastable" + ); + let max_size = sizes.iter().max().unwrap(); + shape[dim] = *max_size; + } + shape +} + +pub fn broadcast_to(tensor: &Tensor, shape: &[usize]) -> Tensor +where + T: Copy, +{ + assert!(tensor.shape.len() <= shape.len(), "Shape is too small"); + if tensor.shape == shape { + return tensor.clone(); + } + if tensor.shape.is_empty() { + return Tensor { + data: vec![tensor.data[0]; shape.iter().product()], + shape: shape.to_vec(), + }; + } + let shape = broadcast_shapes(&[&tensor.shape, shape]); + let diff_ndim = shape.len() - tensor.shape.len(); + let expanded_shape: Vec = shape + .iter() + .enumerate() + .map(|(i, &v)| if i < diff_ndim { 1 } else { v }) + .collect(); + let dims_to_expand: HashSet = shape + .iter() + .enumerate() + .filter(|(i, &v)| *i < diff_ndim || v != tensor.shape[*i - diff_ndim]) + .map(|(i, _)| i) + .collect(); + let size = shape.iter().product(); + let mut data = vec![tensor.data[0]; size]; + for index in 0..size { + let nested_index = get_nested_indice(&shape, index) + .iter() + .enumerate() + .map(|(i, &v)| if dims_to_expand.contains(&i) { 0 } else { v }) + .collect::>(); + let flattened_index = get_flattened_indice(&expanded_shape, &nested_index); + data[index] = tensor.data[flattened_index]; + } + Tensor { data, shape } +} + +pub fn broadcast_tensors(tensors: &[&Tensor]) -> Vec> +where + T: Copy, +{ + let shape = broadcast_shapes( + &tensors + .iter() + .map(|t| t.shape.as_slice()) + .collect::>(), + ); + tensors + .iter() + .map(|t| broadcast_to(t, &shape)) + .collect::>() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_flattened_indice() { + assert_eq!(get_flattened_indice(&[2, 3], &[1, 2]), 5); + assert_eq!(get_flattened_indice(&[2, 3], &[0, 0]), 0); + assert_eq!(get_flattened_indice(&[2, 3], &[1, 0]), 3); + } + + #[test] + fn test_get_nested_indice() { + assert_eq!(get_nested_indice(&[2, 3], 5), vec![1, 2]); + assert_eq!(get_nested_indice(&[2, 3], 0), vec![0, 0]); + assert_eq!(get_nested_indice(&[2, 3], 3), vec![1, 0]); + } + + #[test] + fn test_broadcast_shapes() { + assert_eq!(broadcast_shapes(&[&[1, 2, 3], &[1, 2, 3]]), vec![1, 2, 3]); + assert_eq!(broadcast_shapes(&[&[2, 3], &[3, 2, 1]]), vec![3, 2, 3]); + } + + #[test] + fn test_broadcast_to() { + let tensor = Tensor::new(&[1, 2, 3], &[3]); + assert_eq!( + broadcast_to(&tensor, &[2, 3]), + Tensor::new(&[1, 2, 3, 1, 2, 3], &[2, 3]), + ); + } +} diff --git a/tests/test_xsor.py b/tests/test_xsor.py new file mode 100644 index 0000000..81de8dd --- /dev/null +++ b/tests/test_xsor.py @@ -0,0 +1,5 @@ +import xsor + + +def test_version() -> None: + assert xsor.__version__ == "0.0.1" diff --git a/xsor/__init__.py b/xsor/__init__.py new file mode 100644 index 0000000..e4d425c --- /dev/null +++ b/xsor/__init__.py @@ -0,0 +1,29 @@ +from typing import Sequence + +from xsor.tensor import Tensor + +__version__ = "0.0.1" +__all__ = ["Tensor", "tensor", "zeros", "ones", "eye"] + + +def tensor( + data: Sequence[float], + shape: Sequence[int] | None = None, +) -> Tensor: + return Tensor(data, shape) + + +def zeros(shape: Sequence[int]) -> Tensor: + return Tensor.zeros(shape) + + +def ones(shape: Sequence[int]) -> Tensor: + return Tensor.ones(shape) + + +def eye(size: int) -> Tensor: + return Tensor.eye(size) + + +def sum(tensor: Tensor) -> Tensor: + return tensor.sum() diff --git a/xsor/tensor.py b/xsor/tensor.py new file mode 100644 index 0000000..f559bff --- /dev/null +++ b/xsor/tensor.py @@ -0,0 +1,103 @@ +from __future__ import annotations + +from typing import Sequence, Tuple + +from .xsor import TensorF32 + + +class Tensor: + def __init__( + self, + data: Sequence[float], + shape: Sequence[int] | None = None, + ): + if shape is None: + shape = (len(data),) + self._tensor = TensorF32(data, shape) + + @classmethod + def _from_tensor(cls, tensor: TensorF32) -> Tensor: + output = cls((0,), ()) # type: ignore[arg-type] + output._tensor = tensor + return output + + @property + def data(self) -> Sequence[float]: + return self._tensor.data + + @property + def size(self) -> int: + return self._tensor.size + + @property + def shape(self) -> Tuple[int, ...]: + return tuple(self._tensor.shape) + + @property + def ndim(self) -> int: + return self._tensor.ndim + + @property + def value(self) -> float: + if self.size != 1: + raise ValueError("Tensor.value is only available for scalar tensors") + return self._tensor.value + + def __repr__(self) -> str: + return f"Tensor({self._tensor}, shape={self.shape})" + + def __neg__(self) -> Tensor: + return self._from_tensor(self._tensor.neg()) + + def __add__(self, other: int | float | Tensor) -> Tensor: + if isinstance(other, (int, float)): + return self._from_tensor(self._tensor.add_scalar(float(other))) + return self._from_tensor(self._tensor.add_tensor(other._tensor)) + + def __radd__(self, other: int | float | Tensor) -> Tensor: + return self + other + + def __sub__(self, other: int | float | Tensor) -> Tensor: + if isinstance(other, (int, float)): + return self._from_tensor(self._tensor.sub_scalar(float(other))) + return self._from_tensor(self._tensor.sub_tensor(other._tensor)) + + def __rsub__(self, other: int | float | Tensor) -> Tensor: + return -(self - other) + + def __mul__(self, other: int | float | Tensor) -> Tensor: + if isinstance(other, (int, float)): + return self._from_tensor(self._tensor.mul_scalar(float(other))) + return self._from_tensor(self._tensor.mul_tensor(other._tensor)) + + def __rmul__(self, other: int | float | Tensor) -> Tensor: + return self * other + + def __truediv__(self, other: int | float | Tensor) -> Tensor: + if isinstance(other, (int, float)): + return self._from_tensor(self._tensor.div_scalar(float(other))) + return self._from_tensor(self._tensor.div_tensor(other._tensor)) + + def __rtruediv__(self, other: int | float | Tensor) -> Tensor: + raise NotImplementedError("Tensor.__rtruediv__ is not implemented") + + def __matmul__(self, other: Tensor) -> Tensor: + return self._from_tensor(self._tensor.matmul(other._tensor)) + + def sum(self) -> Tensor: + return self._from_tensor(self._tensor.sum()) + + def __getitem__(self, index: Sequence[int]) -> Tensor: + return self._from_tensor(self._tensor.get(index)) + + @classmethod + def zeros(cls, shape: Sequence[int]) -> Tensor: + return cls._from_tensor(TensorF32.zeros(shape)) + + @classmethod + def ones(cls, shape: Sequence[int]) -> Tensor: + return cls._from_tensor(TensorF32.ones(shape)) + + @classmethod + def eye(cls, n: int) -> Tensor: + return cls._from_tensor(TensorF32.eye(n)) diff --git a/xsor/types.py b/xsor/types.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/xsor/types.py @@ -0,0 +1 @@ + diff --git a/xsor/xsor.pyi b/xsor/xsor.pyi new file mode 100644 index 0000000..1fcf45a --- /dev/null +++ b/xsor/xsor.pyi @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import Sequence + +class TensorF32: + def __init__(self, data: Sequence[float], shape: Sequence[int]) -> None: ... + def neg(self) -> TensorF32: ... + def add_tensor(self, other: TensorF32) -> TensorF32: ... + def sub_tensor(self, other: TensorF32) -> TensorF32: ... + def mul_tensor(self, other: TensorF32) -> TensorF32: ... + def div_tensor(self, other: TensorF32) -> TensorF32: ... + def add_scalar(self, other: float) -> TensorF32: ... + def sub_scalar(self, other: float) -> TensorF32: ... + def mul_scalar(self, other: float) -> TensorF32: ... + def div_scalar(self, other: float) -> TensorF32: ... + def matmul(self, other: TensorF32) -> TensorF32: ... + def sum(self) -> TensorF32: ... + def reshape(self, shape: Sequence[int]) -> TensorF32: ... + def get(self, index: Sequence[int]) -> TensorF32: ... + @property + def data(self) -> Sequence[float]: ... + @property + def size(self) -> int: ... + @property + def shape(self) -> list[int]: ... + @property + def ndim(self) -> int: ... + @property + def value(self) -> float: ... + @staticmethod + def zeros(shape: Sequence[int]) -> TensorF32: ... + @staticmethod + def ones(shape: Sequence[int]) -> TensorF32: ... + @staticmethod + def eye(size: int) -> TensorF32: ...