fastprox 0.1.0rc1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. fastprox-0.1.0rc1/.github/workflows/bench.yml +52 -0
  2. fastprox-0.1.0rc1/.github/workflows/ci.yml +138 -0
  3. fastprox-0.1.0rc1/.github/workflows/fuzz.yml +36 -0
  4. fastprox-0.1.0rc1/.gitignore +13 -0
  5. fastprox-0.1.0rc1/.python-version +1 -0
  6. fastprox-0.1.0rc1/CHANGELOG.md +37 -0
  7. fastprox-0.1.0rc1/CLAUDE.md +34 -0
  8. fastprox-0.1.0rc1/CMakeLists.txt +161 -0
  9. fastprox-0.1.0rc1/LICENSE +21 -0
  10. fastprox-0.1.0rc1/PKG-INFO +100 -0
  11. fastprox-0.1.0rc1/README.md +73 -0
  12. fastprox-0.1.0rc1/ROADMAP.md +103 -0
  13. fastprox-0.1.0rc1/bench/bench_python.py +99 -0
  14. fastprox-0.1.0rc1/bench/compare_external.py +289 -0
  15. fastprox-0.1.0rc1/bindings/python/CMakeLists.txt +17 -0
  16. fastprox-0.1.0rc1/bindings/python/fastprox/__init__.py +433 -0
  17. fastprox-0.1.0rc1/bindings/python/src/module.cpp +224 -0
  18. fastprox-0.1.0rc1/cmake/fastprox.map +7 -0
  19. fastprox-0.1.0rc1/cmake/fastprox.pc.in +9 -0
  20. fastprox-0.1.0rc1/cmake/fastproxConfig.cmake.in +5 -0
  21. fastprox-0.1.0rc1/docs/api.md +204 -0
  22. fastprox-0.1.0rc1/docs/benchmarks.md +111 -0
  23. fastprox-0.1.0rc1/docs/spec.md +461 -0
  24. fastprox-0.1.0rc1/fuzz/CMakeLists.txt +20 -0
  25. fastprox-0.1.0rc1/fuzz/fuzz_ops.cpp +296 -0
  26. fastprox-0.1.0rc1/fuzz/standalone.cpp +79 -0
  27. fastprox-0.1.0rc1/include/fastprox.h +205 -0
  28. fastprox-0.1.0rc1/pyproject.toml +102 -0
  29. fastprox-0.1.0rc1/src/api/api.cpp +515 -0
  30. fastprox-0.1.0rc1/src/core/driver.hpp +320 -0
  31. fastprox-0.1.0rc1/src/core/layout.hpp +163 -0
  32. fastprox-0.1.0rc1/src/core/op.hpp +304 -0
  33. fastprox-0.1.0rc1/src/core/workspace.hpp +91 -0
  34. fastprox-0.1.0rc1/src/ops/group.cpp +146 -0
  35. fastprox-0.1.0rc1/src/ops/isotonic.cpp +124 -0
  36. fastprox-0.1.0rc1/src/ops/ops.hpp +41 -0
  37. fastprox-0.1.0rc1/src/ops/pointwise.cpp +235 -0
  38. fastprox-0.1.0rc1/src/ops/projections.cpp +556 -0
  39. fastprox-0.1.0rc1/src/ops/tv1d.cpp +525 -0
  40. fastprox-0.1.0rc1/tests/c/CMakeLists.txt +12 -0
  41. fastprox-0.1.0rc1/tests/c/test_core.cpp +532 -0
  42. fastprox-0.1.0rc1/tests/c/test_header.c +65 -0
  43. fastprox-0.1.0rc1/tests/fixtures/generate.py +84 -0
  44. fastprox-0.1.0rc1/tests/fixtures/reference.npz +0 -0
  45. fastprox-0.1.0rc1/tests/python/conftest.py +2 -0
  46. fastprox-0.1.0rc1/tests/python/fixture_cases.py +117 -0
  47. fastprox-0.1.0rc1/tests/python/reference.py +104 -0
  48. fastprox-0.1.0rc1/tests/python/test_fixtures.py +42 -0
  49. fastprox-0.1.0rc1/tests/python/test_operators.py +381 -0
  50. fastprox-0.1.0rc1/uv.lock +869 -0
