Skip to content

Commit b9c54d5

Browse files
committed
Merge branch 'dev'
1 parent bf526d5 commit b9c54d5

31 files changed

+2665
-0
lines changed

.clang-format

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
BasedOnStyle: WebKit
2+
ColumnLimit: 80
3+
AlwaysBreakTemplateDeclarations: Yes
4+
Cpp11BracedListStyle: true
5+
SpaceBeforeCpp11BracedList: false
6+
FixNamespaceComments: true
7+
AlignConsecutiveAssignments: Consecutive
8+
AlignOperands: AlignAfterOperator
9+
AlignTrailingComments: true

.github/workflows/wheel.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Wheel
2+
3+
on:
4+
push:
5+
paths:
6+
- 'src/**'
7+
- 'src-python/**'
8+
- 'tests/**'
9+
- 'pyproject.toml'
10+
- 'CMakeLists.txt'
11+
- 'CMakePresets.json'
12+
- 'conanfile.txt'
13+
- '.github/workflows/wheel.yml'
14+
pull_request:
15+
16+
jobs:
17+
build_and_test:
18+
runs-on: ubuntu-latest
19+
20+
defaults:
21+
run:
22+
working-directory: ${{github.workspace}}
23+
24+
steps:
25+
26+
- uses: actions/checkout@v4
27+
with:
28+
ref: dev
29+
30+
- name: Install CMake and Ninja
31+
uses: lukka/get-cmake@latest
32+
33+
- name: Install Conan
34+
uses: turtlebrowser/get-conan@main
35+
36+
- name: Configure Conan
37+
run: conan profile detect --force
38+
39+
- name: Install C++ dependencies
40+
run: >
41+
conan install
42+
--build=missing
43+
--settings build_type=Release
44+
--settings compiler.cppstd=17
45+
${{github.workspace}}
46+
47+
- name: Install Python dependencies
48+
run: >
49+
python -m pip install
50+
-r ${{github.workspace}}/requirements.txt
51+
52+
- name: Build and install package
53+
run: >
54+
python -m pip install
55+
${{github.workspace}}
56+
--no-build-isolation
57+
58+
- name: Test package
59+
run: python -m pytest
60+
61+
- name: Build wheel
62+
run: >
63+
python -m pip wheel
64+
${{github.workspace}}
65+
--no-build-isolation
66+
--wheel-dir ${{github.workspace}}/dist

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Project exclude paths
2+
.conda
3+
.pytest_cache
4+
.vscode
5+
CMakePresets.json
6+
CMakeUserPresets.json
7+
build
8+
dist
9+
__pycache__

CMake/stubgen.cmake

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
function(pybind11_stubgen target)
2+
3+
find_package(Python3 REQUIRED COMPONENTS Interpreter)
4+
add_custom_command(TARGET ${target} POST_BUILD
5+
COMMAND ${Python3_EXECUTABLE} -m pybind11_stubgen
6+
$<TARGET_FILE_BASE_NAME:${target}>
7+
-o $<TARGET_FILE_DIR:${target}>
8+
WORKING_DIRECTORY $<TARGET_FILE_DIR:${target}>
9+
USES_TERMINAL
10+
)
11+
12+
endfunction()
13+
14+
function(pybind11_stubgen_install target destination)
15+
16+
install(DIRECTORY
17+
$<TARGET_FILE_DIR:${target}>/
18+
EXCLUDE_FROM_ALL
19+
COMPONENT python_modules
20+
DESTINATION ${destination}
21+
FILES_MATCHING REGEX "\.pyi$"
22+
)
23+
24+
endfunction()

