Skip to content
This repository was archived by the owner on Sep 26, 2025. It is now read-only.

Commit ab9e22b

Browse files
setuptools example working
1 parent dff0a29 commit ab9e22b

File tree

14 files changed

+357
-0
lines changed

14 files changed

+357
-0
lines changed

common_ext/cy_kde.pyx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# distutils: language = c++
2+
# cython: language_level=3
3+
4+
cimport cython
5+
from libc.stdint cimport uint16_t
6+
7+
cdef extern from "CL/sycl.hpp" namespace "sycl":
8+
cdef cppclass dpcpp_queue "sycl::queue":
9+
pass
10+
11+
12+
cdef extern from "kde.hpp" namespace "example":
13+
void kernel_density_estimate[T](
14+
dpcpp_queue, # execution queue
15+
uint16_t, # datadimensionality
16+
const T *, # evaluation points
17+
T *, # output for kde values
18+
size_t, # number of evaluation points
19+
const T *, # observation data
20+
size_t, # number of observations
21+
T # smoothing parameter
22+
)
23+
24+
cimport dpctl as c_dpctl
25+
import numpy as np
26+
27+
def kde_eval(
28+
c_dpctl.SyclQueue py_q,
29+
cython.floating[:, :] x,
30+
cython.floating[:, :] x_data,
31+
cython.floating h
32+
):
33+
cdef cython.floating[:] f
34+
35+
cdef c_dpctl.DPCTLSyclQueueRef qref = py_q.get_queue_ref()
36+
cdef dpcpp_queue* sycl_queue = <dpcpp_queue *>qref
37+
38+
if x.shape[1] != x_data.shape[1]:
39+
raise ValueError("Evaluation data and observation data have different dimensions")
40+
41+
if (x.shape[0] == 0 or x.shape[1] == 0 or
42+
x_data.shape[0] == 0 or x_data.shape[1] == 0):
43+
raise ValueError("Evaluation and observation data must be non-empty")
44+
45+
if cython.floating is float:
46+
f = np.empty(x.shape[0], dtype=np.float)
47+
else:
48+
f = np.empty(x.shape[0], dtype=np.double)
49+
50+
kernel_density_estimate(
51+
sycl_queue[0],
52+
<uint16_t>x.shape[1],
53+
&x[0, 0],
54+
&f[0],
55+
f.size,
56+
&x_data[0,0],
57+
x_data.shape[0],
58+
h
59+
)
60+
61+
return np.asarray(f)

common_ext/py_kde.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#include <CL/sycl.hpp>
2+
#include <cstdint>
3+
#include <limits>
4+
#include <pybind11/pybind11.h>
5+
#include <pybind11/numpy.h>
6+
#include "dpctl4pybind11.hpp"
7+
#include "kde.hpp"
8+
9+
namespace py = pybind11;
10+
11+
template <typename T>
12+
py::array_t<T>
13+
py_kde_eval_t(
14+
sycl::queue q,
15+
py::array_t<T, py::array::c_style|py::array::forcecast> x,
16+
py::array_t<T, py::array::c_style|py::array::forcecast> data,
17+
T h)
18+
{
19+
py::buffer_info x_pybuf = x.request();
20+
py::buffer_info data_pybuf = data.request();
21+
22+
if (x_pybuf.ndim != 2 || data_pybuf.ndim != 2) {
23+
throw py::value_error("Input arrays must be matrices");
24+
}
25+
26+
auto dim = x_pybuf.shape[1];
27+
constexpr auto dim_max = static_cast<decltype(dim)>(std::numeric_limits<std::uint16_t>::max());
28+
29+
if (dim != data_pybuf.shape[1] || dim > dim_max) {
30+
throw py::value_error("Inputs have inconsistent functionality or too large a width");
31+
}
32+
33+
auto n = x_pybuf.shape[0];
34+
auto n_data = data_pybuf.shape[0];
35+
36+
T *x_ptr = reinterpret_cast<T *>(x_pybuf.ptr);
37+
T *data_ptr = reinterpret_cast<T *>(data_pybuf.ptr);
38+
39+
py::array_t<T, py::array::c_style> f({n}, {sizeof(T)});
40+
41+
example::kernel_density_estimate<T>(
42+
q, static_cast<std::uint16_t>(dim),
43+
const_cast<const T*>(x_ptr),
44+
f.mutable_data(0),
45+
n,
46+
const_cast<const T*>(data_ptr),
47+
n_data,
48+
h);
49+
50+
return f;
51+
}
52+
53+
py::array
54+
py_kde_eval(
55+
sycl::queue exec_q,
56+
py::array x,
57+
py::array data,
58+
py::object h)
59+
{
60+
if (py::isinstance<py::array_t<double>>(x) && py::isinstance<py::array_t<double>>(data)) {
61+
std::cout << "Dealing with doubles" << std::endl;
62+
return py_kde_eval_t<double>(
63+
exec_q,
64+
py::cast<py::array_t<double>>(x),
65+
py::cast<py::array_t<double>>(data),
66+
py::cast<double>(h)
67+
);
68+
} else if (py::isinstance<py::array_t<float>>(x) && py::isinstance<py::array_t<float>>(data)) {
69+
return py_kde_eval_t<float>(
70+
exec_q,
71+
py::cast<py::array_t<float>>(x),
72+
py::cast<py::array_t<float>>(data),
73+
py::cast<float>(h)
74+
);
75+
} else {
76+
throw py::type_error("Both arrays must be either single or double precision floating point types");
77+
}
78+
}
79+
80+
81+
PYBIND11_MODULE(_pybind11_kde, m) {
82+
import_dpctl();
83+
84+
m.def("kde_eval", &py_kde_eval,
85+
"Evaluate kernel density estimation function for every argument for the dataset",
86+
py::arg("exec_q"),
87+
py::arg("x"),
88+
py::arg("data"),
89+
py::arg("h") );
90+
}

