Skip to content

Commit

Permalink
Add Python bindings
Browse files Browse the repository at this point in the history
Co-authored-by: Maurice Laveaux <m.laveaux@tue.nl>
  • Loading branch information
nhusung and mlaveaux committed May 2, 2024
1 parent 7fa30fd commit a7ab177
Show file tree
Hide file tree
Showing 30 changed files with 2,624 additions and 8 deletions.
110 changes: 110 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Python

# spell-checker:ignore pyproject,pydata,jakebailey,CIBW

on:
push:
branches: [main]
paths:
- bindings/python/**
- pyproject.toml
- crates/**
- Cargo.*
pull_request:
branches: [main]
paths:
- bindings/python/**
- pyproject.toml
- crates/**
- Cargo.*

env:
CARGO_TERM_COLOR: always

jobs:
lint-test-doc:
name: Lint, Test & Doc

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Install tools & dependencies
run: python -m pip install --upgrade pip ruff sphinx pydata-sphinx-theme cibuildwheel pytest
- name: Build
run: python -m pip install .
- name: Ruff check
run: ruff check --output-format=github
- name: Ruff format check
run: ruff format --check
- uses: jakebailey/pyright-action@v2
- name: Test
run: pytest
- name: Sphinx
run: sphinx-build -W bindings/python/doc build/python-doc

linux-buildwheel:
name: Build wheels for Linux

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
with:
platforms: arm64,s390x,ppc64le
- name: Install cibuildwheel
run: python3 -m pip install cibuildwheel
- name: Build (linux-buildwheel.py)
run: python3 bindings/python/build/linux-buildwheel.py --install-targets --archs all
- uses: actions/upload-artifact@v4
with:
name: python-wheels-linux
path: wheelhouse/*.whl

mac-buildwheel:
name: Build wheels for macOS

runs-on: ${{ matrix.os.image }}
strategy:
matrix:
os:
- { arch: x86_64, image: macos-13 }
- { arch: arm64, image: macos-14 }

steps:
- uses: actions/checkout@v4
- name: Install cbindgen
run: brew install cbindgen
- name: Build wheels
uses: pypa/cibuildwheel@v2.17.0
env:
CIBW_ARCHS_MACOS: native

- uses: actions/upload-artifact@v4
with:
name: python-wheels-mac-${{ matrix.os.arch }}
path: ./wheelhouse/*.whl

win-buildwheel:
name: Build wheels for Windows

runs-on: windows-latest

steps:
- uses: actions/checkout@v4
- name: Install Rust targets
run: rustup target add aarch64-pc-windows-msvc
- name: Build wheels
uses: pypa/cibuildwheel@v2.17.0
env:
CIBW_ARCHS_WINDOWS: all
CIBW_TEST_SKIP: "*-win_arm64"

- uses: actions/upload-artifact@v4
with:
name: python-wheels-win
path: ./wheelhouse/*.whl
17 changes: 14 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,30 @@
*.orig
# Reject files created by patch
*.rej
# Byte compiled python modules
*.pyc
# vim swap files
.*.sw?
.sw?
# macOS specific files
.DS_store
.DS_Store
# cSpell cache
.cspellcache

# Build products
*.so
*.dylib
*.dll
*.pyc
*.pyd
*.egg-info
*.whl

# clangd index (".clangd" is a config file now, thus trailing slash)
.clangd/
.cache
# Coverage reports
.coverage
# Python cache
__pycache__/
# VS2017 and VSCode config files
.vscode
.vs
Expand Down
31 changes: 30 additions & 1 deletion .project-words.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
aarch
alloc
archs
arcslab
arity
arrayvec
automodule
autosummary
BCDD
BDD
BibTeX
binfmt
binop
bitflags
bitvec
bugprone
buildwheel
capi
cassert
cbindgen
cdylib
cffi
cfgs
CFLAGS
cibuildwheel
clangd
clippy
codegen
Expand Down Expand Up @@ -42,6 +51,7 @@ endcond
fieldless
filesystems
forall
fullname
Graphviz
hashable
hasher
Expand All @@ -61,18 +71,21 @@ impls
intptr
intsec
isize
isort
Javadoc
Köhl
levelized
libc
liboxidd
linenos
madvise
manylinux
mdbook
memchr
Miri
mmap
MSVC
MTBDD
musllinux
NAND
nanorand
nanos
Expand All @@ -85,20 +98,33 @@ pointee
postprocess
precompute
ptrdiff
pycache
pycodestyle
PYFFI
Pyflakes
pypa
pyproject
pypy
pyright
pytest
pyupgrade
RAII
realloc
repr
retag
rpath
rustc
RUSTDOCFLAGS
RUSTFLAGS
rustfmt
rustix
Rustonomicon
rustup
rwlock
satisfiability
segtree
serde
setuptools
sideeffect
smallvec
spinlock
Expand All @@ -121,6 +147,7 @@ subtraits
swappable
sysinfo
TACAS
toctree
trybuild
typedefs
uchar
Expand All @@ -130,6 +157,8 @@ uninit
unoptimized
unref
usize
venv
virtualenv
wakeups
wyrand
ZBDD
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,18 @@ The main code is located in the [crates](crates) directory. The framework is cen

![Crate Dependency Graph](doc/book/src/img/crate-deps.svg)

Besides the Rust code, there are also bindings for C/C++ and Python in the `bindings` directory. OxiDD has a foreign function interface (FFI) located in the `oxidd-ffi` crate. It does not expose the entire API that can be used from Rust, but it is sufficient to, e.g., create BDDs and apply various logical operators on them. In principle, you can use the FFI from any language that can call C functions. However, there are also more ergonomic C++ bindings that build on top of the C FFI. You can just use include this repository using CMake. To use OxiDD from Python, the easiest way is to use the package on PyPI (to be published soon).


## FAQ

Q: What about bindings for language X?

OxiDD has a foreign function interface (FFI) located in the `oxidd-ffi` crate. It does not expose the entire API that can be used from Rust, but it is sufficient to, e.g., create BDDs and apply various logical operators on them. In principle, you can use the FFI from any language that can call C functions. However, there are also more ergonomic C++ bindings that build on top of the C FFI. You can just use include this repository using CMake. Additionally, we are currently working on Python bindings that will be published very soon. If you want to use OxiDD from a different language, please contact us. We really like to support you and your use-case.
As mentioned above, OxiDD already supports C/C++ and Python. C# and Java bindings might follow later this year. If you want to use OxiDD from a different language, please contact us. We would really like to support you and your use-case.

Q: What about dynamic/automatic reordering?

OxiDD already supports reordering in the sense of establishing a given variable order. Implementing this without introducing unsafe code in the algorithms applying operators, adding rather expensive synchronization mechanisms, or disabling concurrency entirely was a larger effort. More details on that can be found in [our paper](https://doi.org/10.1007/978-3-031-57256-2_13). But now, adding reordering heuristics such as sifting is a low-hanging fruit. Next up, we will also work on dynamic reordering (i.e., aborting operations for reordering and restarting them afterwards) and automatic reordering (i.e., heuristics that identify points in time where dynamic reordering is beneficial).


## Licensing
Expand Down
35 changes: 35 additions & 0 deletions bindings/python/DEVELOPING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Developing the Python Bindings

To start developing, create a virtual environment (`python -m venv .venv`) and run:

just devtools-py

This will install a few development tools and run `pip install --editable .`. The latter makes all your edits in Python files available in the virtual environment without the need to run any install commands again. Behind the scenes, the `pip install` command builds the `oxidd-ffi` crate in release mode, generates `target/include/oxidd.h` using `cbindgen`, and compiles a Python extension module called `_oxidd` via CFFI. This also means that if you change the Rust code, you need to run `pip install --editable .` again.


## Common Actions

Most actions can be run using `just` (a tool similar to `make`, available on `crates.io` and via the respective package manager on most systems):

- `just fmt-py`: Run `ruff` as formatter
- `just lint-py`: Run `ruff` as linter and `pyright` as static type checker
- `just doc-py`: Build documentation using Sphinx. The output is placed in `target/python/doc`.
- `just test-py`: Run `pytest` and report test coverage to `target/python/coverage`.


## Build Process Internals

We currently use `setuptools` for the build process. Therefore, the entrypoint is `setup.py` in the project root. `setuptools` is in the progress of migrating away from `setup.py`, so most settings are already taken from `pyproject.toml`. The remaining settings in `setup.py` cannot yet be specified in `pyproject.toml`. `setup.py` mainly registers a CFFI module with `bindings/python/build/ffi.py` as the build script. The latter invokes `cargo`, `cbindgen`, and compilation via CFFI.

There are different ways to link the OxiDD library and the Python extension module together. To control this, there is the `OXIDD_PYFFI_LINK_MODE` environment variable, which can be:
- `static`: Statically link against `liboxidd_ffi.a` or `oxidd_ffi.lib` in the `target/<profile>` directory.

This is the mode we will be using for shipping via PyPI since we do not need to mess around with the RPath (which does not exist on Windows anyway). It is also the default mode on Windows.
- `shared-system`: Dynamically link against a system-installed `liboxidd.so`, `liboxidd.dylib`, or `oxidd.dll`, respectively.

This mode is useful for packaging in, e.g., Linux distributions. With this mode, we do not need to ship the main OxiDD library in both packages, `liboxidd` and `python3-oxidd`. We can furthermore decouple updates of the two packages.
- `shared-dev`: Dynamically link against `liboxidd_ffi.so`, `liboxidd_ffi.dylib`, or `oxidd_ffi.dll` in the `target/<profile>` directory.

This mode is the default for developing on Unix systems. When tuning heuristics for instance, a simple `cargo build --release` suffices, no `pip install --editable .` is required before re-running the Python script. On Windows, setting this mode up requires extra work, since there is no RPath like on Unix systems.

Building Python wheels is possible using [`cibuildwheel`](https://cibuildwheel.pypa.io/en/stable/). To build for Linux, we use a wrapper script (`./build/linux-buildwheel.py`) around `cibuildwheel` to avoid installing a Rust toolchain in the containers. It will both cross-compile the `oxidd-ffi` crate for all the specified target architectures and run `cbindgen` on the host system. Only compiling the Python module and packaging is left to the `cibuildwheel` containers. We pass the environment variable `OXIDD_PYFFI_CONTAINER_BUILD=1` to the build script to indicate that it should run neither `cargo build` nor `cbindgen` but look for `liboxidd_ffi.a` in the respective target subdirectory (e.g., `target/aarch64-unknown-linux-gnu/release`). `OXIDD_PYFFI_CONTAINER_BUILD=1` also implies `OXIDD_PYFFI_LINK_MODE=static`. Note that building for non-native architectures requires [emulation](https://cibuildwheel.pypa.io/en/stable/faq/#emulation).
Empty file.
Loading

0 comments on commit a7ab177

Please sign in to comment.