CMakeLists.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
cmake_minimum_required(VERSION 3.15)
2+
project(AutoDiffPython VERSION 0.1.0 LANGUAGES CXX)
3+
4+
set(PY_VERSION_SUFFIX "")
5+
set(PY_FULL_VERSION ${PROJECT_VERSION}${PY_VERSION_SUFFIX})
6+
7+
# Make sure that the Python and CMake versions match
8+
if (DEFINED PY_BUILD_CMAKE_PACKAGE_VERSION)
9+
if (NOT "${PY_BUILD_CMAKE_PACKAGE_VERSION}" MATCHES "^${PY_FULL_VERSION}$")
10+
message(FATAL_ERROR "Version number does not match "
11+
"(${PY_BUILD_CMAKE_PACKAGE_VERSION} - ${PY_FULL_VERSION}).")
12+
endif()
13+
endif()
14+
15+
find_package(Python REQUIRED COMPONENTS Interpreter Development)
16+
find_package(pybind11 REQUIRED CONFIG)
17+
find_package(Eigen3 REQUIRED CONFIG)
18+
19+
include(FetchContent)
20+
FetchContent_Declare(
21+
autodiff
22+
GIT_REPOSITORY https://github.com/krippner/auto-diff.git
23+
GIT_TAG v0.4.0
24+
)
25+
FetchContent_MakeAvailable(autodiff)
26+
27+
option(WITH_PY_STUBS
28+
"Generate Python stub files (.pyi) for the Python modules." On
29+
)
30+
if (WITH_PY_STUBS AND NOT CMAKE_CROSSCOMPILING)
31+
include(CMake/stubgen.cmake)
32+
endif()
33+
34+
add_subdirectory(src)

