Skip to content

Commit 2d58541

Browse files
author
peng.li24
committed
scipycpp v1.0.0: header-only C++ scipy API based on numpcpp+Eigen3+pocketfft
Modules: stats, integrate, optimize, interpolate, signal, spatial, special, linalg (Eigen3), fft (pocketfft). 36 bit-exact alignment tests passing (float64+float32).
0 parents  commit 2d58541

28 files changed

Lines changed: 1771 additions & 0 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
name: Bug Report
3+
about: Report a bug in scipycpp
4+
title: "[Bug] "
5+
labels: bug
6+
assignees: ""
7+
---
8+
9+
## Description
10+
11+
A clear description of the bug.
12+
13+
## Steps to Reproduce
14+
15+
1.
16+
2.
17+
3.
18+
19+
## Expected Behavior
20+
21+
What should have happened.
22+
23+
## Actual Behavior
24+
25+
What actually happened, including any error messages.
26+
27+
## Environment
28+
29+
- OS: [e.g. Ubuntu 22.04]
30+
- Compiler: [e.g. GCC 12, Clang 16]
31+
- CMake version:
32+
- Python version:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
name: Feature Request
3+
about: Suggest a new feature or enhancement
4+
title: "[Feature] "
5+
labels: enhancement
6+
assignees: ""
7+
---
8+
9+
## Problem
10+
11+
What problem would this feature solve?
12+
13+
## Proposed Solution
14+
15+
Describe the feature or API you'd like to see.
16+
17+
## Alternatives Considered
18+
19+
Any alternative approaches you've thought about.
20+
21+
## Additional Context
22+
23+
Any other context, references, or use cases.

.github/workflows/ci.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: CI
2+
3+
on:
4+
push: { branches: [master], tags: ['v*'] }
5+
pull_request: { branches: [master] }
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-22.04
10+
steps:
11+
- uses: actions/checkout@v4
12+
- name: Install dependencies
13+
run: |
14+
sudo apt-get update
15+
sudo apt-get install -y python3 python3-pip python3-dev libeigen3-dev
16+
pip3 install pybind11 numpy scipy pytest
17+
- name: Build test module
18+
run: make -C tests
19+
- name: Run tests
20+
run: make -C tests test
21+
22+
release:
23+
if: startsWith(github.ref, 'refs/tags/v')
24+
needs: test
25+
runs-on: ubuntu-22.04
26+
steps:
27+
- uses: actions/checkout@v4
28+
- name: Build .deb
29+
run: mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make deb
30+
- name: Publish
31+
uses: softprops/action-gh-release@v2
32+
with:
33+
files: build/scipycpp-dev-*.deb
34+
generate_release_notes: true
35+
env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" }

.gitignore

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Build directories
2+
build/
3+
cmake-build-*/
4+
5+
# Compiled output
6+
*.o
7+
*.so
8+
*.dylib
9+
*.dll
10+
*.a
11+
*.lib
12+
*.exe
13+
14+
# Python
15+
__pycache__/
16+
*.py[cod]
17+
*$py.class
18+
.pytest_cache/
19+
20+
# IDE
21+
.vscode/
22+
.idea/
23+
*.swp
24+
*.swo
25+
*~
26+
.DS_Store
27+
28+
# CMake generated
29+
CMakeCache.txt
30+
CMakeFiles/
31+
cmake_install.cmake
32+
Makefile
33+
!tests/Makefile
34+
CTestTestfile.cmake
35+
compile_commands.json
36+
37+
# Packaging
38+
*.deb
39+
*.rpm
40+
41+
# CMake packaging artifacts (in-source builds)
42+
CPackConfig.cmake
43+
CPackSourceConfig.cmake
44+
_CPack_Packages/
45+
install_manifest.txt
46+
scipycpp-config-version.cmake
47+
scipycpp-config.cmake