@@ -0,0 +1,52 @@
1
+ name: bench
2
+
3
+ # Benchmarks on GitHub's Linux runners: homogeneous cores (unlike a laptop
4
+ # with performance and efficiency cores), but shared VMs, so compare solvers
5
+ # within one run rather than across runs. Started by hand from the Actions tab.
6
+ on:
7
+ workflow_dispatch:
8
+ inputs:
9
+ full:
10
+ description: "All sizes up to 1e7 (slow) instead of 1e3 and 1e5"
11
+ type: boolean
12
+ default: false
13
+
14
+ jobs:
15
+ bench:
16
+ name: bench ${{ matrix.os }}
17
+ runs-on: ${{ matrix.os }}
18
+ timeout-minutes: 90
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ os: [ubuntu-latest, ubuntu-24.04-arm]
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: astral-sh/setup-uv@v6
26
+ - name: Tools for the external codes (proxTV needs LAPACK, Condat's files GSL)
27
+ run: sudo apt-get update && sudo apt-get install -y liblapack-dev libgsl-dev
28
+ - run: uv sync --python 3.12
29
+ - name: Machine
30
+ run: |
31
+ {
32
+ lscpu | grep -E 'Model name|^CPU\(s\)|Thread|Core|Socket|MHz|L2|L3' || true
33
+ cc --version | head -1
34
+ c++ --version | head -1
35
+ } | tee machine.txt
36
+ { echo "### ${{ matrix.os }}"; echo '```'; cat machine.txt; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
37
+ - name: Overhead, throughput and parallel scaling
38
+ run: uv run python bench/bench_python.py | tee bench_python.txt
39
+ - name: Against proxTV and Condat's code
40
+ run: uv run python bench/compare_external.py ${{ inputs.full && ' ' || '--quick' }} | tee compare_external.txt
41
+ - name: Summary
42
+ if: always()
43
+ run: |
44
+ for f in bench_python.txt compare_external.txt; do
45
+ [ -f "$f" ] || continue
46
+ { echo "#### $f"; echo '```'; cat "$f"; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
47
+ done
48
+ - uses: actions/upload-artifact@v4
49
+ if: always()
50
+ with:
51
+ name: bench-${{ matrix.os }}
52
+ path: "*.txt"
@@ -0,0 +1,138 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch: # also builds the wheels, without a tag
7
+ inputs:
8
+ publish:
9
+ description: Upload the wheels and sdist to TestPyPI
10
+ type: boolean
11
+ default: false
12
+
13
+ jobs:
14
+ c:
15
+ name: C/C++ ${{ matrix.os }} ${{ matrix.cc }} openmp=${{ matrix.openmp }}
16
+ runs-on: ${{ matrix.os }}
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ include:
21
+ - { os: ubuntu-latest, cc: gcc, cxx: g++, openmp: ON }
22
+ - { os: ubuntu-latest, cc: clang, cxx: clang++, openmp: ON }
23
+ - { os: ubuntu-latest, cc: gcc, cxx: g++, openmp: OFF }
24
+ - { os: macos-latest, cc: clang, cxx: clang++, openmp: ON }
25
+ - { os: macos-15-intel, cc: clang, cxx: clang++, openmp: ON }
26
+ - { os: windows-latest, cc: cl, cxx: cl, openmp: ON }
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - if: runner.os == 'macOS'
30
+ run: brew install libomp
31
+ - name: Configure
32
+ env: { CC: "${{ matrix.cc }}", CXX: "${{ matrix.cxx }}" }
33
+ run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DFASTPROX_OPENMP=${{ matrix.openmp }}
34
+ - run: cmake --build build --config Release
35
+ - run: ctest --test-dir build -C Release --output-on-failure --timeout 300
36
+
37
+ sanitizers:
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+ - run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DFASTPROX_SANITIZE=ON -DFASTPROX_OPENMP=OFF -DFASTPROX_FUZZ=ON
42
+ - run: cmake --build build
43
+ - run: ctest --test-dir build --output-on-failure --timeout 300
44
+
45
+ valgrind:
46
+ runs-on: ubuntu-latest
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+ - run: sudo apt-get update && sudo apt-get install -y valgrind
50
+ - run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DFASTPROX_OPENMP=OFF
51
+ - run: cmake --build build
52
+ - name: Memcheck
53
+ run: |
54
+ for t in build/tests/c/test_header build/tests/c/test_core; do
55
+ valgrind --error-exitcode=1 --leak-check=full --errors-for-leak-kinds=definite "$t"
56
+ done
57
+
58
+ python:
59
+ name: Python ${{ matrix.python }} ${{ matrix.os }}
60
+ runs-on: ${{ matrix.os }}
61
+ strategy:
62
+ fail-fast: false
63
+ matrix:
64
+ os: [ubuntu-latest, macos-latest, windows-latest]
65
+ python: ["3.12"]
66
+ include:
67
+ - { os: ubuntu-latest, python: "3.14" } # the abi3 wheel on a newer CPython
68
+ steps:
69
+ - uses: actions/checkout@v4
70
+ - if: runner.os == 'macOS'
71
+ run: brew install libomp
72
+ - uses: astral-sh/setup-uv@v6
73
+ - run: uv sync --python ${{ matrix.python }}
74
+ - run: uv run pytest -q
75
+
76
+ wheels:
77
+ name: Wheels ${{ matrix.os }}
78
+ if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
79
+ runs-on: ${{ matrix.os }}
80
+ strategy:
81
+ matrix:
82
+ # macOS runners are pinned: the wheel's minimum macOS is that of the
83
+ # runner's Homebrew libomp, which must match MACOSX_DEPLOYMENT_TARGET.
84
+ os: [ubuntu-latest, ubuntu-24.04-arm, macos-14, macos-15-intel, windows-latest]
85
+ steps:
86
+ - uses: actions/checkout@v4
87
+ - uses: pypa/cibuildwheel@v3.1
88
+ - uses: actions/upload-artifact@v4
89
+ with:
90
+ name: wheels-${{ matrix.os }}
91
+ path: wheelhouse/*.whl
92
+
93
+ sdist:
94
+ name: Source distribution
95
+ if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
96
+ runs-on: ubuntu-latest
97
+ steps:
98
+ - uses: actions/checkout@v4
99
+ - uses: astral-sh/setup-uv@v6
100
+ - run: uv build --sdist
101
+ - name: Tag matches the package version
102
+ if: startsWith(github.ref, 'refs/tags/')
103
+ run: |
104
+ version=$(tar xzf dist/*.tar.gz -O --wildcards '*/PKG-INFO' | sed -n 's/^Version: //p')
105
+ test "${GITHUB_REF_NAME}" = "v${version}" || {
106
+ echo "tag ${GITHUB_REF_NAME} does not match version ${version}"; exit 1; }
107
+ - uses: actions/upload-artifact@v4
108
+ with:
109
+ name: sdist
110
+ path: dist/*.tar.gz
111
+
112
+ # Trusted publishing (OIDC): no API tokens are stored in the repository.
113
+ # The publishers are registered on PyPI and TestPyPI for this workflow file
114
+ # and these environment names.
115
+ publish-testpypi:
116
+ name: Publish to TestPyPI
117
+ if: github.event_name == 'workflow_dispatch' && inputs.publish
118
+ needs: [c, sanitizers, valgrind, python, wheels, sdist]
119
+ runs-on: ubuntu-latest
120
+ environment: { name: testpypi, url: "https://test.pypi.org/p/fastprox" }
121
+ permissions: { id-token: write }
122
+ steps:
123
+ - uses: actions/download-artifact@v4
124
+ with: { path: dist, merge-multiple: true }
125
+ - uses: pypa/gh-action-pypi-publish@release/v1
126
+ with: { repository-url: "https://test.pypi.org/legacy/" }
127
+
128
+ publish-pypi:
129
+ name: Publish to PyPI
130
+ if: startsWith(github.ref, 'refs/tags/v')
131
+ needs: [c, sanitizers, valgrind, python, wheels, sdist]
132
+ runs-on: ubuntu-latest
133
+ environment: { name: pypi, url: "https://pypi.org/p/fastprox" }
134
+ permissions: { id-token: write }
135
+ steps:
136
+ - uses: actions/download-artifact@v4
137
+ with: { path: dist, merge-multiple: true }
138
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,36 @@
1
+ name: fuzz
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "17 3 * * *" # nightly
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ libfuzzer:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/cache@v4
14
+ with:
15
+ path: corpus
16
+ key: fuzz-corpus-${{ github.run_id }}
17
+ restore-keys: fuzz-corpus-
18
+ - name: Build
19
+ env: { CC: clang, CXX: clang++ }
20
+ run: |
21
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DFASTPROX_FUZZ=ON \
22
+ -DFASTPROX_SANITIZE=ON -DFASTPROX_OPENMP=OFF -DBUILD_SHARED_LIBS=OFF
23
+ cmake --build build --target fuzz_ops
24
+ - name: Fuzz for 20 minutes
25
+ run: |
26
+ mkdir -p corpus
27
+ ./build/fuzz/fuzz_ops corpus -max_total_time=1200 -timeout=10 -rss_limit_mb=4096 \
28
+ -max_len=8192 -print_final_stats=1
29
+ - if: failure()
30
+ uses: actions/upload-artifact@v4
31
+ with:
32
+ name: fuzz-crashes
33
+ path: |
34
+ crash-*
35
+ timeout-*
36
+ leak-*
@@ -0,0 +1,13 @@
1
+ build/
2
+ .venv/
3
+ __pycache__/
4
+ *.egg-info/
5
+ *.so
6
+ *.dylib
7
+ *.pyd
8
+ compile_commands.json
9
+ .pytest_cache/
10
+ .hypothesis/
11
+ dist/
12
+ .cache/
13
+ fuzz-crash.bin
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ All notable changes to fastprox. Planned work is in [ROADMAP.md](ROADMAP.md).
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## [Unreleased] — 0.1.0rc1
7
+
8
+ ### Added
9
+
10
+ - Plain-C API (`include/fastprox.h`) over a C++17 core: opaque operator and workspace handles, `fp_prox`, `fp_prox_conj`, `fp_jvp`, `fp_vjp`, error codes with every exception caught at the boundary.
11
+ - Operators: soft thresholding (weighted), elastic net, Huber, group ℓ2 and ℓ2 norm, box, nonnegative orthant, ℓ2 ball, simplex, weighted ℓ1 ball, ℓ∞ norm, isotonic regression, and 1D TV (Condat's direct algorithm and taut string, edge weights).
12
+ - JVP and VJP in the input, the step and the operator parameters for every operator.
13
+ - Batching over any axis of strided n-d arrays, OpenMP parallelism with bit-identical results for any thread count, and allocation-free calls with a workspace.
14
+ - Python package built with nanobind (abi3, CPython ≥ 3.12): one-shot functions and operator classes with `prox`, `prox_conj`, `jvp` and `vjp`.
15
+ - C/C++ and Python test suites, a benchmark script, and a CI workflow.
16
+ - Release pipeline: sdist job, trusted publishing to TestPyPI (manual dispatch with `publish`) and to PyPI (tags `v*`, checked against the package version); PyPI metadata and a README for the project page; pre-release tags through `FASTPROX_VERSION_SUFFIX` in `CMakeLists.txt`; Python tests also run on CPython 3.14.
17
+ - Fuzz harness for the whole API (libFuzzer target and standalone driver) with a nightly workflow; CVXPY + Clarabel reference fixtures; benchmarks against proxTV and Condat's code (`bench/compare_external.py`, `docs/benchmarks.md`); API reference (`docs/api.md`); Valgrind CI job.
18
+
19
+ ### Changed
20
+
21
+ - 1D TV: the Condat path falls back to the taut string after 3n + 1024 rescanned steps (was 16n + 1024 steps in total), which makes adversarial inputs such as ramps about 3.5× cheaper without affecting typical signals.
22
+ - 1D TV: the bound updates of Condat's algorithm are branch-free selects with a table of reciprocals, 25–35% faster on noisy signals (same arithmetic, so the same results).
23
+ - 1D TV: weighted problems now use Condat's direct algorithm, generalised to per-edge weights (radii precomputed per piece), with the same budgeted fallback to the taut string; `FP_TV1D_CONDAT` accepts weights. Weighted rows went from 1.4–2.8× slower than proxTV's weighted taut string to faster in most settings.
24
+ - 1D TV: radii that can never be active (above the dual bound j · (max x − min x)) are clamped, and when no edge can be active the result is the constant mean. Extreme weights or steps no longer cost accuracy: the residual stays within 10 n ε ‖x‖∞.
25
+ - 1D TV: the taut string stores its two chains as segments (length, rise, cached slope) in coordinates local to the current knot, an idea taken from proxTV's classic taut string. It is 17–26% faster and needs no compensated sums: the cumulative sum of the signal is never formed.
26
+ - Unweighted simplex and ℓ1-ball projections keep candidate values instead of indices (about 30% faster). The simplex also tests finiteness inside its pivot pass instead of a separate pass (a further 19%), and the ℓ1 ball and ℓ∞ prox reuse their ℓ1 norm as the test.
27
+ - 1D TV: bound updates are branch-free only in the first 8 samples of a segment; weighted rows read their radii directly when none can need clamping; operator construction makes one pass over the weights.
28
+ - Workspace scratch is no longer zero-filled, and the finiteness check is a branch-free pass: one-shot calls on large rows are noticeably faster.
29
+ - The version is defined once, in `CMakeLists.txt`; the Python package reads it from there.
30
+
31
+ ### Fixed
32
+
33
+ - Weighted ℓ1-ball projection returned infinities for tiny weights (around 1e-279) because sums of squared weights underflowed (found by fuzzing). Weights are now normalised by their maximum, and ratios below 1e-100 are rejected.
34
+ - `fp_prox_conj` with σ = 0 on arrays with negative strides computed byte offsets in unsigned arithmetic (undefined behaviour, found by fuzzing).
35
+ - Static Windows builds marked API symbols `dllexport`.
36
+ - The taut string kept cumulative sums in plain doubles and missed the 10 n ε ‖x‖∞ accuracy bound on long rows; its chains now use local coordinates.
37
+ - A test assumed weighted isotonic regression is firmly nonexpansive in the Euclidean norm; it is so in the weighted norm, which the test now uses.
@@ -0,0 +1,34 @@
1
+ # fastprox
2
+
3
+ Exact proximal operators with JVP/VJP. C++17 core behind the plain-C header
4
+ `include/fastprox.h`; Python bindings via nanobind. The design spec is
5
+ `docs/spec.md` — read it before changing the API or adding operators.
6
+
7
+ ## Commands
8
+
9
+ - C library and tests: `cmake -S . -B build/c -G Ninja && cmake --build build/c && ./build/c/tests/c/test_core && ./build/c/tests/c/test_header`
10
+ - Sanitizers: configure a second tree with `-DFASTPROX_SANITIZE=ON -DFASTPROX_OPENMP=OFF -DCMAKE_BUILD_TYPE=Debug`
11
+ - Python (always through uv): `uv sync`, `uv run pytest -q`. uv rebuilds the extension when C++ sources change (`tool.uv.cache-keys`).
12
+ - Benchmarks: `uv run python bench/bench_python.py`
13
+ - Releasing: set the version in `project()` and `FASTPROX_VERSION_SUFFIX` (`CMakeLists.txt`) and the macros in `fastprox.h`; dry run with `gh workflow run ci.yml -f publish=true` (TestPyPI); then push the tag `v<version>`, which publishes to PyPI.
14
+
15
+ ## Layout
16
+
17
+ - `src/core/op.hpp` — `fp_op` base class; `ElemOp<D>` (elementwise, needs `prox1`/`deriv1`) and `RowOp<D>` (row kernels `prox`/`jvp`/`vjp`) CRTP helpers.
18
+ - `src/core/driver.hpp` — batching over strided n-d arrays, OpenMP, saved structure; `src/core/layout.hpp` — row and elementwise iteration plans.
19
+ - `src/ops/*.cpp` — one file per operator family, each with a `make_*` factory declared in `src/ops/ops.hpp`.
20
+ - `src/api/api.cpp` — the only `extern "C"` code: validation, dtype dispatch, try/catch.
21
+ - `bindings/python/src/module.cpp` calls only the C API; `bindings/python/fastprox/__init__.py` is the user-facing package.
22
+
23
+ ## Tracking
24
+
25
+ - `ROADMAP.md` lists planned work per release with status boxes; tick items as they land and add new ones as they come up.
26
+ - `CHANGELOG.md` records finished changes under `[Unreleased]`, in the same change that makes them.
27
+ - Any decision that departs from `docs/spec.md` goes in the decisions table at the end of `ROADMAP.md`, with its date.
28
+
29
+ ## Rules
30
+
31
+ - Clean room: never read GPL, CeCILL or unlicensed code (Condat's C files, Kolmogorov's maxflow/TVTree, Chambolle's graphTV, PyMaxflow, gco). Implement from papers and cite them in the kernel's header comment. proxTV (BSD) may be consulted with attribution.
32
+ - No `-ffast-math`. Results must be bit-identical for any thread count: rows are independent, and cross-row sums (vjp parameter gradients) use fixed blocks from `reduction_blocks`, never thread-dependent partitions.
33
+ - No allocation inside `fp_prox`/`fp_jvp`/`fp_vjp` when a workspace is given; size everything in `build_workspace` (tested by an allocation counter in `tests/c/test_core.cpp`).
34
+ - Adding an operator: kernel + derivatives in `src/ops/`, factory in `ops.hpp`, constructor in `fastprox.h` and `api.cpp`, nanobind wrapper, Python class with `_layout`, and an entry in `CASES` in `tests/python/test_operators.py` (which runs reference, finite-difference, adjointness, nonexpansiveness, threading and stride tests on it) plus `derivative_cases` in `tests/c/test_core.cpp`.
@@ -0,0 +1,161 @@
1
+ cmake_minimum_required(VERSION 3.20)
2
+ project(fastprox VERSION 0.1.0 LANGUAGES C CXX)
3
+ # PEP 440 pre-release tag (a1, rc1, ...) appended to the version; empty for a
4
+ # final release. pyproject.toml reads both lines.
5
+ set(FASTPROX_VERSION_SUFFIX "rc1")
6
+ set(FASTPROX_VERSION_FULL "${PROJECT_VERSION}${FASTPROX_VERSION_SUFFIX}")
7
+
8
+ option(FASTPROX_OPENMP "Parallelise over rows with OpenMP" ON)
9
+ option(FASTPROX_BUILD_TESTS "Build the C and C++ tests" ${PROJECT_IS_TOP_LEVEL})
10
+ option(FASTPROX_PYTHON "Build the nanobind Python extension" OFF)
11
+ option(FASTPROX_SANITIZE "Build with AddressSanitizer and UBSan" OFF)
12
+ option(FASTPROX_FUZZ "Build the fuzzing harnesses" OFF)
13
+ option(BUILD_SHARED_LIBS "Build libfastprox as a shared library" ON)
14
+
15
+ set(CMAKE_CXX_STANDARD 17)
16
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
17
+ set(CMAKE_CXX_EXTENSIONS OFF)
18
+ set(CMAKE_C_STANDARD 99)
19
+ set(CMAKE_C_STANDARD_REQUIRED ON)
20
+ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
21
+ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
22
+ set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
23
+ endif()
24
+
25
+ include(GNUInstallDirs)
26
+
27
+ # Windows finds DLLs next to the executable: keep the library, tests and fuzz
28
+ # drivers in one directory, or every test hangs on a "DLL not found" dialog.
29
+ if(WIN32 AND NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
30
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
31
+ endif()
32
+
33
+ # project() above is the single source of the version (pyproject.toml reads it
34
+ # too); the public header's macros must agree with it.
35
+ file(STRINGS include/fastprox.h _fp_version_lines REGEX "#define FP_VERSION_(MAJOR|MINOR|PATCH) ")
36
+ string(REGEX REPLACE ".*MAJOR ([0-9]+).*MINOR ([0-9]+).*PATCH ([0-9]+).*" "\\1.\\2.\\3"
37
+ _fp_header_version "${_fp_version_lines}")
38
+ if(NOT _fp_header_version STREQUAL PROJECT_VERSION)
39
+ message(FATAL_ERROR "fastprox.h declares version ${_fp_header_version}, "
40
+ "CMakeLists.txt ${PROJECT_VERSION}")
41
+ endif()
42
+
43
+ # The Python extension links the core statically.
44
+ if(FASTPROX_PYTHON)
45
+ set(BUILD_SHARED_LIBS OFF)
46
+ endif()
47
+
48
+ add_library(fastprox
49
+ src/api/api.cpp
50
+ src/ops/pointwise.cpp
51
+ src/ops/group.cpp
52
+ src/ops/projections.cpp
53
+ src/ops/isotonic.cpp
54
+ src/ops/tv1d.cpp
55
+ )
56
+ add_library(fastprox::fastprox ALIAS fastprox)
57
+
58
+ target_include_directories(fastprox
59
+ PUBLIC
60
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
61
+ $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
62
+ PRIVATE
63
+ ${CMAKE_CURRENT_SOURCE_DIR}/src
64
+ )
65
+ target_compile_definitions(fastprox
66
+ PRIVATE FASTPROX_BUILDING FASTPROX_VERSION_STRING="${FASTPROX_VERSION_FULL}")
67
+ if(NOT BUILD_SHARED_LIBS)
68
+ target_compile_definitions(fastprox PUBLIC FASTPROX_STATIC)
69
+ endif()
70
+ set_target_properties(fastprox PROPERTIES
71
+ CXX_VISIBILITY_PRESET hidden
72
+ VISIBILITY_INLINES_HIDDEN ON
73
+ POSITION_INDEPENDENT_CODE ON
74
+ VERSION ${PROJECT_VERSION}
75
+ SOVERSION ${PROJECT_VERSION_MAJOR}
76
+ )
77
+
78
+ # No -ffast-math anywhere: the direct algorithms compare running bounds and
79
+ # determinism depends on the order of floating-point operations.
80
+ if(MSVC)
81
+ target_compile_options(fastprox PRIVATE /W4 /fp:precise)
82
+ else()
83
+ target_compile_options(fastprox PRIVATE -Wall -Wextra -Wno-unknown-pragmas -fno-fast-math)
84
+ endif()
85
+
86
+ if(FASTPROX_OPENMP)
87
+ if(APPLE AND NOT OpenMP_ROOT)
88
+ # Apple Clang ships without libomp; use Homebrew's when present.
89
+ foreach(_prefix /opt/homebrew/opt/libomp /usr/local/opt/libomp)
90
+ if(EXISTS "${_prefix}/include/omp.h")
91
+ set(OpenMP_ROOT "${_prefix}")
92
+ break()
93
+ endif()
94
+ endforeach()
95
+ endif()
96
+ find_package(OpenMP COMPONENTS CXX)
97
+ if(OpenMP_CXX_FOUND)
98
+ target_link_libraries(fastprox PRIVATE OpenMP::OpenMP_CXX)
99
+ else()
100
+ message(WARNING "OpenMP not found: fastprox will run serially")
101
+ endif()
102
+ endif()
103
+
104
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
105
+ # Keep the host libstdc++ out of the picture (MATLAB, old distributions).
106
+ target_link_options(fastprox PRIVATE -static-libstdc++ -static-libgcc)
107
+ endif()
108
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND BUILD_SHARED_LIBS)
109
+ target_link_options(fastprox PRIVATE
110
+ "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/cmake/fastprox.map")
111
+ endif()
112
+
113
+ if(FASTPROX_SANITIZE AND NOT MSVC)
114
+ add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer)
115
+ add_link_options(-fsanitize=address,undefined)
116
+ target_compile_options(fastprox PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
117
+ target_link_options(fastprox PUBLIC -fsanitize=address,undefined)
118
+ endif()
119
+
120
+ # ---------------------------------------------------------------- install
121
+ if(NOT FASTPROX_PYTHON)
122
+ include(CMakePackageConfigHelpers)
123
+ install(TARGETS fastprox EXPORT fastproxTargets
124
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
125
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
126
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
127
+ INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
128
+ install(FILES include/fastprox.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
129
+ install(EXPORT fastproxTargets NAMESPACE fastprox::
130
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/fastprox)
131
+ configure_package_config_file(cmake/fastproxConfig.cmake.in
132
+ ${CMAKE_CURRENT_BINARY_DIR}/fastproxConfig.cmake
133
+ INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/fastprox)
134
+ write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/fastproxConfigVersion.cmake
135
+ COMPATIBILITY SameMajorVersion)
136
+ install(FILES
137
+ ${CMAKE_CURRENT_BINARY_DIR}/fastproxConfig.cmake
138
+ ${CMAKE_CURRENT_BINARY_DIR}/fastproxConfigVersion.cmake
139
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/fastprox)
140
+ configure_file(cmake/fastprox.pc.in ${CMAKE_CURRENT_BINARY_DIR}/fastprox.pc @ONLY)
141
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fastprox.pc
142
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
143
+ endif()
144
+
145
+ # ---------------------------------------------------------------- tests
146
+ if(FASTPROX_BUILD_TESTS)
147
+ enable_testing()
148
+ add_subdirectory(tests/c)
149
+ endif()
150
+
151
+ if(FASTPROX_FUZZ)
152
+ if(NOT FASTPROX_BUILD_TESTS)
153
+ enable_testing()
154
+ endif()
155
+ add_subdirectory(fuzz)
156
+ endif()
157
+
158
+ # ---------------------------------------------------------------- python
159
+ if(FASTPROX_PYTHON)
160
+ add_subdirectory(bindings/python)
161
+ endif()
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Villacís
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastprox
3
+ Version: 0.1.0rc1
4
+ Summary: Exact proximal operators and projections with derivatives
5
+ Keywords: proximal operator,projection,total variation,optimization,bilevel,autodiff
6
+ Author-Email: David Villacís <david@villacis.net>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Operating System :: MacOS
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: C++
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
20
+ Project-URL: Homepage, https://github.com/dvillacis/fastprox
21
+ Project-URL: Documentation, https://github.com/dvillacis/fastprox/blob/master/docs/api.md
22
+ Project-URL: Changelog, https://github.com/dvillacis/fastprox/blob/master/CHANGELOG.md
23
+ Project-URL: Issues, https://github.com/dvillacis/fastprox/issues
24
+ Requires-Python: >=3.12
25
+ Requires-Dist: numpy>=1.26
26
+ Description-Content-Type: text/markdown
27
+
28
+ # fastprox
29
+
30
+ Exact proximal operators and projections with derivatives. A C++17 core
31
+ behind a plain-C API, with Python bindings.
32
+
33
+ - **Exact.** Direct algorithms (Condat's 1D TV and simplex projection, the
34
+ taut string, pool-adjacent-violators) instead of iterative solvers.
35
+ - **Differentiable.** Jacobian-vector and vector-Jacobian products in the
36
+ input, the step and the operator's parameters, for bilevel learning and
37
+ implicit differentiation.
38
+ - **Batched.** Applied along any axis of a float32 or float64 array, with rows
39
+ processed in parallel by OpenMP and results bit-identical for any number of
40
+ threads.
41
+ - **Permissive.** MIT licensed; every kernel is written from the papers.
42
+
43
+ ## Install
44
+
45
+ ```sh
46
+ pip install --pre fastprox # 0.1.0rc1, a release candidate
47
+ ```
48
+
49
+ Wheels are abi3 for CPython 3.12 and later on Linux (x86_64, aarch64), macOS
50
+ (arm64 from 14.0, x86_64 from 15.0) and Windows (amd64), with the OpenMP
51
+ runtime bundled.
52
+
53
+ ## Use
54
+
55
+ ```python
56
+ import numpy as np
57
+ import fastprox as fp
58
+
59
+ x = np.random.default_rng(0).normal(size=(64, 1000))
60
+
61
+ z = fp.tv1d(x, gamma=0.5) # 1D total variation along the last axis
62
+ p = fp.project_simplex(x, radius=1.0) # each row onto the simplex
63
+ s = fp.soft_threshold(x, lam=0.1)
64
+
65
+ # Operators with derivatives
66
+ op = fp.TV1D(weights=0.8)
67
+ z, ctx = op.prox(x, 0.5, keep=True)
68
+ dz = op.jvp(ctx, dx=np.ones_like(x)) # Jacobian-vector product
69
+ gx, grads = op.vjp(ctx, g=x) # vector-Jacobian product
70
+ print(grads.gamma, grads.weights) # gradients in the step and the weights
71
+ ```
72
+
73
+ ## Operators (0.1)
74
+
75
+ Soft thresholding (weighted), elastic net, Huber, group ℓ2, box,
76
+ nonnegative orthant, ℓ2 ball, simplex, weighted ℓ1 ball, ℓ∞ norm, isotonic
77
+ regression, and 1D total variation with per-edge weights. Each one has a
78
+ conjugate prox through the Moreau identity.
79
+
80
+ ## Documentation
81
+
82
+ - [API reference](https://github.com/dvillacis/fastprox/blob/master/docs/api.md) (C and Python)
83
+ - [Benchmarks](https://github.com/dvillacis/fastprox/blob/master/docs/benchmarks.md) against proxTV and Condat's C code
84
+ - [Design specification](https://github.com/dvillacis/fastprox/blob/master/docs/spec.md)
85
+ - [Roadmap](https://github.com/dvillacis/fastprox/blob/master/ROADMAP.md) and [changelog](https://github.com/dvillacis/fastprox/blob/master/CHANGELOG.md)
86
+
87
+ ## C library
88
+
89
+ ```sh
90
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
91
+ cmake --build build && cmake --install build --prefix /usr/local
92
+ ```
93
+
94
+ Then `find_package(fastprox)` and link `fastprox::fastprox`, or use
95
+ `pkg-config --cflags --libs fastprox`. The header is
96
+ [`include/fastprox.h`](https://github.com/dvillacis/fastprox/blob/master/include/fastprox.h).
97
+
98
+ ## License
99
+
100
+ MIT