README.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# AutoDiff - automatic differentiation for Python
2+
3+
[![Wheel](https://github.com/krippner/auto-diff-python/actions/workflows/wheel.yml/badge.svg)](https://github.com/krippner/auto-diff-python/actions/workflows/wheel.yml)
4+
5+
A lightweight Python package that provides fast **automatic differentiation (AD)** in forward and reverse mode for scalar and array computations.
6+
7+
AD is an **efficient algorithm** for computing **exact derivatives** of numeric programs.
8+
It is a standard tool in numerous fields, including optimization, machine learning, and scientific computing.
9+
10+
> [!NOTE]
11+
> This repository focuses on providing Python bindings and does not include the C++ backend, which is part of a separate, standalone C++ library. The C++ version offers additional features not available in these bindings. For more information on the C++ version, please visit the [AutoDiff repository](https://github.com/krippner/auto-diff).
12+
13+
## Features
14+
15+
- **Automatic differentiation**:
16+
- Jacobian matrix with forward- and reverse-mode AD
17+
- Jacobian-vector products, e.g., gradients and directional derivatives
18+
- Support for scalar, 1D and 2D array, and linear algebra computations
19+
- **Fast and efficient implementation**:
20+
- Backend written in C++ (using [this repository](https://github.com/krippner/auto-diff))
21+
- Leverages the performance of the [Eigen](https://eigen.tuxfamily.org) linear algebra library
22+
- Memory efficient (see [Variables vs. expressions](docs/expressions.md#variables-vs-expressions))
23+
- **Simple and intuitive API**
24+
- Regular control flow: function calls, loops, branches
25+
- Eager evaluation: what you evaluate is what you differentiate
26+
- Lazy re-evaluations: offering you precise control over what to evaluate
27+
- Math-inspired syntax
28+
29+
For more details, see the [documentation](#documentation).
30+
31+
## Installation
32+
33+
If you are on Linux, you can download the latest wheel file from the [releases page](https://github.com/krippner/auto-diff-python/releases) and install it using pip.
34+
35+
```bash
36+
python -m pip install autodiff-0.1.0-cp311-cp311-linux_x86_64.whl
37+
```
38+
39+
The wheel contains the extension modules as well as Python stub files for autocompletion and documentation in your IDE.
40+
41+
## Usage
42+
43+
Below are two simple examples of how to use the `autodiff` package to compute the gradient of a function with respect to its inputs.
44+
45+
The package provides two sub-modules: `array` and `scalar`.
46+
The `array` module is more general and can be used to compute gradients of functions involving both scalars and arrays (1D and 2D).
47+
48+
> [!CAUTION]
49+
> It is not possible to mix the `array` and `scalar` modules in the same program, as they use incompatible internal representations for variables.
50+
51+
### NumPy array example
52+
53+
```python
54+
# Example: gradient computation with NumPy arrays
55+
import numpy as np
56+
from autodiff.array import Function, var, d
57+
58+
# Create two 1D array variables
59+
x = var(np.array([1, 2, 3]))
60+
y = var(np.array([4, 5, 6]))
61+
62+
# Assign their (element-wise) product to a new variable
63+
z = var(x * y)
64+
65+
# Variables are evaluated eagerly
66+
print("z =", z()) # z = [ 4. 10. 18.]
67+
68+
# Create the function f : (x, y) ↦ z = x * y
69+
f = Function(z) # short for: Function(sources=(x, y), target=z)
70+
71+
# Set the (element-wise) derivative of z with respect to itself
72+
z.set_derivative(np.ones((1, 3)))
73+
74+
# Compute the gradient of f at (x, y) using reverse-mode AD
75+
f.pull_gradient()
76+
77+
# Get the components of the (element-wise) gradient
78+
print("∇_x f =", d(x)) # ∇_x f = [[4. 5. 6.]]
79+
print("∇_y f =", d(y)) # ∇_y f = [[1. 2. 3.]]
80+
81+
```
82+
83+
### Scalar example
84+
85+
For functions mapping only scalars to scalars, the `scalar` module is more efficient and convenient.
86+
No further imports are required.
87+
88+
```python
89+
# Example: gradient computation with scalars
90+
from autodiff.scalar import Function, var, d
91+
92+
# Create two scalar variables
93+
x = var(1.5)
94+
y = var(-2.0)
95+
96+
# Assign their product to a new variable
97+
z = var(x * y)
98+
99+
# Variables are evaluated eagerly
100+
print("z =", z()) # z = -3.0
101+
102+
# Create the function f : (x, y) ↦ z = x * y
103+
f = Function(z) # short for: Function(sources=(x, y), target=z)
104+
105+
# Compute the gradient of f at (x, y) using reverse-mode AD
106+
f.pull_gradient_at(z)
107+
108+
# Get the components of the gradient
109+
print("∂f/∂x =", d(x)) # ∂f/∂x = -2.0
110+
print("∂f/∂y =", d(y)) # ∂f/∂y = 1.5
111+
112+
```
113+
114+
## Documentation
115+
116+
1. [Variables and expressions](docs/expressions.md#top) - writing programs with `autodiff`
117+
1. [Variables](docs/expressions.md#variables)
118+
2. [Expressions](docs/expressions.md#expressions)
119+
3. [Variables vs. expressions](docs/expressions.md#variables-vs-expressions)
120+
2. [Functions](docs/functions.md#top) - (deferred) evaluation and differentiation
121+
1. [Lazy evaluation](docs/functions.md#lazy-evaluation)
122+
2. [Forward-mode differentiation](docs/functions.md#forward-mode-differentiation)
123+
3. [Reverse-mode differentiation (aka backpropagation)](docs/functions.md#reverse-mode-differentiation-aka-backpropagation)
124+
4. [Advanced: changing the program after evaluation](docs/functions.md#advanced-changing-the-program-after-evaluation)
125+
3. [The `autodiff.scalar` module](docs/scalar.md#top) - working with scalars only
126+
1. [Classes](docs/scalar.md#classes)
127+
2. [Variable factory functions](docs/scalar.md#variable-factory-functions)
128+
3. [Operations](docs/scalar.md#operations)
129+
4. [The `autodiff.array` module](docs/array.md#top) - working with scalars and NumPy arrays
130+
1. [Classes](docs/array.md#classes)
131+
2. [Variable factory functions](docs/array.md#variable-factory-functions)
132+
3. [Operations](docs/array.md#operations)
133+
4. [Matrix-valued expressions](docs/array.md#matrix-valued-expressions)
134+
5. [Applications](docs/applications.md#top) - common use cases and examples
135+
1. [Control flow](docs/applications.md#control-flow)
136+
2. [Computing the Jacobian matrix](docs/applications.md#computing-the-jacobian-matrix)
137+
3. [Gradient computation](docs/applications.md#gradient-computation)
138+
4. [Element-wise gradient computation](docs/applications.md#element-wise-gradient-computation)
139+
5. [Jacobian-vector products](docs/applications.md#jacobian-vector-products)

conanfile.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[requires]
2+
eigen/3.4.0
3+
pybind11/2.12.0
4+
5+
[generators]
6+
CMakeDeps
7+
CMakeToolchain
8+
9+
[layout]
10+
cmake_layout

0 commit comments

Comments
 (0)