├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .pre-commit-config.yaml ├── noxfile.py ├── projects ├── core-c-hello │ ├── .gitignore │ ├── CMakeLists.txt │ ├── pyproject.toml │ └── skcdpure.c ├── core-pybind11-hello │ ├── .gitignore │ ├── CMakeLists.txt │ ├── pyproject.toml │ └── skcdemo.cpp ├── hatchling-pybind11-hello │ ├── CMakeLists.txt │ ├── cpp │ │ └── _core.cpp │ ├── pyproject.toml │ └── src │ │ └── skcdemo │ │ └── __init__.py ├── hello-cmake-package │ ├── CMakeLists.txt │ ├── README.md │ ├── cmake │ │ └── helloConfig.cmake.in │ ├── include │ │ └── hello.hpp │ ├── pyproject.toml │ ├── setup.py │ ├── src │ │ ├── hello.cpp │ │ └── hello │ │ │ ├── __init__.py │ │ │ ├── __main__.py.in │ │ │ └── hello_py.cpp │ └── tests │ │ ├── cpp │ │ ├── CMakeLists.txt │ │ └── test.cpp │ │ └── test_hello.py ├── hello-cpp │ ├── CMakeLists.txt │ ├── hello │ │ ├── __init__.py │ │ └── _hello.cxx │ ├── pyproject.toml │ ├── setup.py │ └── tests │ │ └── test_hello_cpp.py ├── hello-cython │ ├── CMakeLists.txt │ ├── hello │ │ ├── CMakeLists.txt │ │ ├── __init__.py │ │ └── _hello.pyx │ ├── pyproject.toml │ ├── setup.py │ └── tests │ │ └── test_hello_cython.py ├── hello-free-threading │ ├── CMakeLists.txt │ ├── pyproject.toml │ ├── src │ │ └── freecomputepi │ │ │ ├── __init__.py │ │ │ ├── _core.cpp │ │ │ ├── _pybcore.cpp │ │ │ ├── comp.py │ │ │ ├── pure.py │ │ │ └── pybind.py │ └── tests │ │ └── test_thread.py ├── hello-pure │ ├── hello │ │ └── __init__.py │ ├── pyproject.toml │ ├── setup.py │ └── test_hello_pure.py ├── hello-pybind11 │ ├── CMakeLists.txt │ ├── README.md │ ├── pyproject.toml │ ├── setup.py │ ├── src │ │ └── hello │ │ │ ├── __init__.py │ │ │ └── hello_py.cpp │ ├── tests │ │ └── test_hello_pybind11.py │ └── tox.ini ├── pen2-cython │ ├── CMakeLists.txt │ ├── pyproject.toml │ ├── requirements-dev.txt │ ├── scripts │ │ ├── CMakeLists.txt │ │ └── pen2_cython │ ├── setup.py │ └── src │ │ ├── CMakeLists.txt │ │ ├── __init__.py │ │ ├── __main__.py │ │ └── _pen2.pyx ├── pi-fortran │ ├── CMakeLists.txt │ ├── README.md │ ├── pi │ │ ├── _pi.f │ │ └── pi.pyf │ ├── pyproject.toml │ └── tests │ │ └── test_pi.py └── tower-of-babel │ ├── CMakeLists.txt │ ├── requirements-dev.txt │ ├── scripts │ ├── CMakeLists.txt │ └── tbabel.pyx │ ├── setup.py │ ├── tbabel_boost_common.h │ ├── tbabel_boost_common_imp.h │ ├── tbabel_boost_module.cxx │ ├── tbabel_boost_module.h │ ├── tbabel_boost_module.pxd │ ├── tbabel_boost_shared.cxx │ ├── tbabel_boost_shared.h │ ├── tbabel_boost_shared.pxd │ ├── tbabel_boost_static.cxx │ ├── tbabel_boost_static.h │ ├── tbabel_boost_static.pxd │ ├── tbabel_cython_common.pxi │ ├── tbabel_cython_common_imp.pxi │ ├── tbabel_cython_module.pxd │ ├── tbabel_cython_module.pyx │ ├── tbabel_cython_shared.pxd │ ├── tbabel_cython_shared.pyx │ ├── tbabel_cython_static.pxd │ ├── tbabel_cython_static.pyx │ └── tbabel_python.py ├── requirements-test.txt └── ruff.toml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | - main 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | FORCE_COLOR: 3 17 | 18 | jobs: 19 | checks: 20 | runs-on: ${{ matrix.runs-on }} 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | runs-on: [ubuntu-latest, macos-13, windows-latest] 25 | session: [dist, test] 26 | 27 | name: ${{ matrix.session }} on ${{ matrix.runs-on }} 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: awvwgk/setup-fortran@main 32 | id: setup-fortran 33 | if: runner.os != 'Windows' 34 | # We aren't using this yet, and takes 4 mins to setup on Windows 35 | # On macOS this causes a bug to be hit on macos-13+ and CMake, gcc14 may fix 36 | with: 37 | compiler: gcc 38 | version: 12 39 | - uses: astral-sh/setup-uv@v3 40 | - run: uvx nox -s ${{ matrix.session }} 41 | 42 | free-threading: 43 | runs-on: ${{ matrix.runs-on }} 44 | strategy: 45 | fail-fast: false 46 | matrix: 47 | runs-on: [ubuntu-latest, windows-latest, macos-13, macos-14] 48 | name: cibw on ${{ matrix.runs-on }} 49 | steps: 50 | - uses: actions/checkout@v4 51 | - uses: astral-sh/setup-uv@v3 52 | - uses: pypa/cibuildwheel@v2.21 53 | with: 54 | package-dir: projects/hello-free-threading 55 | env: 56 | CIBW_PRERELEASE_PYTHONS: True 57 | 58 | pass: 59 | if: always() 60 | needs: [checks] 61 | runs-on: ubuntu-latest 62 | steps: 63 | - name: Decide whether the needed jobs succeeded or failed 64 | uses: re-actors/alls-green@release/v1 65 | with: 66 | jobs: ${{ toJSON(needs) }} 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | _skbuild 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | htmlcov/ 27 | .tox/ 28 | .coverage 29 | .coverage.* 30 | .cache 31 | nosetests.xml 32 | coverage.xml 33 | *.cover 34 | .hypothesis/ 35 | .pytest_cache/ 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Complexity 46 | output/*.html 47 | output/*/index.html 48 | 49 | # Sphinx 50 | docs/_build 51 | 52 | # IDE junk 53 | .idea/* 54 | *.swp 55 | 56 | # build output (testing) 57 | skbuild/cmake_test_compile/* 58 | *.env 59 | wheelhouse 60 | dist/* 61 | 62 | # Generated manifests 63 | MANIFEST 64 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-added-large-files 6 | - id: check-case-conflict 7 | - id: check-merge-conflict 8 | - id: check-symlinks 9 | - id: check-yaml 10 | - id: debug-statements 11 | - id: end-of-file-fixer 12 | - id: mixed-line-ending 13 | - id: requirements-txt-fixer 14 | - id: trailing-whitespace 15 | 16 | - repo: https://github.com/astral-sh/ruff-pre-commit 17 | rev: "v0.11.7" 18 | hooks: 19 | - id: ruff 20 | args: [--fix, --show-fixes] 21 | - id: ruff-format 22 | 23 | - repo: https://github.com/codespell-project/codespell 24 | rev: "v2.4.1" 25 | hooks: 26 | - id: codespell 27 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import nox 4 | 5 | nox.needs_version = ">=2024.4.15" 6 | nox.options.default_venv_backend = "uv|virtualenv" 7 | 8 | hello_list = ["hello-pure", "hello-cpp", "hello-pybind11", "hello-cython"] 9 | if not sys.platform.startswith("win"): 10 | hello_list.extend(["hello-cmake-package", "pi-fortran"]) 11 | long_hello_list = [ 12 | *hello_list, 13 | "pen2-cython", 14 | "core-c-hello", 15 | "core-pybind11-hello", 16 | "hatchling-pybind11-hello", 17 | ] 18 | if sys.version_info >= (3, 13): 19 | hello_list.append("hello-free-threading") 20 | 21 | 22 | @nox.session 23 | @nox.parametrize("module", long_hello_list, ids=long_hello_list) 24 | def dist(session: nox.Session, module: str) -> None: 25 | session.cd(f"projects/{module}") 26 | session.install("build") 27 | 28 | # Builds SDist and wheel 29 | opt = ["--installer=uv"] if session.venv_backend == "uv" else [] 30 | session.run("python", "-m", "build", *opt) 31 | 32 | 33 | @nox.session 34 | @nox.parametrize("module", hello_list, ids=hello_list) 35 | def test(session: nox.Session, module: str) -> None: 36 | session.cd(f"projects/{module}") 37 | session.install("pytest", "pytest-cov") 38 | 39 | session.install(".") 40 | session.run("pytest") 41 | -------------------------------------------------------------------------------- /projects/core-c-hello/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /projects/core-c-hello/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.26) 2 | 3 | project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION} LANGUAGES C) 4 | 5 | find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) 6 | 7 | Python_add_library(skcdpure MODULE WITH_SOABI skcdpure.c) 8 | 9 | install(TARGETS skcdpure DESTINATION .) 10 | -------------------------------------------------------------------------------- /projects/core-c-hello/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["scikit-build-core"] 3 | build-backend = "scikit_build_core.build" 4 | 5 | [project] 6 | name = "skcdpure" 7 | version = "0.1.0" 8 | requires-python = ">=3.9" 9 | -------------------------------------------------------------------------------- /projects/core-c-hello/skcdpure.c: -------------------------------------------------------------------------------- 1 | #define PY_SSIZE_T_CLEAN 2 | #include 3 | 4 | static PyObject *hello_function(PyObject *self, PyObject *args) { 5 | PyObject* builtins = PyImport_ImportModule("builtins"); 6 | PyObject* print = PyObject_GetAttrString(builtins, "print"); 7 | PyObject* msg = PyUnicode_FromString("Hello, scikit-build-core CAPI fans!"); 8 | PyObject* none = PyObject_CallOneArg(print, msg); 9 | 10 | Py_XDECREF(print); 11 | Py_XDECREF(builtins); 12 | Py_XDECREF(msg); 13 | Py_XDECREF(none); 14 | 15 | Py_RETURN_NONE; 16 | } 17 | 18 | static PyMethodDef skcdpure_methods[] = { 19 | {"hello", hello_function, METH_NOARGS, "Hello function"}, 20 | {NULL, NULL, 0, NULL}}; 21 | 22 | static struct PyModuleDef skcdpure_module = {PyModuleDef_HEAD_INIT, "skcdpure", 23 | NULL, -1, skcdpure_methods}; 24 | 25 | PyMODINIT_FUNC PyInit_skcdpure(void) { 26 | return PyModule_Create(&skcdpure_module); 27 | } 28 | -------------------------------------------------------------------------------- /projects/core-pybind11-hello/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /projects/core-pybind11-hello/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.26) 2 | 3 | project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION} LANGUAGES CXX) 4 | 5 | find_package(pybind11 CONFIG REQUIRED) 6 | 7 | pybind11_add_module(skcdemo MODULE skcdemo.cpp) 8 | 9 | install(TARGETS skcdemo DESTINATION .) 10 | -------------------------------------------------------------------------------- /projects/core-pybind11-hello/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["scikit-build-core", "pybind11"] 3 | build-backend = "scikit_build_core.build" 4 | 5 | [project] 6 | name = "skcdemo" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /projects/core-pybind11-hello/skcdemo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace py = pybind11; 4 | 5 | PYBIND11_MODULE(skcdemo, m) { 6 | m.def("hello", [](){ 7 | py::print("Hello, scikit-build!"); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /projects/hatchling-pybind11-hello/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.29) 2 | 3 | project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION} LANGUAGES CXX) 4 | 5 | set(PYBIND11_FINDPYTHON ON) 6 | find_package(pybind11 CONFIG REQUIRED) 7 | 8 | pybind11_add_module(_core MODULE cpp/_core.cpp) 9 | 10 | install(TARGETS _core DESTINATION skcdemo) 11 | -------------------------------------------------------------------------------- /projects/hatchling-pybind11-hello/cpp/_core.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace py = pybind11; 4 | 5 | PYBIND11_MODULE(_core, m) { 6 | m.def("hello", [](){ 7 | py::print("Hello, scikit-build!"); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /projects/hatchling-pybind11-hello/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling", "scikit-build-core~=0.10.0", "pybind11"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "skcdemo" 7 | version = "0.1.0" 8 | 9 | [tool.hatch.build.targets.wheel.hooks.scikit-build] 10 | experimental = true 11 | -------------------------------------------------------------------------------- /projects/hatchling-pybind11-hello/src/skcdemo/__init__.py: -------------------------------------------------------------------------------- 1 | from ._core import hello 2 | 3 | __all__ = ["hello"] 4 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.26) 2 | 3 | project( 4 | hello 5 | VERSION "0.1.0" 6 | LANGUAGES CXX) 7 | 8 | include(GNUInstallDirs) 9 | 10 | # define the C++ library "hello" 11 | add_library(hello SHARED "${PROJECT_SOURCE_DIR}/src/hello.cpp") 12 | 13 | target_include_directories( 14 | hello PUBLIC $ 15 | $) 16 | 17 | install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" 18 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 19 | 20 | # Standard installation subdirs for the C++ library are used. The files will end 21 | # up in the specified subdirectories under the Python package root. For example, 22 | # "/hello/lib/" if the destination is "lib/". 23 | # 24 | # Installing the objects in the package provides encapsulation and will become 25 | # important later for binary redistribution reasons. 26 | install( 27 | TARGETS hello 28 | EXPORT helloTargets 29 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) 30 | 31 | # The CMake package config and target files are installed under the Python 32 | # package root. This is necessary to ensure that all the relative paths in the 33 | # helloTargets.cmake resolve correctly. It also provides encapsulation. 34 | # 35 | # The actual path used must be selected so that consuming projects can locate it 36 | # via `find_package`. To support finding CMake packages in the Python package 37 | # prefix, using `find_package`s default search path of 38 | # `//share/*/cmake/` is reasonable. Adding the Python 39 | # package installation prefix to CMAKE_PREFIX_PATH in combination with this path 40 | # will allow `find_package` to find this package and any other package installed 41 | # via a Python package if the CMake and Python packages are named the same. 42 | set(HELLO_CMAKE_PACKAGE_INSTALL_SUBDIR "share/hello/cmake") 43 | 44 | install( 45 | EXPORT helloTargets 46 | NAMESPACE hello:: 47 | DESTINATION ${HELLO_CMAKE_PACKAGE_INSTALL_SUBDIR}) 48 | 49 | include(CMakePackageConfigHelpers) 50 | 51 | write_basic_package_version_file( 52 | helloConfigVersion.cmake 53 | VERSION ${PROJECT_VERSION} 54 | COMPATIBILITY SameMinorVersion) 55 | 56 | configure_package_config_file( 57 | "${PROJECT_SOURCE_DIR}/cmake/helloConfig.cmake.in" helloConfig.cmake 58 | INSTALL_DESTINATION ${HELLO_CMAKE_PACKAGE_INSTALL_SUBDIR}) 59 | 60 | install(FILES "${PROJECT_BINARY_DIR}/helloConfig.cmake" 61 | "${PROJECT_BINARY_DIR}/helloConfigVersion.cmake" 62 | DESTINATION ${HELLO_CMAKE_PACKAGE_INSTALL_SUBDIR}) 63 | 64 | # We are using the SKBUILD variable, which is defined when scikit-build is 65 | # running the CMake build, to control building the Python wrapper. This allows 66 | # the C++ project to be installed, standalone, when using the standard CMake 67 | # build flow. 68 | if(DEFINED SKBUILD) 69 | 70 | # call pybind11-config to obtain the root of the cmake package 71 | execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pybind11 --cmakedir 72 | OUTPUT_VARIABLE pybind11_ROOT_RAW) 73 | string(STRIP ${pybind11_ROOT_RAW} pybind11_ROOT) 74 | find_package(pybind11) 75 | 76 | pybind11_add_module(_hello MODULE 77 | "${PROJECT_SOURCE_DIR}/src/hello/hello_py.cpp") 78 | 79 | target_link_libraries(_hello PRIVATE hello) 80 | 81 | # Installing the extension module to the root of the package 82 | install(TARGETS _hello DESTINATION .) 83 | 84 | configure_file("${PROJECT_SOURCE_DIR}/src/hello/__main__.py.in" 85 | "${PROJECT_BINARY_DIR}/src/hello/__main__.py") 86 | 87 | install(FILES "${PROJECT_BINARY_DIR}/src/hello/__main__.py" DESTINATION .) 88 | 89 | # The extension module must load the hello library as a dependency when the 90 | # extension module is loaded. The easiest way to locate the hello library is 91 | # via RPATH. Absolute RPATHs are possible, but they make the resulting 92 | # binaries not redistributable to other Python installations (conda is broke, 93 | # wheel reuse is broke, and more!). 94 | # 95 | # Placing the hello library in the package and using relative RPATHs that 96 | # doesn't point outside of the package means that the built package is 97 | # relocatable. This allows for safe binary redistribution. 98 | if(APPLE) 99 | set_target_properties( 100 | _hello PROPERTIES INSTALL_RPATH "@loader_path/${CMAKE_INSTALL_LIBDIR}") 101 | else() 102 | set_target_properties(_hello PROPERTIES INSTALL_RPATH 103 | "$ORIGIN/${CMAKE_INSTALL_LIBDIR}") 104 | endif() 105 | 106 | endif() 107 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/README.md: -------------------------------------------------------------------------------- 1 | # Hello 2 | 3 | This is an example project demonstrating the use of scikit-build for distributing a standalone C library, *hello*; 4 | a CMake package for that library; and a Python wrapper implemented in pybind11. 5 | 6 | The example assume some familiarity with CMake and pybind11, only really going into detail on the scikit-build parts. 7 | pybind11 is used to implement the binding, but anything is possible: swig, C API library, etc. 8 | 9 | To install the package run in the project directory 10 | 11 | ```bash 12 | pip install . 13 | ``` 14 | 15 | To run the Python tests, first install the package then in the project directory run 16 | 17 | ```bash 18 | pytest 19 | ``` 20 | 21 | To run the C++ test, first install the package, then configure and build the project 22 | 23 | ```bash 24 | cmake -S test/cpp -B build/ -Dhello_ROOT=$(python -m hello --cmakefiles) 25 | cmake --build build/ 26 | ``` 27 | 28 | Then run ctest in the build dir 29 | 30 | ```bash 31 | cd build/ 32 | ctest 33 | ``` 34 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/cmake/helloConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/helloTargets.cmake") 4 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/include/hello.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HELLO_HPP 2 | #define HELLO_HPP 3 | 4 | #include 5 | 6 | namespace hello { 7 | 8 | void hello(); 9 | 10 | int return_two(); 11 | 12 | } // namespace hello 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "scikit-build", 5 | "cmake>=3.14", 6 | "ninja", 7 | "pybind11>=2.12" 8 | ] 9 | build-backend = "setuptools.build_meta" 10 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/setup.py: -------------------------------------------------------------------------------- 1 | from skbuild import setup 2 | 3 | setup( 4 | name="hello-cmake-package", 5 | version="0.1.0", 6 | packages=["hello"], 7 | package_dir={"": "src"}, 8 | cmake_install_dir="src/hello", 9 | ) 10 | 11 | # When building extension modules `cmake_install_dir` should always be set to the 12 | # location of the package you are building extension modules for. 13 | # Specifying the installation directory in the CMakeLists subtley breaks the relative 14 | # paths in the helloTargets.cmake file to all of the library components. 15 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/src/hello.cpp: -------------------------------------------------------------------------------- 1 | #include "hello.hpp" 2 | 3 | namespace hello { 4 | 5 | void hello() { std::cout << "Hello, World!" << std::endl; } 6 | 7 | int return_two() { return 2; } 8 | 9 | } // namespace hello 10 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/src/hello/__init__.py: -------------------------------------------------------------------------------- 1 | from ._hello import hello, return_two 2 | 3 | __all__ = ("hello", "return_two") 4 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/src/hello/__main__.py.in: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | from typing import List, Any 4 | from pathlib import Path 5 | import hello 6 | 7 | 8 | def main(argv: List[Any]) -> int: 9 | 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument("--cmakefiles", action='store_true', help="Print hello project CMake module directory. Useful for setting hello_ROOT in CMake.") 12 | parser.add_argument("--prefix", action='store_true', help="Print hello package installation prefix. Useful for setting CMAKE_PREFIX_PATH in CMake.") 13 | 14 | args = parser.parse_args(args=argv[1:]) 15 | 16 | if not argv[1:]: 17 | parser.print_help() 18 | return 19 | 20 | prefix = Path(hello.__file__).parent 21 | 22 | if args.cmakefiles: 23 | print(prefix / "@HELLO_CMAKE_PACKAGE_INSTALL_SUBDIR@") 24 | 25 | if args.prefix: 26 | print(prefix) 27 | 28 | 29 | if __name__ == "__main__": 30 | sys.exit(main(sys.argv)) 31 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/src/hello/hello_py.cpp: -------------------------------------------------------------------------------- 1 | #include "hello.hpp" 2 | #include 3 | 4 | 5 | PYBIND11_MODULE(_hello, m) { 6 | m.doc() = "Example module"; 7 | m.def("hello", &hello::hello, "Prints \"Hello, World!\""); 8 | m.def("return_two", &hello::return_two, "Returns 2"); 9 | } 10 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/tests/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14...3.19) 2 | 3 | project(hello-user VERSION 0.1.0) 4 | 5 | find_package(hello REQUIRED) 6 | 7 | add_executable(hello_user "${PROJECT_SOURCE_DIR}/test.cpp") 8 | target_link_libraries(hello_user PRIVATE hello::hello) 9 | 10 | include(CTest) 11 | 12 | add_test(NAME hello_test COMMAND hello_user) 13 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/tests/cpp/test.cpp: -------------------------------------------------------------------------------- 1 | #include "hello.hpp" 2 | 3 | 4 | int main() 5 | { 6 | hello::hello(); 7 | return (hello::return_two() != 2); 8 | } 9 | -------------------------------------------------------------------------------- /projects/hello-cmake-package/tests/test_hello.py: -------------------------------------------------------------------------------- 1 | import hello 2 | 3 | 4 | def test_hello(): 5 | hello.hello() 6 | 7 | 8 | def test_return_two(): 9 | assert hello.return_two() == 2 10 | -------------------------------------------------------------------------------- /projects/hello-cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.26) 2 | 3 | project(hello) 4 | 5 | find_package(PythonExtensions REQUIRED) 6 | 7 | add_library(_hello MODULE hello/_hello.cxx) 8 | python_extension_module(_hello) 9 | install(TARGETS _hello LIBRARY DESTINATION hello) 10 | -------------------------------------------------------------------------------- /projects/hello-cpp/hello/__init__.py: -------------------------------------------------------------------------------- 1 | from ._hello import ( 2 | elevation, # noqa: F401 3 | hello, # noqa: F401 4 | ) 5 | -------------------------------------------------------------------------------- /projects/hello-cpp/hello/_hello.cxx: -------------------------------------------------------------------------------- 1 | 2 | // Python includes 3 | #include 4 | 5 | // STD includes 6 | #include 7 | 8 | //----------------------------------------------------------------------------- 9 | static PyObject *hello_example(PyObject *self, PyObject *args) 10 | { 11 | // Unpack a string from the arguments 12 | const char *strArg; 13 | if (!PyArg_ParseTuple(args, "s", &strArg)) 14 | return NULL; 15 | 16 | // Print message and return None 17 | PySys_WriteStdout("Hello, %s!\n", strArg); 18 | Py_RETURN_NONE; 19 | } 20 | 21 | //----------------------------------------------------------------------------- 22 | static PyObject *elevation_example(PyObject *self, PyObject *args) 23 | { 24 | // Return an integer 25 | return PyLong_FromLong(21463L); 26 | } 27 | 28 | //----------------------------------------------------------------------------- 29 | static PyMethodDef hello_methods[] = { 30 | { 31 | "hello", 32 | hello_example, 33 | METH_VARARGS, 34 | "Prints back 'Hello ', for example example: hello.hello('you')" 35 | }, 36 | 37 | { 38 | "elevation", 39 | elevation_example, 40 | METH_VARARGS, 41 | "Returns elevation of Nevado Sajama." 42 | }, 43 | {NULL, NULL, 0, NULL} /* Sentinel */ 44 | }; 45 | 46 | //----------------------------------------------------------------------------- 47 | #if PY_MAJOR_VERSION < 3 48 | PyMODINIT_FUNC init_hello(void) 49 | { 50 | (void) Py_InitModule("_hello", hello_methods); 51 | } 52 | #else /* PY_MAJOR_VERSION >= 3 */ 53 | static struct PyModuleDef hello_module_def = { 54 | PyModuleDef_HEAD_INIT, 55 | "_hello", 56 | "Internal \"_hello\" module", 57 | -1, 58 | hello_methods 59 | }; 60 | 61 | PyMODINIT_FUNC PyInit__hello(void) 62 | { 63 | return PyModule_Create(&hello_module_def); 64 | } 65 | #endif /* PY_MAJOR_VERSION >= 3 */ 66 | -------------------------------------------------------------------------------- /projects/hello-cpp/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "scikit-build>=0.13", 5 | "cmake>=3.18", 6 | "ninja", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | -------------------------------------------------------------------------------- /projects/hello-cpp/setup.py: -------------------------------------------------------------------------------- 1 | from skbuild import setup 2 | 3 | setup( 4 | name="hello-cpp", 5 | version="1.2.3", 6 | description="a minimal example package (cpp version)", 7 | author="The scikit-build team", 8 | license="MIT", 9 | packages=["hello"], 10 | python_requires=">=3.9", 11 | ) 12 | -------------------------------------------------------------------------------- /projects/hello-cpp/tests/test_hello_cpp.py: -------------------------------------------------------------------------------- 1 | import hello 2 | 3 | 4 | def test_hello(capfd): 5 | hello.hello("World") 6 | captured = capfd.readouterr() 7 | assert captured.out == "Hello, World!\n" 8 | 9 | 10 | def test_elevation(): 11 | assert hello.elevation() == 21463 12 | -------------------------------------------------------------------------------- /projects/hello-cython/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.26) 2 | 3 | project(hello_cython) 4 | 5 | find_package(PythonExtensions REQUIRED) 6 | find_package(Cython REQUIRED) 7 | 8 | add_subdirectory(hello) 9 | -------------------------------------------------------------------------------- /projects/hello-cython/hello/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | add_cython_target(_hello CXX) 4 | add_library(_hello MODULE ${_hello}) 5 | python_extension_module(_hello) 6 | 7 | install(TARGETS _hello LIBRARY DESTINATION hello) 8 | -------------------------------------------------------------------------------- /projects/hello-cython/hello/__init__.py: -------------------------------------------------------------------------------- 1 | from ._hello import elevation, hello 2 | 3 | __all__ = ("elevation", "hello") 4 | -------------------------------------------------------------------------------- /projects/hello-cython/hello/_hello.pyx: -------------------------------------------------------------------------------- 1 | 2 | cpdef void hello(str strArg): 3 | "Prints back 'Hello ', for example example: hello.hello('you')" 4 | print("Hello, {}!".format(strArg)) 5 | 6 | cpdef long elevation(): 7 | "Returns elevation of Nevado Sajama." 8 | return 21463L 9 | -------------------------------------------------------------------------------- /projects/hello-cython/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "scikit-build>=0.13", 5 | "cmake>=3.18", 6 | "ninja", 7 | "cython", 8 | ] 9 | build-backend = "setuptools.build_meta" 10 | -------------------------------------------------------------------------------- /projects/hello-cython/setup.py: -------------------------------------------------------------------------------- 1 | from skbuild import setup 2 | 3 | setup( 4 | name="hello-cython", 5 | version="1.2.3", 6 | description="a minimal example package (cython version)", 7 | author="The scikit-build team", 8 | license="MIT", 9 | packages=["hello"], 10 | python_requires=">=3.9", 11 | ) 12 | -------------------------------------------------------------------------------- /projects/hello-cython/tests/test_hello_cython.py: -------------------------------------------------------------------------------- 1 | import hello 2 | 3 | 4 | def test_hello(capfd): 5 | hello.hello("World") 6 | captured = capfd.readouterr() 7 | assert captured.out == "Hello, World!\n" 8 | 9 | 10 | def test_elevation(): 11 | assert hello.elevation() == 21463 12 | -------------------------------------------------------------------------------- /projects/hello-free-threading/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.29) 2 | 3 | project(FreeComputePi LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 17 CACHE STRING "The C++ standard to use") 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_CXX_EXTENSIONS OFF) 8 | 9 | find_package( 10 | Python 11 | COMPONENTS Interpreter Development.Module 12 | REQUIRED) 13 | 14 | python_add_library(_core MODULE src/freecomputepi/_core.cpp WITH_SOABI) 15 | 16 | find_package(pybind11 REQUIRED) 17 | pybind11_add_module(_pybcore MODULE src/freecomputepi/_pybcore.cpp) 18 | 19 | install(TARGETS _core _pybcore DESTINATION freecomputepi) 20 | -------------------------------------------------------------------------------- /projects/hello-free-threading/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["scikit-build-core >=0.9.5", "pybind11 >=2.13.1"] 3 | build-backend = "scikit_build_core.build" 4 | 5 | [project] 6 | name = "freecomputepi" 7 | version = "0.0.1" 8 | requires-python = ">=3.13" 9 | 10 | [tool.cibuildwheel] 11 | build-frontend = "build[uv]" 12 | build = "cp313*" 13 | free-threaded-support = true 14 | test-command = "pytest {package} --durations=0" 15 | test-requires = ["pytest"] 16 | -------------------------------------------------------------------------------- /projects/hello-free-threading/src/freecomputepi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-build/scikit-build-sample-projects/9cb9bd4e3bbe0f5d9fdadaf7a4a621a5abc66411/projects/hello-free-threading/src/freecomputepi/__init__.py -------------------------------------------------------------------------------- /projects/hello-free-threading/src/freecomputepi/_core.cpp: -------------------------------------------------------------------------------- 1 | #define PY_SSIZE_T_CLEAN 2 | #include 3 | 4 | #include 5 | 6 | 7 | static PyObject * pi(PyObject *self, PyObject *arg) { 8 | int n = PyLong_AsLong(arg); 9 | if (n == -1 && PyErr_Occurred()) { 10 | return NULL; 11 | } 12 | double sum = 0.0; 13 | 14 | std::random_device r; 15 | std::default_random_engine e1(r()); 16 | std::uniform_real_distribution uniform_dist(-1, 1); 17 | 18 | for (int i = 0; i < n; i++) { 19 | double x = uniform_dist(e1); 20 | double y = uniform_dist(e1); 21 | if (x * x + y * y <= 1.0) { 22 | sum += 1.0; 23 | } 24 | } 25 | 26 | return Py_BuildValue("d", 4.0 * sum / n); 27 | } 28 | 29 | extern "C" { 30 | 31 | static PyMethodDef core_methods[] = { 32 | {"pi", pi, METH_O, "Compute pi"}, 33 | {NULL, NULL, 0, NULL} /* Sentinel */ 34 | }; 35 | 36 | static int _core_exec(PyObject *m) { 37 | if (PyModule_AddFunctions(m, core_methods) < 0) 38 | return -1; 39 | return 0; 40 | } 41 | 42 | static PyModuleDef_Slot module_slots[] = { 43 | {Py_mod_exec, (void*) _core_exec}, 44 | {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, 45 | #ifdef Py_GIL_DISABLED 46 | {Py_mod_gil, Py_MOD_GIL_NOT_USED}, 47 | #endif 48 | {0, NULL}, 49 | }; 50 | 51 | static struct PyModuleDef coremodule = { 52 | PyModuleDef_HEAD_INIT, 53 | "_core", 54 | NULL, 55 | 0, 56 | core_methods, 57 | module_slots, 58 | NULL, 59 | NULL, 60 | NULL, 61 | }; 62 | 63 | 64 | PyMODINIT_FUNC PyInit__core(void) { 65 | return PyModuleDef_Init(&coremodule); 66 | } 67 | 68 | }; 69 | -------------------------------------------------------------------------------- /projects/hello-free-threading/src/freecomputepi/_pybcore.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace py = pybind11; 5 | 6 | double pi(int n) { 7 | double sum = 0.0; 8 | 9 | std::random_device r; 10 | std::default_random_engine e1(r()); 11 | std::uniform_real_distribution uniform_dist(-1, 1); 12 | 13 | for (int i = 0; i < n; i++) { 14 | double x = uniform_dist(e1); 15 | double y = uniform_dist(e1); 16 | if (x * x + y * y <= 1.0) { 17 | sum += 1.0; 18 | } 19 | } 20 | return 4.0 * sum / n; 21 | } 22 | 23 | PYBIND11_MODULE(_pybcore, m, py::mod_gil_not_used()) { 24 | m.def("pi", &pi); 25 | } 26 | -------------------------------------------------------------------------------- /projects/hello-free-threading/src/freecomputepi/comp.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import statistics 3 | import time 4 | from concurrent.futures import ThreadPoolExecutor 5 | 6 | from ._core import pi 7 | 8 | 9 | def pi_in_threads(threads: int, trials: int) -> float: 10 | if threads == 0: 11 | return pi(trials) 12 | with ThreadPoolExecutor(max_workers=threads) as executor: 13 | return statistics.mean(executor.map(pi, [trials // threads] * threads)) 14 | 15 | 16 | def main() -> None: 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument("--threads", type=int, default=0) 19 | parser.add_argument("--trials", type=int, default=100_000_000) 20 | args = parser.parse_args() 21 | 22 | start = time.monotonic() 23 | π = pi_in_threads(args.threads, args.trials) 24 | stop = time.monotonic() 25 | 26 | print(f"{args.trials} trials, {π = }, {stop - start:.4} s") 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /projects/hello-free-threading/src/freecomputepi/pure.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import random 3 | import statistics 4 | import time 5 | from concurrent.futures import ThreadPoolExecutor 6 | 7 | 8 | def pi(trials: int) -> float: 9 | Ncirc = 0 10 | ran = random.Random() 11 | 12 | for _ in range(trials): 13 | x = ran.uniform(-1, 1) 14 | y = ran.uniform(-1, 1) 15 | 16 | test = x * x + y * y 17 | if test <= 1: 18 | Ncirc += 1 19 | 20 | return 4.0 * (Ncirc / trials) 21 | 22 | 23 | def pi_in_threads(threads: int, trials: int) -> float: 24 | if threads == 0: 25 | return pi(trials) 26 | with ThreadPoolExecutor(max_workers=threads) as executor: 27 | return statistics.mean(executor.map(pi, [trials // threads] * threads)) 28 | 29 | 30 | def main() -> None: 31 | parser = argparse.ArgumentParser() 32 | parser.add_argument("--threads", type=int, default=0) 33 | parser.add_argument("--trials", type=int, default=10_000_000) 34 | args = parser.parse_args() 35 | 36 | start = time.monotonic() 37 | π = pi_in_threads(args.threads, args.trials) 38 | stop = time.monotonic() 39 | 40 | print(f"{args.trials} trials, {π = }, {stop - start:.4} s") 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /projects/hello-free-threading/src/freecomputepi/pybind.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import statistics 3 | import time 4 | from concurrent.futures import ThreadPoolExecutor 5 | 6 | from ._pybcore import pi 7 | 8 | 9 | def pi_in_threads(threads: int, trials: int) -> float: 10 | if threads == 0: 11 | return pi(trials) 12 | with ThreadPoolExecutor(max_workers=threads) as executor: 13 | return statistics.mean(executor.map(pi, [trials // threads] * threads)) 14 | 15 | 16 | def main() -> None: 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument("--threads", type=int, default=0) 19 | parser.add_argument("--trials", type=int, default=100_000_000) 20 | args = parser.parse_args() 21 | 22 | start = time.monotonic() 23 | π = pi_in_threads(args.threads, args.trials) 24 | stop = time.monotonic() 25 | 26 | print(f"{args.trials} trials, {π = }, {stop - start:.4} s") 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /projects/hello-free-threading/tests/test_thread.py: -------------------------------------------------------------------------------- 1 | import freecomputepi.comp 2 | import freecomputepi.pure 3 | import freecomputepi.pybind 4 | import pytest 5 | 6 | 7 | @pytest.mark.parametrize("threads", [0, 1, 2, 4]) 8 | def test_pure(threads): 9 | π = freecomputepi.pure.pi_in_threads(threads, 10_000_000) 10 | assert π == pytest.approx(3.1415926535, rel=0.01) 11 | 12 | 13 | @pytest.mark.parametrize("threads", [0, 1, 2, 4]) 14 | def test_comp(threads): 15 | π = freecomputepi.comp.pi_in_threads(threads, 100_000_000) 16 | assert π == pytest.approx(3.1415926535, rel=0.01) 17 | 18 | 19 | @pytest.mark.parametrize("threads", [0, 1, 2, 4]) 20 | def test_pybind(threads): 21 | π = freecomputepi.pybind.pi_in_threads(threads, 100_000_000) 22 | assert π == pytest.approx(3.1415926535, rel=0.01) 23 | -------------------------------------------------------------------------------- /projects/hello-pure/hello/__init__.py: -------------------------------------------------------------------------------- 1 | def hello(txt): 2 | print(f"Hello, {txt}!") 3 | 4 | 5 | def elevation(): 6 | """Returns elevation of Nevado Sajama.""" 7 | return 21463 8 | -------------------------------------------------------------------------------- /projects/hello-pure/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "scikit-build>=0.13", 5 | "cmake>=3.18", 6 | "ninja", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | -------------------------------------------------------------------------------- /projects/hello-pure/setup.py: -------------------------------------------------------------------------------- 1 | from skbuild import setup 2 | 3 | setup( 4 | name="hello-pure", 5 | version="1.2.3", 6 | description="a minimal example package (pure python version)", 7 | author="The scikit-build team", 8 | license="MIT", 9 | packages=["hello"], 10 | python_requires=">=3.9", 11 | ) 12 | -------------------------------------------------------------------------------- /projects/hello-pure/test_hello_pure.py: -------------------------------------------------------------------------------- 1 | import hello 2 | 3 | 4 | def test_hello(capfd): 5 | hello.hello("World") 6 | captured = capfd.readouterr() 7 | assert captured.out == "Hello, World!\n" 8 | 9 | 10 | def test_elevation(): 11 | assert hello.elevation() == 21463 12 | -------------------------------------------------------------------------------- /projects/hello-pybind11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.26) 2 | 3 | project(hello-pybind11 VERSION "0.1") 4 | 5 | # Define CMAKE_INSTALL_xxx: LIBDIR, INCLUDEDIR 6 | include(GNUInstallDirs) 7 | include(FetchContent) 8 | 9 | FetchContent_Declare( 10 | pybind11 11 | URL https://github.com/pybind/pybind11/archive/refs/tags/v2.13.6.tar.gz 12 | URL_HASH SHA256=e08cb87f4773da97fa7b5f035de8763abc656d87d5773e62f6da0587d1f0ec20 13 | ) 14 | FetchContent_MakeAvailable(pybind11) 15 | 16 | set(python_module_name _hello) 17 | pybind11_add_module(${python_module_name} MODULE 18 | src/hello/hello_py.cpp 19 | ) 20 | 21 | install(TARGETS ${python_module_name} DESTINATION .) 22 | -------------------------------------------------------------------------------- /projects/hello-pybind11/README.md: -------------------------------------------------------------------------------- 1 | # PyBind11 + Scikit Build example 2 | 3 | 4 | ## Building 5 | 6 | To build, you must have pip 10 or greater, *or* you need to manually install 7 | `scikit-build` and `cmake`. Once you create a wheel, that wheel can be used in 8 | earlier versions of pip. 9 | 10 | Example build and install sequence: 11 | 12 | ```bash 13 | pip install . 14 | python -c "import hello; hello.hello()" 15 | ``` 16 | 17 | This should print "Hello, World!". 18 | 19 | ## Testing 20 | 21 | Testing is managed by tox. This will build the package in a temp directory and runs the tests in the test dir. 22 | 23 | ```shell 24 | tox 25 | ``` 26 | -------------------------------------------------------------------------------- /projects/hello-pybind11/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | "scikit-build>=0.13", 5 | "cmake", 6 | "ninja", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | 10 | [tool.pytest.ini_options] 11 | testpaths = ["tests"] 12 | -------------------------------------------------------------------------------- /projects/hello-pybind11/setup.py: -------------------------------------------------------------------------------- 1 | from skbuild import setup 2 | 3 | setup( 4 | name="hello-pybind11", 5 | version="1.2.3", 6 | description="a minimal example package (with pybind11)", 7 | author="Pablo Hernandez-Cerdan", 8 | license="MIT", 9 | packages=["hello"], 10 | package_dir={"": "src"}, 11 | cmake_install_dir="src/hello", 12 | python_requires=">=3.9", 13 | ) 14 | -------------------------------------------------------------------------------- /projects/hello-pybind11/src/hello/__init__.py: -------------------------------------------------------------------------------- 1 | from ._hello import hello, return_two 2 | 3 | __all__ = ("hello", "return_two") 4 | -------------------------------------------------------------------------------- /projects/hello-pybind11/src/hello/hello_py.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace py = pybind11; 5 | 6 | void hello() { 7 | std::cout << "Hello, World!" << std::endl; 8 | } 9 | 10 | int return_two() { 11 | return 2; 12 | } 13 | 14 | PYBIND11_MODULE(_hello, m) { 15 | m.doc() = "_hello"; 16 | m.def("hello", &hello, "Prints \"Hello, World!\""); 17 | m.def("return_two", &return_two, "Returns 2"); 18 | } 19 | -------------------------------------------------------------------------------- /projects/hello-pybind11/tests/test_hello_pybind11.py: -------------------------------------------------------------------------------- 1 | # This kind of import is automatically done when importing hello from outside 2 | import unittest 3 | 4 | import hello 5 | 6 | 7 | class TestHello(unittest.TestCase): 8 | def test_hello(self): 9 | hello.hello() 10 | 11 | def test_return_two(self): 12 | self.assertEqual(hello.return_two(), 2) 13 | 14 | 15 | if __name__ == "__main__": 16 | unittest.main() 17 | # You can run all python test with: 18 | # ctest -R python -V 19 | # from the build folder 20 | -------------------------------------------------------------------------------- /projects/hello-pybind11/tox.ini: -------------------------------------------------------------------------------- 1 | [testenv] 2 | commands = python -m unittest discover -s tests/ 3 | -------------------------------------------------------------------------------- /projects/pen2-cython/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.26) 2 | 3 | project(pen2_cython) 4 | 5 | enable_testing() 6 | 7 | find_package(PythonExtensions REQUIRED) 8 | find_package(Cython REQUIRED) 9 | 10 | add_subdirectory(src) 11 | add_subdirectory(scripts) 12 | -------------------------------------------------------------------------------- /projects/pen2-cython/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "scikit-build>=0.13", 5 | "cmake>=3.18", 6 | "ninja", 7 | "cython", 8 | ] 9 | build-backend = "setuptools.build_meta" 10 | -------------------------------------------------------------------------------- /projects/pen2-cython/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | cython 2 | skbuild 3 | -------------------------------------------------------------------------------- /projects/pen2-cython/scripts/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | install(FILES pen2_cython DESTINATION scripts) 3 | file(COPY pen2_cython DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 4 | 5 | add_test(NAME pen2_cython 6 | COMMAND ${PYTHON_EXECUTABLE} 7 | "${CMAKE_INSTALL_PREFIX}/scripts/pen2_cython" -d 2) 8 | set_property(TEST pen2_cython PROPERTY ENVIRONMENT 9 | "PYTHONPATH=${CMAKE_SOURCE_DIR}/src${PYTHON_PATH_SEPARATOR}${CMAKE_BINARY_DIR}") 10 | -------------------------------------------------------------------------------- /projects/pen2-cython/scripts/pen2_cython: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from pen2_cython import main 4 | 5 | main() 6 | -------------------------------------------------------------------------------- /projects/pen2-cython/setup.py: -------------------------------------------------------------------------------- 1 | from skbuild import setup 2 | 3 | setup( 4 | name="pen2_cython", 5 | version="0.0.1", 6 | description="double pendulum simulation (cython version)", 7 | author="The scikit-build team", 8 | license="MIT", 9 | packages=["pen2_cython"], 10 | package_dir={"pen2_cython": "src"}, 11 | scripts=["scripts/pen2_cython"], 12 | python_requires=">=3.9", 13 | ) 14 | -------------------------------------------------------------------------------- /projects/pen2-cython/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # add_cython_target(_pen2) 3 | add_cython_target(_pen2 OUTPUT_VAR X) 4 | add_library(_pen2 MODULE ${X}) 5 | python_extension_module(_pen2) 6 | 7 | install(TARGETS _pen2 LIBRARY DESTINATION src) 8 | install(FILES __main__.py DESTINATION src) 9 | install(FILES __init__.py DESTINATION src) 10 | -------------------------------------------------------------------------------- /projects/pen2-cython/src/__init__.py: -------------------------------------------------------------------------------- 1 | from math import pi 2 | 3 | from _pen2 import Pen2Sim 4 | 5 | DEFAULT_M0 = 1.0 6 | DEFAULT_M1 = 1.2 7 | DEFAULT_L0 = 1.00 8 | DEFAULT_L1 = 0.75 9 | DEFAULT_ITH0 = 0.001 * int(1000 * pi) 10 | DEFAULT_ITH1 = DEFAULT_ITH0 - 0.001 11 | DEFAULT_IW0 = 0.0 12 | DEFAULT_IW1 = 0.0 13 | # NOTE(opadron): Simulation seems to go in slow motion with the standard gravity 14 | # constant, so just use a default at 10X strength. 15 | DEFAULT_G = 98.1 16 | DEFAULT_DT = 0.001 * int(1000.0 / 60.0) 17 | DEFAULT_DURATION = None 18 | 19 | 20 | def main(): 21 | from argparse import ArgumentParser 22 | 23 | parser = ArgumentParser(description="run a double pendulum simulation") 24 | parser.add_argument( 25 | "--mass0", 26 | "-m0", 27 | type=float, 28 | default=DEFAULT_M0, 29 | metavar="M0", 30 | help="Mass of the first pendulum weight (kg).", 31 | ) 32 | parser.add_argument( 33 | "--mass1", 34 | "-m1", 35 | type=float, 36 | default=DEFAULT_M1, 37 | metavar="M1", 38 | help="Mass of the second pendulum weight (kg).", 39 | ) 40 | parser.add_argument( 41 | "--length0", 42 | "-L0", 43 | type=float, 44 | default=DEFAULT_L0, 45 | metavar="L0", 46 | help="Length of the first pendulum arm (m).", 47 | ) 48 | parser.add_argument( 49 | "--length1", 50 | "-L1", 51 | type=float, 52 | default=DEFAULT_L1, 53 | metavar="L1", 54 | help="Length of the second pendulum arm (m).", 55 | ) 56 | parser.add_argument( 57 | "--theta0", 58 | "-th0", 59 | type=float, 60 | default=DEFAULT_ITH0, 61 | metavar="TH0", 62 | help=( 63 | "Initial angle of the first pendulum arm (radians). " 64 | "Zero (0.0) is pointing straight down." 65 | ), 66 | ) 67 | parser.add_argument( 68 | "--theta1", 69 | "-th1", 70 | type=float, 71 | default=DEFAULT_ITH1, 72 | metavar="TH0", 73 | help=( 74 | "Initial angle of the second pendulum arm (radians). " 75 | "Zero (0.0) is pointing parallel to the first arm." 76 | ), 77 | ) 78 | parser.add_argument( 79 | "--omega0", 80 | "-w0", 81 | type=float, 82 | default=DEFAULT_IW0, 83 | metavar="W0", 84 | help="Initial anglular velocity of the first pendulum arm (radians/s).", 85 | ) 86 | parser.add_argument( 87 | "--omega1", 88 | "-w1", 89 | type=float, 90 | default=DEFAULT_IW1, 91 | metavar="W1", 92 | help=("Initial anglular velocity of the second pendulum arm (radians/s)."), 93 | ) 94 | parser.add_argument( 95 | "--gravity", 96 | "-g", 97 | type=float, 98 | default=DEFAULT_G, 99 | metavar="G", 100 | help=( 101 | "Strength of the constant downward acceleration " 102 | "felt by both pendulum weights due to gravity (m/s^2)." 103 | ), 104 | ) 105 | parser.add_argument( 106 | "--time_step", 107 | "-dt", 108 | type=float, 109 | default=DEFAULT_DT, 110 | metavar="DT", 111 | help="Simulation time step size (s).", 112 | ) 113 | parser.add_argument( 114 | "--duration", 115 | "-d", 116 | type=float, 117 | default=DEFAULT_DURATION, 118 | metavar="D", 119 | help="Simulation duration (s).", 120 | ) 121 | 122 | args = parser.parse_args() 123 | run_simulation( 124 | mass0=args.mass0, 125 | mass1=args.mass1, 126 | length0=args.length0, 127 | length1=args.length1, 128 | theta0=args.theta0, 129 | theta1=args.theta1, 130 | omega0=args.omega0, 131 | omega1=args.omega1, 132 | gravity=args.gravity, 133 | time_step=args.time_step, 134 | duration=args.duration, 135 | ) 136 | 137 | 138 | def run_simulation(**kwds): 139 | from math import pi, sqrt 140 | 141 | from vtk import ( 142 | vtkActor, 143 | vtkCylinderSource, 144 | vtkInteractorStyleTerrain, 145 | vtkPolyDataMapper, 146 | vtkRenderer, 147 | vtkRenderWindow, 148 | vtkRenderWindowInteractor, 149 | vtkSphereSource, 150 | vtkTransform, 151 | ) 152 | from vtk.util.colors import blue, brown, red 153 | 154 | M0 = kwds.get("mass0", DEFAULT_M0) 155 | M1 = kwds.get("mass1", DEFAULT_M1) 156 | L0 = kwds.get("length0", DEFAULT_L0) 157 | L1 = kwds.get("length1", DEFAULT_L1) 158 | iTH0 = kwds.get("theta0", DEFAULT_ITH0) 159 | iTH1 = kwds.get("theta1", DEFAULT_ITH1) 160 | iW0 = kwds.get("omega0", DEFAULT_IW0) 161 | iW1 = kwds.get("omega1", DEFAULT_IW1) 162 | G = kwds.get("gravity", DEFAULT_G) 163 | dt = kwds.get("time_step", DEFAULT_DT) 164 | duration = kwds.get("duration", DEFAULT_DURATION) 165 | 166 | sim = Pen2Sim(iTH0, iTH1, iW0, iW1, M0, M1, L0, L1, G, dt) 167 | 168 | sphere0 = vtkSphereSource() 169 | sphere1 = vtkSphereSource() 170 | 171 | cylinder0 = vtkCylinderSource() 172 | cylinder1 = vtkCylinderSource() 173 | 174 | sphere0.SetCenter(0.0, 0.0, 0.0) 175 | sphere1.SetCenter(0.0, 0.0, 0.0) 176 | 177 | sphere0.SetRadius(0.25 * sqrt(M0 / pi)) 178 | sphere1.SetRadius(0.25 * sqrt(M1 / pi)) 179 | 180 | cylinder0.SetRadius(0.04) 181 | cylinder1.SetRadius(0.04) 182 | 183 | cTran0 = vtkTransform() 184 | cTran1 = vtkTransform() 185 | 186 | sMap0 = vtkPolyDataMapper() 187 | sMap1 = vtkPolyDataMapper() 188 | 189 | cMap0 = vtkPolyDataMapper() 190 | cMap1 = vtkPolyDataMapper() 191 | 192 | sMap0.SetInputConnection(sphere0.GetOutputPort()) 193 | sMap1.SetInputConnection(sphere1.GetOutputPort()) 194 | 195 | cMap0.SetInputConnection(cylinder0.GetOutputPort()) 196 | cMap1.SetInputConnection(cylinder1.GetOutputPort()) 197 | 198 | sAct0 = vtkActor() 199 | sAct1 = vtkActor() 200 | sAct0.SetMapper(sMap0) 201 | sAct1.SetMapper(sMap1) 202 | sAct0.GetProperty().SetColor(red) 203 | sAct1.GetProperty().SetColor(blue) 204 | 205 | cAct0 = vtkActor() 206 | cAct1 = vtkActor() 207 | cAct0.SetMapper(cMap0) 208 | cAct1.SetMapper(cMap1) 209 | cAct0.GetProperty().SetColor(brown) 210 | cAct1.GetProperty().SetColor(brown) 211 | 212 | renderer = vtkRenderer() 213 | renderWindow = vtkRenderWindow() 214 | renderWindow.AddRenderer(renderer) 215 | 216 | interactor = vtkRenderWindowInteractor() 217 | interactor.SetInteractorStyle(vtkInteractorStyleTerrain()) 218 | interactor.SetRenderWindow(renderWindow) 219 | 220 | renderer.AddActor(sAct0) 221 | renderer.AddActor(sAct1) 222 | renderer.AddActor(cAct0) 223 | renderer.AddActor(cAct1) 224 | renderer.SetBackground(0.1, 0.2, 0.4) 225 | renderer.GetActiveCamera().Dolly(0.125) 226 | 227 | renderWindow.SetSize(800, 600) 228 | 229 | renderWindow.Render() 230 | interactor.Initialize() 231 | 232 | state = [None] 233 | 234 | def callback(*args, **kwds): 235 | if state[0] is None: 236 | state[0] = list(sim.initial_state()) 237 | cylinder0.SetHeight(state[0][5]) 238 | cylinder1.SetHeight(state[0][6]) 239 | 240 | else: 241 | state[0][:] = sim.advance() 242 | 243 | x0, x1, y0, y1, t, L0, L1, th0, th1 = state[0][:9] 244 | 245 | cTran0.Identity() 246 | cTran1.Identity() 247 | 248 | cTran1.Translate(x0, y0, 0.0) 249 | 250 | cTran0.RotateZ(180.0 * th0 / pi) 251 | cTran1.RotateZ(180.0 * th1 / pi) 252 | 253 | cTran0.Translate(0.0, -0.5 * L0, 0.0) 254 | cTran1.Translate(0.0, -0.5 * L1, 0.0) 255 | 256 | cAct0.SetUserTransform(cTran0) 257 | cAct1.SetUserTransform(cTran1) 258 | 259 | sAct0.SetPosition(x0, y0, 0.0) 260 | sAct1.SetPosition(x1, y1, 0.0) 261 | renderWindow.Render() 262 | 263 | if duration is not None and t >= duration: 264 | interactor.RemoveObserver(timerid) 265 | interactor.ExitCallback() 266 | 267 | interactor.AddObserver("TimerEvent", callback) 268 | timerid = interactor.CreateRepeatingTimer(int(1000 * dt)) 269 | 270 | interactor.Start() 271 | -------------------------------------------------------------------------------- /projects/pen2-cython/src/__main__.py: -------------------------------------------------------------------------------- 1 | if __name__ == "__main__": 2 | from . import main 3 | 4 | main() 5 | -------------------------------------------------------------------------------- /projects/pen2-cython/src/_pen2.pyx: -------------------------------------------------------------------------------- 1 | 2 | #cython: boundscheck=False 3 | #cython: cdivision=True 4 | #cython: cdivision_warnings=True 5 | #cython: profile=True 6 | 7 | from libc.math cimport sin, cos 8 | 9 | cdef class Pen2Sim: 10 | cdef: 11 | double th0, th1, w0, w1 12 | double M0, M1, L0, L1, G, dt, t 13 | double th0_dot, th1_dot, w0_dot, w1_dot 14 | 15 | double U_th0, U_th1, U_w0, U_w1 16 | double k0_th0, k0_th1, k0_w0, k0_w1 17 | double k1_th0, k1_th1, k1_w0, k1_w1 18 | double k2_th0, k2_th1, k2_w0, k2_w1 19 | double k3_th0, k3_th1, k3_w0, k3_w1 20 | 21 | double Ix0, Ix1, Iy0, Iy1, Ith0, Ith1 22 | 23 | def __init__(self, double th0, double th1, 24 | double w0, double w1, 25 | double M0, double M1, 26 | double L0, double L1, 27 | double G, double dt): 28 | self.th0 = th0 29 | self.th1 = th1 30 | self.w0 = w0 31 | self.w1 = w1 32 | self.M0 = M0 33 | self.M1 = M1 34 | self.L0 = L0 35 | self.L1 = L1 36 | self.G = G 37 | self.dt = dt 38 | self.t = 0.0 39 | 40 | self.Ith0 = self.th0 41 | self.Ith1 = self.th1 42 | 43 | self.Ix0 = self.L0*sin(self.Ith0) 44 | self.Ix1 = self.Ix0 + self.L1*sin(self.Ith1) 45 | 46 | self.Iy0 = -self.L0*cos(self.Ith0) 47 | self.Iy1 = self.Iy0 - self.L1*cos(self.Ith1) 48 | 49 | def initial_state(self): 50 | return (self.Ix0, self.Ix1, self.Iy0, self.Iy1, 0.0, self.L0, self.L1, self.Ith0, self.Ith1) 51 | 52 | cdef void eval_rhs(self): 53 | cdef: 54 | double th0 = self.U_th0 55 | double th1 = self.U_th1 56 | double w0 = self.U_w0 57 | double w1 = self.U_w1 58 | double M0 = self.M0 59 | double M1 = self.M1 60 | double L0 = self.L0 61 | double L1 = self.L1 62 | double G = self.G 63 | 64 | self.th0_dot = w0 65 | self.th1_dot = w1 66 | 67 | self.w0_dot = -( 68 | 4.0*G *M0 *sin( th0 ) 69 | + 3.0*G *M1 *sin( th0 ) 70 | + G *M1 *sin( th0 - 2.0*th1) 71 | + L0*M1*(w0**2)*sin(2.0*th0 - 2.0*th1) 72 | + 4.0*L1*M1*(w1**2)*sin( th0 - th1) 73 | )/( 74 | 2.0*L0*(4.0*M0 - M1*cos(th0 - th1)**2 + 2.0*M1) 75 | ) 76 | 77 | self.w1_dot = ( 78 | -(2.0*M0 + M1)*( 79 | G *sin( th1) 80 | - L0*(w0**2)*sin(th0 - th1) 81 | ) 82 | 83 | + cos(th0 - th1)*( 84 | G *M0 *sin(th0 ) 85 | + G *M1 *sin(th0 ) 86 | + L1*M1*(w1**2)*sin(th0 - th1) 87 | ) 88 | )/( 89 | L1*( 90 | 4.0*M0 91 | - M1*cos(th0 - th1)**2 92 | + 2.0*M1 93 | ) 94 | ) 95 | 96 | cdef simulate(self): 97 | # classical runge-kutta 98 | self.U_th0 = self.th0 99 | self.U_th1 = self.th1 100 | self.U_w0 = self.w0 101 | self.U_w1 = self.w1 102 | self.eval_rhs() 103 | self.k0_th0 = self.th0_dot 104 | self.k0_th1 = self.th1_dot 105 | self.k0_w0 = self.w0_dot 106 | self.k0_w1 = self.w1_dot 107 | 108 | self.U_th0 = self.th0 + 0.5*self.dt*self.k0_th0 109 | self.U_th1 = self.th1 + 0.5*self.dt*self.k0_th1 110 | self.U_w0 = self.w0 + 0.5*self.dt*self.k0_w0 111 | self.U_w1 = self.w1 + 0.5*self.dt*self.k0_w1 112 | self.eval_rhs() 113 | self.k1_th0 = self.th0_dot 114 | self.k1_th1 = self.th1_dot 115 | self.k1_w0 = self.w0_dot 116 | self.k1_w1 = self.w1_dot 117 | 118 | self.U_th0 = self.th0 + 0.5*self.dt*self.k1_th0 119 | self.U_th1 = self.th1 + 0.5*self.dt*self.k1_th1 120 | self.U_w0 = self.w0 + 0.5*self.dt*self.k1_w0 121 | self.U_w1 = self.w1 + 0.5*self.dt*self.k1_w1 122 | self.eval_rhs() 123 | self.k2_th0 = self.th0_dot 124 | self.k2_th1 = self.th1_dot 125 | self.k2_w0 = self.w0_dot 126 | self.k2_w1 = self.w1_dot 127 | 128 | self.U_th0 = self.th0 + self.dt*self.k2_th0 129 | self.U_th1 = self.th1 + self.dt*self.k2_th1 130 | self.U_w0 = self.w0 + self.dt*self.k2_w0 131 | self.U_w1 = self.w1 + self.dt*self.k2_w1 132 | self.eval_rhs() 133 | self.k3_th0 = self.th0_dot 134 | self.k3_th1 = self.th1_dot 135 | self.k3_w0 = self.w0_dot 136 | self.k3_w1 = self.w1_dot 137 | 138 | cdef double dth0, dth1, dw0, dw1 139 | dth0 = self.k0_th0 + 2.0*(self.k1_th0 + self.k2_th0) + self.k3_th0 140 | dth1 = self.k0_th1 + 2.0*(self.k1_th1 + self.k2_th1) + self.k3_th1 141 | dw0 = self.k0_w0 + 2.0*(self.k1_w0 + self.k2_w0 ) + self.k3_w0 142 | dw1 = self.k0_w1 + 2.0*(self.k1_w1 + self.k2_w1 ) + self.k3_w1 143 | 144 | dth0 *= self.dt 145 | dth1 *= self.dt 146 | dw0 *= self.dt 147 | dw1 *= self.dt 148 | 149 | dth0 /= 6.0 150 | dth1 /= 6.0 151 | dw0 /= 6.0 152 | dw1 /= 6.0 153 | 154 | self.th0 += dth0 155 | self.th1 += dth1 156 | self.w0 += dw0 157 | self.w1 += dw1 158 | 159 | self.t += self.dt 160 | 161 | cdef double x0, x1, y0, y1 162 | 163 | x0 = self.L0*sin(self.th0) 164 | x1 = x0 + self.L1*sin(self.th1) 165 | 166 | y0 = -self.L0*cos(self.th0) 167 | y1 = y0 - self.L1*cos(self.th1) 168 | 169 | return (x0, x1, y0, y1, self.t, self.L0, self.L1, self.th0, self.th1) 170 | 171 | def advance(self): 172 | return self.simulate() 173 | -------------------------------------------------------------------------------- /projects/pi-fortran/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17.2...3.26) 2 | 3 | project(pi 4 | VERSION 1.0.1 5 | DESCRIPTION "pi estimator" 6 | LANGUAGES C Fortran 7 | ) 8 | 9 | find_package(Python REQUIRED COMPONENTS Interpreter Development.Module NumPy) 10 | 11 | # F2PY headers 12 | execute_process( 13 | COMMAND "${Python_EXECUTABLE}" 14 | -c "import numpy.f2py; print(numpy.f2py.get_include())" 15 | OUTPUT_VARIABLE F2PY_INCLUDE_DIR 16 | OUTPUT_STRIP_TRAILING_WHITESPACE 17 | ) 18 | 19 | set(f2py_module_name "pi") 20 | set(fortran_src_file "${CMAKE_SOURCE_DIR}/pi/_pi.f") 21 | set(f2py_module_c "${f2py_module_name}module.c") 22 | 23 | add_custom_command( 24 | OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_c}" 25 | COMMAND ${PYTHON_EXECUTABLE} -m "numpy.f2py" 26 | "${fortran_src_file}" 27 | "${CMAKE_SOURCE_DIR}/pi/pi.pyf" # Must include custom .pyf file 28 | -m "pi" 29 | --lower # Important 30 | DEPENDS pi/_pi.f # Fortran source 31 | ) 32 | 33 | python_add_library(${CMAKE_PROJECT_NAME} MODULE 34 | "${f2py_module_name}module.c" 35 | "${F2PY_INCLUDE_DIR}/fortranobject.c" 36 | "${fortran_src_file}" WITH_SOABI) 37 | 38 | target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC 39 | ${F2PY_INCLUDE_DIR} 40 | ) 41 | 42 | target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Python::NumPy) 43 | 44 | install(TARGETS ${CMAKE_PROJECT_NAME} DESTINATION pi) 45 | -------------------------------------------------------------------------------- /projects/pi-fortran/README.md: -------------------------------------------------------------------------------- 1 | # Pi 2 | 3 | This is an example project demonstrating the use of scikit-build for distributing a standalone FORTRAN library, *pi*; 4 | a CMake package for that library; and a Python wrapper implemented in f2py. 5 | 6 | The example assume some familiarity with CMake and f2py, only really going into detail on the scikit-build parts. 7 | 8 | To install the package run in the project directory 9 | 10 | ```bash 11 | pip install . 12 | ``` 13 | 14 | To run the Python tests, first install the package, then in the project directory run 15 | 16 | ```bash 17 | pytest 18 | ``` 19 | 20 | This is slightly modified from the example in the [numpy docs](https://numpy.org/devdocs/f2py/buildtools/skbuild.html), but we are using Monte Carlo to estimate the value of $\pi$. 21 | 22 | A few surprises: 23 | 1. The dreaded underscore problem has a way of cropping up. One solution is explicitly writing out the interface in a [signature (`.pyf`) file](https://numpy.org/devdocs/f2py/signature-file.html). 24 | 2. The module will require numpy to work. 25 | 3. Between failed builds, it is best to clear out the `_skbuild` folder. 26 | -------------------------------------------------------------------------------- /projects/pi-fortran/pi/_pi.f: -------------------------------------------------------------------------------- 1 | subroutine estimate_pi(pi_estimated, num_trials) 2 | C Estimate pi using Monte Carlo 3 | integer num_trials, num_in_circle 4 | real*8 pi_estimated, x, y 5 | Cf2py intent(in) num_trials 6 | Cf2py intent(out) pi_estimated 7 | 8 | num_in_circle = 0d0 9 | do i = 1, num_trials 10 | call random_number(x) 11 | call random_number(y) 12 | if (x**2 + y**2 < 1.0d0) then 13 | num_in_circle = num_in_circle + 1 14 | endif 15 | end do 16 | pi_estimated = (4d0 * num_in_circle) / num_trials 17 | end subroutine estimate_pi 18 | -------------------------------------------------------------------------------- /projects/pi-fortran/pi/pi.pyf: -------------------------------------------------------------------------------- 1 | ! -*- f90 -*- 2 | ! Note: the context of this file is case sensitive. 3 | python module pi 4 | interface 5 | subroutine estimate_pi(pi_estimated,num_trials) ! in pi/_pi.f 6 | real*8 intent(out) :: pi_estimated 7 | integer intent(in) :: num_trials 8 | end subroutine estimate_pi 9 | end interface 10 | end python module pi 11 | -------------------------------------------------------------------------------- /projects/pi-fortran/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "scikit-build-core", 4 | "numpy>=1.21", 5 | ] 6 | build-backend = "scikit_build_core.build" 7 | 8 | [project] 9 | name = "pi-fortran" 10 | version = "1.0.1" 11 | requires-python = ">=3.9" 12 | dependencies = ["numpy>=1.21"] 13 | 14 | [tool.scikit-build] 15 | ninja.version = ">=1.10" 16 | cmake.version = ">=3.17.2" 17 | -------------------------------------------------------------------------------- /projects/pi-fortran/tests/test_pi.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import pytest 4 | from pi import pi 5 | 6 | 7 | def test_estimate_pi(): 8 | pi_est = pi.estimate_pi(1e8) 9 | assert pi_est == pytest.approx(math.pi, rel=0.001) 10 | -------------------------------------------------------------------------------- /projects/tower-of-babel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.26) 2 | 3 | project(tower_of_babel) 4 | 5 | enable_testing() 6 | 7 | find_package(PythonExtensions REQUIRED) 8 | find_package(Cython REQUIRED) 9 | find_package(Boost COMPONENTS python) 10 | 11 | 12 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 13 | 14 | add_cython_target(tbabel_cython_static CXX) 15 | add_cython_target(tbabel_cython_shared CXX) 16 | add_cython_target(tbabel_cython_module CXX) 17 | 18 | 19 | add_library(tbabel_cython_static STATIC ${tbabel_cython_static}) 20 | add_library(tbabel_cython_shared SHARED ${tbabel_cython_shared}) 21 | add_library(tbabel_cython_module MODULE ${tbabel_cython_module}) 22 | 23 | 24 | include_directories(${Boost_INCLUDE_DIRS}) 25 | 26 | 27 | add_library(tbabel_boost_static STATIC tbabel_boost_static.cxx) 28 | add_library(tbabel_boost_shared SHARED tbabel_boost_shared.cxx) 29 | add_library(tbabel_boost_module MODULE tbabel_boost_module.cxx) 30 | 31 | target_link_libraries(tbabel_boost_static ${Boost_LIBRARIES}) 32 | target_link_libraries(tbabel_boost_shared ${Boost_LIBRARIES}) 33 | target_link_libraries(tbabel_boost_module ${Boost_LIBRARIES}) 34 | 35 | 36 | python_extension_module(tbabel_cython_static) 37 | python_extension_module(tbabel_cython_shared) 38 | python_extension_module(tbabel_cython_module) 39 | 40 | python_extension_module(tbabel_boost_static) 41 | python_extension_module(tbabel_boost_shared) 42 | python_extension_module(tbabel_boost_module) 43 | 44 | set(site_packages "${PYTHON_RELATIVE_SITE_PACKAGES_DIR}") 45 | 46 | install(TARGETS tbabel_cython_static DESTINATION lib) 47 | install(TARGETS tbabel_cython_shared DESTINATION lib) 48 | install(TARGETS tbabel_cython_module DESTINATION ${site_packages}) 49 | 50 | install(TARGETS tbabel_boost_static DESTINATION lib) 51 | install(TARGETS tbabel_boost_shared DESTINATION lib) 52 | install(TARGETS tbabel_boost_module DESTINATION ${site_packages}) 53 | 54 | 55 | install(FILES tbabel_cython_static.pxd DESTINATION ${site_packages}) 56 | install(FILES tbabel_cython_shared.pxd DESTINATION ${site_packages}) 57 | install(FILES tbabel_cython_module.pxd DESTINATION ${site_packages}) 58 | 59 | install(FILES tbabel_cython_common.pxi DESTINATION ${site_packages}) 60 | 61 | 62 | install(FILES tbabel_boost_static.h DESTINATION include) 63 | install(FILES tbabel_boost_shared.h DESTINATION include) 64 | install(FILES tbabel_boost_module.h DESTINATION include) 65 | 66 | install(FILES tbabel_boost_common.h DESTINATION include) 67 | 68 | install(FILES tbabel_python.py DESTINATION ${site_packages}) 69 | 70 | file(COPY tbabel_python.py DESTINATION ${CMAKE_BINARY_DIR}) 71 | 72 | add_subdirectory(scripts) 73 | -------------------------------------------------------------------------------- /projects/tower-of-babel/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | cython 2 | skbuild 3 | -------------------------------------------------------------------------------- /projects/tower-of-babel/scripts/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_cython_target(tbabel CXX EMBED_MAIN) 3 | 4 | # generate modules header file 5 | python_modules_header(modules) 6 | include_directories(${modules_INCLUDE_DIRS}) 7 | 8 | # compile and link 9 | get_property(MODS GLOBAL PROPERTY PY_LINKED_MODULES_LIST) 10 | add_executable(tbabel ${tbabel}) 11 | target_link_libraries(tbabel ${MODS}) 12 | python_standalone_executable(tbabel) 13 | 14 | add_test(NAME tbabel COMMAND tbabel 15 | WORKING_DIRECTORY ${CMAKE_BUILD_DIRECTORY}) 16 | 17 | set_property(TEST tbabel PROPERTY ENVIRONMENT 18 | "LD_LIBRARY_PATH=${CMAKE_BUILD_DIRECTORY};DYLD_LIBRARY_PATH=${CMAKE_BUILD_DIRECTORY}") 19 | 20 | install(TARGETS tbabel DESTINATION scripts) 21 | -------------------------------------------------------------------------------- /projects/tower-of-babel/scripts/tbabel.pyx: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | cdef extern from "stdlib.h": 5 | void srand(int) 6 | 7 | cdef extern from "time.h": 8 | long int time(int) 9 | 10 | cdef extern from "modules.h": 11 | pass 12 | 13 | import tbabel_cython_static as tb_cy_static 14 | import tbabel_cython_shared as tb_cy_shared 15 | import tbabel_cython_module as tb_cy_module 16 | 17 | import tbabel_boost_static as tb_boost_static 18 | import tbabel_boost_shared as tb_boost_shared 19 | import tbabel_boost_module as tb_boost_module 20 | 21 | import tbabel_python as tb_python 22 | 23 | def main(): 24 | from argparse import ArgumentParser 25 | 26 | parser = ArgumentParser(description=( 27 | "Integration stress test that tests interoperability between all forms " 28 | "of extension modules and pure python code.")) 29 | 30 | parser.add_argument("--depth", "-d", type=long, 31 | default=2000, help="call stack depth") 32 | args = parser.parse_args() 33 | 34 | srand(time(0)) 35 | 36 | modules = ( 37 | tb_cy_static, 38 | tb_cy_shared, 39 | tb_cy_module, 40 | 41 | tb_boost_static, 42 | tb_boost_shared, 43 | tb_boost_module, 44 | ) 45 | 46 | py_routines = [ x.cycle for x in modules ] 47 | cy_routines = [ x.get_c_handle() for x in modules ] 48 | 49 | py_routines.append(tb_python.cycle) 50 | 51 | sys.setrecursionlimit(long(1.75*args.depth)) 52 | sys.stdout.write("PY -> ") 53 | tb_python.cycle(args.depth, 0, cy_routines, py_routines) 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /projects/tower-of-babel/setup.py: -------------------------------------------------------------------------------- 1 | from skbuild import setup 2 | 3 | setup( 4 | name="tower_of_babel", 5 | version="0.0.1", 6 | description="integration test of skbuild's support across various python" 7 | "module types and code generation technologies", 8 | author="The scikit-build team", 9 | license="MIT", 10 | scripts=["scripts/tbabel"], 11 | python_requires=">=3.9", 12 | ) 13 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_common.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #define CONCAT(X, Y) CONCAT_(X, Y) 5 | #define CONCAT_(X, Y) X##Y 6 | 7 | #define TOSTR(X) TOSTR_(X) 8 | #define TOSTR_(X) #X 9 | 10 | #define TB_CYCLE CONCAT(CONCAT(tb_, TBABEL_MODE_LOWER), _cycle) 11 | #define TB_MODULE CONCAT(tbabel_boost_, TBABEL_MODE_LOWER) 12 | 13 | typedef void (*cy_routine)(unsigned int, unsigned int, 14 | PyObject *, PyObject *); 15 | 16 | extern void TB_CYCLE(unsigned int, unsigned int, PyObject *, PyObject *); 17 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_common_imp.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | void TB_CYCLE(unsigned int N, unsigned int i, 8 | PyObject *_cy_routines, 9 | PyObject *_py_routines) 10 | { 11 | unsigned int nCy = (unsigned int)PyList_Size(_cy_routines); 12 | unsigned int nPy = (unsigned int)PyList_Size(_py_routines); 13 | unsigned int newI = ((unsigned int)rand())%(nCy + nPy); 14 | 15 | PySys_WriteStdout("Boost " TOSTR(TBABEL_MODE) "\n"); 16 | 17 | if(N) 18 | { 19 | if(newI < nCy) 20 | { 21 | PySys_WriteStdout("C -> "); 22 | 23 | PyObject *_addr = PyList_GetItem(_cy_routines, (Py_ssize_t)newI); 24 | unsigned long addr = PyLong_AsUnsignedLong(_addr); 25 | ((cy_routine)((*(void **)(&addr))))( 26 | N - 1, newI, _cy_routines, _py_routines 27 | ); 28 | } 29 | else 30 | { 31 | PySys_WriteStdout("PY -> "); 32 | PyObject *func = PyList_GetItem(_py_routines, (Py_ssize_t)newI - nCy); 33 | PyObject *args = PyTuple_Pack( 34 | 4, 35 | PyLong_FromSize_t((std::size_t)(N - 1)), 36 | PyLong_FromSize_t((std::size_t)newI), 37 | _cy_routines, 38 | _py_routines 39 | ); 40 | 41 | PyObject_Call(func, args, NULL); 42 | 43 | Py_XDECREF(args); 44 | } 45 | } 46 | } 47 | 48 | static void cycle(unsigned int N, unsigned int i, 49 | boost::python::list &_cy_routines, 50 | boost::python::list &_py_routines) 51 | { 52 | TB_CYCLE(N, i, _cy_routines.ptr(), _py_routines.ptr()); 53 | } 54 | 55 | static PyObject *get_c_handle() 56 | { 57 | return PyLong_FromSize_t((std::size_t)(TB_CYCLE)); 58 | } 59 | 60 | BOOST_PYTHON_MODULE(TB_MODULE) 61 | { 62 | using namespace boost::python; 63 | def("cycle", cycle); 64 | def("get_c_handle", get_c_handle); 65 | } 66 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_module.cxx: -------------------------------------------------------------------------------- 1 | 2 | #define TBABEL_MODE MODULE 3 | #define TBABEL_MODE_LOWER module 4 | #include 5 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_module.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _TBABEL_BOOST_MODULE_H 3 | #define _TBABEL_BOOST_MODULE_H 4 | 5 | #define TBABEL_MODE MODULE 6 | #define TBABEL_MODE_LOWER module 7 | #include 8 | 9 | #endif /* !_TBABEL_BOOST_MODULE_H */ 10 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_module.pxd: -------------------------------------------------------------------------------- 1 | 2 | cimport cython 3 | 4 | cdef extern from "tbabel_boost_module.h": 5 | ctypedef void (*cy_routine)( 6 | unsigned int, unsigned int, list, list) 7 | 8 | void cycle "tb_module_cycle"(unsigned int N, unsigned int i, 9 | list cy_routines, list py_routines) 10 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_shared.cxx: -------------------------------------------------------------------------------- 1 | 2 | #define TBABEL_MODE SHARED 3 | #define TBABEL_MODE_LOWER shared 4 | #include 5 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_shared.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _TBABEL_BOOST_SHARED_H 3 | #define _TBABEL_BOOST_SHARED_H 4 | 5 | #define TBABEL_MODE SHARED 6 | #define TBABEL_MODE_LOWER shared 7 | #include 8 | 9 | #endif /* !_TBABEL_BOOST_SHARED_H */ 10 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_shared.pxd: -------------------------------------------------------------------------------- 1 | 2 | cimport cython 3 | 4 | cdef extern from "tbabel_boost_shared.h": 5 | ctypedef void (*cy_routine)( 6 | unsigned int, unsigned int, list, list) 7 | 8 | void cycle "tb_shared_cycle"(unsigned int N, unsigned int i, 9 | list cy_routines, list py_routines) 10 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_static.cxx: -------------------------------------------------------------------------------- 1 | 2 | #define TBABEL_MODE STATIC 3 | #define TBABEL_MODE_LOWER static 4 | #include 5 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_static.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _TBABEL_BOOST_STATIC_H 3 | #define _TBABEL_BOOST_STATIC_H 4 | 5 | #define TBABEL_MODE STATIC 6 | #define TBABEL_MODE_LOWER static 7 | #include 8 | 9 | #endif /* !_TBABEL_BOOST_STATIC_H */ 10 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_boost_static.pxd: -------------------------------------------------------------------------------- 1 | 2 | cimport cython 3 | 4 | cdef extern from "tbabel_boost_static.h": 5 | ctypedef void (*cy_routine)( 6 | unsigned int, unsigned int, list, list) 7 | 8 | void cycle "tb_static_cycle"(unsigned int N, unsigned int i, 9 | list cy_routines, list py_routines) 10 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_cython_common.pxi: -------------------------------------------------------------------------------- 1 | 2 | cimport cython 3 | 4 | ctypedef void (*cy_routine)( 5 | unsigned int, unsigned int, list, list) 6 | 7 | cdef void _cycle(unsigned int N, unsigned int i, 8 | list cy_routines, list py_routines) 9 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_cython_common_imp.pxi: -------------------------------------------------------------------------------- 1 | 2 | # MODULE_TYPE must be defined 3 | # 4 | # DEF MODULE_TYPE = "STATIC" 5 | # include "tbabel/cython_common_imp.pxi" 6 | 7 | cdef extern from "stdlib.h": 8 | unsigned int rand() 9 | 10 | from sys import stdout 11 | 12 | cdef void _cycle(unsigned int N, unsigned int i, 13 | list _cy_routines, list _py_routines): 14 | 15 | cdef unsigned int nCy = len(_cy_routines) 16 | cdef unsigned int nPy = len(_py_routines) 17 | 18 | cdef unsigned int newI = rand()%(nCy + nPy) 19 | 20 | stdout.write("Cython ") 21 | stdout.write(MODULE_TYPE) 22 | stdout.write("\n") 23 | stdout.flush() 24 | 25 | if N: 26 | if newI < nCy: 27 | stdout.write("C -> ") 28 | (((_cy_routines[newI])))( 29 | N - 1, newI, _cy_routines, _py_routines) 30 | 31 | else: 32 | stdout.write("PY -> ") 33 | _py_routines[newI - nCy]( 34 | N - 1, newI, _cy_routines, _py_routines) 35 | 36 | def cycle(N, i, cy_routines, py_routines): 37 | _cycle(N, i, cy_routines, py_routines) 38 | 39 | def get_c_handle(): 40 | return (_cycle) 41 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_cython_module.pxd: -------------------------------------------------------------------------------- 1 | 2 | include "tbabel_cython_common.pxi" 3 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_cython_module.pyx: -------------------------------------------------------------------------------- 1 | 2 | DEF MODULE_TYPE = "MODULE" 3 | include "tbabel_cython_common_imp.pxi" 4 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_cython_shared.pxd: -------------------------------------------------------------------------------- 1 | 2 | include "tbabel_cython_common.pxi" 3 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_cython_shared.pyx: -------------------------------------------------------------------------------- 1 | 2 | DEF MODULE_TYPE = "SHARED" 3 | include "tbabel_cython_common_imp.pxi" 4 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_cython_static.pxd: -------------------------------------------------------------------------------- 1 | 2 | include "tbabel_cython_common.pxi" 3 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_cython_static.pyx: -------------------------------------------------------------------------------- 1 | 2 | DEF MODULE_TYPE = "STATIC" 3 | include "tbabel_cython_common_imp.pxi" 4 | -------------------------------------------------------------------------------- /projects/tower-of-babel/tbabel_python.py: -------------------------------------------------------------------------------- 1 | from random import getrandbits 2 | from sys import stdout 3 | 4 | 5 | def cycle(n, i, cy_routines, py_routines): # noqa: ARG001 6 | n_py = len(py_routines) 7 | 8 | new_index = getrandbits(32) % n_py 9 | 10 | stdout.write("Python ") 11 | stdout.write("MODULE") 12 | stdout.write("\n") 13 | stdout.flush() 14 | 15 | if n: 16 | stdout.write("PY -> ") 17 | py_routines[new_index](n - 1, new_index, cy_routines, py_routines) 18 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | codecov>=2.0.5 2 | coverage>=4.2 3 | cython>=0.25.1 4 | flake8>=3.0.4 5 | path.py>=8.2.1 6 | pytest>=3.0.3 7 | pytest-cov>=2.4.0 8 | pytest-mock>=1.4.0 9 | pytest-runner>=2.9 10 | pytest-virtualenv>=1.2.5 11 | six>=1.10.0 12 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | target-version = "py39" 2 | line-length = 88 3 | 4 | [lint] 5 | extend-select = [ 6 | "B", # flake8-bugbear 7 | "I", # isort 8 | "ARG", # flake8-unused-arguments 9 | "C4", # flake8-comprehensions 10 | "EM", # flake8-errmsg 11 | "ICN", # flake8-import-conventions 12 | "ISC", # flake8-implicit-str-concat 13 | "G", # flake8-logging-format 14 | "PGH", # pygrep-hooks 15 | "PIE", # flake8-pie 16 | "PL", # pylint 17 | "PTH", # flake8-use-pathlib 18 | "RET", # flake8-return 19 | "RUF", # Ruff-specific 20 | "SIM", # flake8-simplify 21 | "UP", # pyupgrade 22 | "YTT", # flake8-2020 23 | "EXE", # flake8-executable 24 | "NPY", # NumPy specific rules 25 | "PD", # pandas-vet 26 | ] 27 | ignore = [ 28 | "PLR09", # Design related pylint codes 29 | "PLR2004", # Magic values 30 | "ISC001", # Conflicts with formatter 31 | "PLC2401", # Non-ascii variable names are valid 32 | ] 33 | flake8-unused-arguments.ignore-variadic-names = true 34 | --------------------------------------------------------------------------------