kde_setuptools/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
__pycache__
2+
build
3+
*.so
4+
src/cy_kde.cpp
5+
*~
6+
kde_setuptools.egg-info

kde_setuptools/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Building this extension
2+
3+
Assuming `Cython` and `pybind11` are installed and DCP++ has been activated:
4+
5+
```bash
6+
CC=dpcpp LDSHARED="dpcpp --shared" python setup.py develop
7+
pytest -m tests
8+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from ._cython_kde import kde_eval as cython_kde_eval
2+
from ._pybind11_kde import kde_eval as pybind11_kde_eval
3+
4+
__all__ = ["cyton_kde_eval", "pybind11_kde_eval"]

kde_setuptools/setup.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from setuptools import setup, Extension
2+
from pybind11.setup_helpers import Pybind11Extension
3+
import dpctl
4+
5+
ext_modules = [
6+
Pybind11Extension(
7+
"kde_setuptools._pybind11_kde",
8+
["./src/pybind11_kde.cpp",],
9+
include_dirs=["./src", dpctl.get_include(),]
10+
),
11+
Extension(
12+
"kde_setuptools._cython_kde",
13+
["./src/cy_kde.pyx",],
14+
include_dirs=["./src", dpctl.get_include(),],
15+
language="c++"
16+
),
17+
]
18+
19+
setup(
20+
name="kde_setuptools",
21+
author="Intel Corporation",
22+
version="0.0.1",
23+
description="An example of data-parallel extensions built with oneAPI",
24+
long_description="""
25+
Example of using oneAPI to build data-parallel extension using setuptools.
26+
27+
Part of oneAPI for Scientific Python community virtual poster.
28+
Also see README.md
29+
""",
30+
license="Apache 2.0",
31+
url="https://intelpython.github.io/oneAPI-for-SciPy",
32+
ext_modules=ext_modules
33+
)

kde_setuptools/src/cy_kde.pyx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../common_ext/cy_kde.pyx

kde_setuptools/src/kde.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../common_src/kde.hpp
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../common_ext/py_kde.cpp
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import numpy as np
2+
import dpctl
3+
4+
from kde_setuptools import cython_kde_eval as kde_eval
5+
6+
import pytest
7+
8+
def ref_kde(x, data, h):
9+
"""
10+
Reference NumPy implementation for KDE evaluation
11+
"""
12+
assert x.ndim == 2 and data.ndim == 2
13+
assert x.shape[1] == data.shape[1]
14+
dim = x.shape[1]
15+
n_data = data.shape[0]
16+
return np.exp(
17+
np.square(x[:, np.newaxis, :]-data).sum(axis=-1)/(-2*h*h)
18+
).sum(axis=1)/(np.sqrt(2*np.pi)*h)**dim / n_data
19+
20+
21+
def test_1d():
22+
try:
23+
q = dpctl.SyclQueue()
24+
except dpctl.SyclQueueCreationError:
25+
pytest.skip("Execution queue could not be created, skipping...")
26+
27+
x = np.linspace(0.1, 0.9, num=15).reshape((-1, 1))
28+
data = np.random.rand(128,1)
29+
30+
f_cy = kde_eval(q, x, data, 0.1)
31+
f_ref = ref_kde(x, data, 0.1)
32+
33+
assert np.allclose(f_cy, f_ref)
34+
35+
36+
def test_2d():
37+
try:
38+
q = dpctl.SyclQueue()
39+
except dpctl.SyclQueueCreationError:
40+
pytest.skip("Execution queue could not be created, skipping...")
41+
42+
x = np.dstack(
43+
np.meshgrid(
44+
np.linspace(0.1, 0.9, num=7),
45+
np.linspace(0.1, 0.9, num=7)
46+
)
47+
).reshape(-1, 2)
48+
49+
data = np.random.rand(128*128, 2)
50+
51+
f_cy = kde_eval(q, x, data, 0.05)
52+
f_ref = ref_kde(x, data, 0.05)
53+
54+
assert np.allclose(f_cy, f_ref)
55+
56+
57+
def test_3d():
58+
try:
59+
q = dpctl.SyclQueue()
60+
except dpctl.SyclQueueCreationError:
61+
pytest.skip("Execution queue could not be created, skipping...")
62+
63+
x = np.dstack(
64+
np.meshgrid(
65+
np.linspace(0.1, 0.9, num=7),
66+
np.linspace(0.1, 0.9, num=7),
67+
np.linspace(0.1, 0.9, num=7)
68+
)
69+
).reshape(-1, 3)
70+
71+
data = np.random.rand(16000,3)
72+
73+
f_cy = kde_eval(q, x, data, 0.01)
74+
f_ref = ref_kde(x, data, 0.01)
75+
76+
assert np.allclose(f_cy, f_ref)

0 commit comments

Comments
 (0)