CMakeLists.txt

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
project(scipycpp VERSION 1.0.0 LANGUAGES CXX)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
set(CMAKE_CXX_EXTENSIONS OFF)
7+
8+
# ---- INTERFACE target for find_package consumers ----------------------------
9+
add_library(scipycpp INTERFACE)
10+
target_include_directories(scipycpp INTERFACE
11+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
12+
$<INSTALL_INTERFACE:include/scipycpp>
13+
)
14+
target_compile_features(scipycpp INTERFACE cxx_std_17)
15+
16+
# Transitive dependencies: scipycpp → pocketfft, Eigen3, numpcpp
17+
target_link_libraries(scipycpp INTERFACE
18+
pocketfft::pocketfft
19+
Eigen3::Eigen
20+
numpycpp::numpycpp
21+
)
22+
23+
# ---- Install — header-only C++ library --------------------------------------
24+
install(DIRECTORY scipy
25+
DESTINATION include/scipycpp
26+
FILES_MATCHING PATTERN "*.h"
27+
)
28+
install(DIRECTORY pycpp
29+
DESTINATION include/scipycpp
30+
FILES_MATCHING PATTERN "*.h"
31+
PATTERN ".pytest_cache" EXCLUDE
32+
)
33+
34+
include(CMakePackageConfigHelpers)
35+
configure_package_config_file(
36+
scipycpp-config.cmake.in
37+
${CMAKE_CURRENT_BINARY_DIR}/scipycpp-config.cmake
38+
INSTALL_DESTINATION lib/cmake/scipycpp
39+
)
40+
write_basic_package_version_file(
41+
${CMAKE_CURRENT_BINARY_DIR}/scipycpp-config-version.cmake
42+
VERSION ${PROJECT_VERSION}
43+
COMPATIBILITY SameMajorVersion
44+
)
45+
install(FILES
46+
${CMAKE_CURRENT_BINARY_DIR}/scipycpp-config.cmake
47+
${CMAKE_CURRENT_BINARY_DIR}/scipycpp-config-version.cmake
48+
DESTINATION lib/cmake/scipycpp
49+
)
50+
51+
# ---- Docs & examples -------------------------------------------------------
52+
install(FILES README.md DESTINATION share/scipycpp)
53+
install(DIRECTORY example/ DESTINATION share/scipycpp/example)
54+
55+
# ---- CPack DEB packaging ----------------------------------------------------
56+
set(CPACK_PACKAGE_NAME "scipycpp-dev")
57+
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
58+
set(CPACK_PACKAGE_VENDOR "array2d")
59+
set(CPACK_PACKAGE_CONTACT "array2d")
60+
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "C++ header-only scipy API — based on numpcpp+Eigen3+pocketfft")
61+
set(CPACK_GENERATOR "DEB")
62+
set(CPACK_DEB_COMPONENT_INSTALL OFF)
63+
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "array2d")
64+
set(CPACK_DEBIAN_PACKAGE_SECTION "libdevel")
65+
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
66+
set(CPACK_DEBIAN_FILE_NAME "scipycpp-dev-${CPACK_PACKAGE_VERSION}-Linux.deb")
67+
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")
68+
69+
# Recommend the required dependencies
70+
set(CPACK_DEBIAN_PACKAGE_DEPENDS "numpycpp-dev, libeigen3-dev, pocketfft-dev")
71+
72+
include(CPack)
73+
add_custom_target(deb COMMAND cpack -G DEB
74+
COMMENT "Packaging scipycpp-dev-${CPACK_PACKAGE_VERSION}-Linux.deb")
75+
76+
message(STATUS "scipycpp v${PROJECT_VERSION} (header-only C++ library)")
77+
message(STATUS " Dependencies: numpcpp + Eigen3 + pocketfft")
78+
message(STATUS " DEB: make deb → scipycpp-dev-${CPACK_PACKAGE_VERSION}-Linux.deb")
79+
message(STATUS " Test: cd tests/ && make → build + run alignment tests")

README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# scipycpp
2+
3+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4+
[![C++17](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://en.cppreference.com/w/cpp/17)
5+
6+
## Overview
7+
8+
`scipycpp` is a **header-only C++ library** implementing scipy's core APIs with
9+
**bit-level precision alignment** against Python scipy.
10+
11+
Built on proven C++ libraries:
12+
- **[numpycpp](https://github.com/array2d/numpycpp)** — numpy primitives + SVML bridge
13+
- **[Eigen3](https://eigen.tuxfamily.org/)** — linear algebra (solve, inv, det, eig, svd, cholesky)
14+
- **[pocketfft](https://github.com/mreineck/pocketfft)** — FFT (same library numpy/scipy use internally)
15+
16+
## Quick Start
17+
18+
```cpp
19+
#include "scipy/core.h"
20+
21+
// Integration
22+
auto [val, err] = scipy::integrate::quad([](double x){ return x*x; }, 0.0, 4.0);
23+
24+
// Stats
25+
std::vector<double> pdf(3);
26+
scipy::stats::norm_pdf(data, pdf.data(), 3);
27+
28+
// Linalg (Eigen3-backed)
29+
double A[4] = {2,1,1,3}, b[2] = {5,6}, x[2];
30+
scipy::linalg::solve(A, x, b, 2);
31+
32+
// FFT (pocketfft-backed — same as numpy/scipy)
33+
scipy::fft::fft(signal, spectrum, 4);
34+
```
35+
36+
### Dependencies
37+
38+
```bash
39+
# Install dependencies
40+
sudo dpkg -i numpycpp-dev-*.deb
41+
sudo apt-get install libeigen3-dev
42+
sudo dpkg -i pocketfft-dev-*.deb
43+
44+
# Install scipycpp
45+
mkdir build && cd build
46+
cmake .. && make deb
47+
sudo dpkg -i scipycpp-dev-*.deb
48+
```
49+
50+
```cmake
51+
find_package(scipycpp REQUIRED)
52+
target_link_libraries(myapp PRIVATE scipycpp::scipycpp)
53+
```
54+
55+
## Modules
56+
57+
| Module | Backend | Key APIs |
58+
|--------|---------|----------|
59+
| `integrate` | numpcpp + pure C++ | quad, simpson, trapezoid |
60+
| `optimize` | pure C++ | minimize_scalar (Brent), root_scalar |
61+
| `interpolate` | pure C++ | interp1d, CubicSpline |
62+
| `signal` | numpcpp | convolve, correlate |
63+
| `stats` | **numpycpp SVML** | norm.pdf/cdf/ppf (bit-exact) |
64+
| `spatial` | pure C++ | euclidean, cosine, manhattan, KDTree |
65+
| `special` | C++17 std | erf, gamma, beta, digamma, logsumexp |
66+
| `linalg` | **Eigen3** | solve, inv, det, eigvalsh, cholesky, svd |
67+
| `fft` | **pocketfft** | fft, ifft, rfft, irfft |
68+
69+
## Testing
70+
71+
```bash
72+
cd tests && make && make test
73+
```
74+
75+
## License
76+
77+
MIT

bench/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
project(scipycpp-bench LANGUAGES CXX)
3+
set(CMAKE_CXX_STANDARD 17)
4+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
5+
6+
include(FetchContent)
7+
FetchContent_Declare(googlebench
8+
GIT_REPOSITORY https://github.com/google/benchmark.git
9+
GIT_TAG v1.8.3
10+
)
11+
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
12+
FetchContent_MakeAvailable(googlebench)
13+
14+
add_executable(bench_core bench_core.cpp)
15+
target_include_directories(bench_core PRIVATE ..)
16+
target_link_libraries(bench_core PRIVATE benchmark::benchmark)

bench/bench_core.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include "scipy/core.h"
2+
#include <benchmark/benchmark.h>
3+
#include <vector>
4+
#include <random>
5+
6+
static void BM_trapezoid(benchmark::State& state) {
7+
size_t n = state.range(0);
8+
std::vector<double> y(n);
9+
std::mt19937 rng(42);
10+
std::uniform_real_distribution<double> dist(1.0, 100.0);
11+
for (size_t i = 0; i < n; ++i) y[i] = dist(rng);
12+
for (auto _ : state) {
13+
double r = scipy::integrate::trapezoid(y.data(), n);
14+
benchmark::DoNotOptimize(r);
15+
}
16+
state.SetItemsProcessed(state.iterations() * n);
17+
}
18+
BENCHMARK(BM_trapezoid)->Range(1<<10, 1<<20);
19+
20+
static void BM_normpdf(benchmark::State& state) {
21+
size_t n = state.range(0);
22+
std::vector<double> x(n), r(n);
23+
std::mt19937 rng(42);
24+
std::normal_distribution<double> dist(0.0, 1.0);
25+
for (size_t i = 0; i < n; ++i) x[i] = dist(rng);
26+
for (auto _ : state) {
27+
scipy::stats::norm_pdf(x.data(), r.data(), n);
28+
benchmark::DoNotOptimize(r.data());
29+
}
30+
state.SetItemsProcessed(state.iterations() * n);
31+
}
32+
BENCHMARK(BM_normpdf)->Range(1<<10, 1<<20);
33+
BENCHMARK_MAIN();

example/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
project(scipycpp_example LANGUAGES CXX)
3+
find_package(scipycpp REQUIRED)
4+
find_package(numpycpp REQUIRED)
5+
find_package(Eigen3 REQUIRED)
6+
find_package(pocketfft REQUIRED)
7+
add_executable(example main.cpp)
8+
target_link_libraries(example PRIVATE scipycpp::scipycpp numpycpp::numpycpp Eigen3::Eigen pocketfft::pocketfft)

example/main.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include <scipy/core.h>
2+
#include <iostream>
3+
#include <vector>
4+
#include <complex>
5+
6+
int main() {
7+
// --- Integration ---
8+
std::vector<double> y = {0.0, 1.0, 4.0, 9.0, 16.0};
9+
std::cout << "trapezoid: " << scipy::integrate::trapezoid(y.data(), y.size()) << std::endl;
10+
std::cout << "simpson: " << scipy::integrate::simpson(y.data(), nullptr, y.size()) << std::endl;
11+
12+
auto [val, err] = scipy::integrate::quad([](double x) { return x * x; }, 0.0, 4.0);
13+
std::cout << "quad(x^2, 0,4): " << val << " (err=" << err << ")" << std::endl;
14+
15+
// --- Stats ---
16+
std::vector<double> x = {-1.0, 0.0, 1.0};
17+
std::vector<double> pdf(3);
18+
scipy::stats::norm_pdf(x.data(), pdf.data(), 3);
19+
std::cout << "norm.pdf: " << pdf[0] << " " << pdf[1] << " " << pdf[2] << std::endl;
20+
21+
// --- Linalg ---
22+
double A[4] = {2.0, 1.0, 1.0, 3.0};
23+
double b[2] = {5.0, 6.0};
24+
double xs[2];
25+
scipy::linalg::solve(A, xs, b, 2);
26+
std::cout << "solve: x=[" << xs[0] << ", " << xs[1] << "]" << std::endl;
27+
28+
// --- FFT ---
29+
std::vector<std::complex<double>> sig = {{1,0}, {2,0}, {3,0}, {4,0}};
30+
std::vector<std::complex<double>> spec(4);
31+
scipy::fft::fft(sig.data(), spec.data(), 4);
32+
std::cout << "fft:";
33+
for (auto& v : spec) std::cout << " (" << v.real() << "," << v.imag() << ")";
34+
std::cout << std::endl;
35+
36+
return 0;
37+
}

0 commit comments

Comments
 (0)