Skip to content

Commit 8a4163e

Browse files
committed
implement dpnp.piecewise
1 parent 77a90bf commit 8a4163e

File tree

13 files changed

+1581
-3
lines changed

13 files changed

+1581
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
* Added `timeout-minutes` property to GitHub jobs [#2526](https://github.com/IntelPython/dpnp/pull/2526)
1616
* Added implementation of `dpnp.ndarray.data` and `dpnp.ndarray.data.ptr` attributes [#2521](https://github.com/IntelPython/dpnp/pull/2521)
1717
* Added `dpnp.ndarray.__contains__` method [#2534](https://github.com/IntelPython/dpnp/pull/2534)
18+
* Added implementation of `dpnp.piecewise` [#2550](https://github.com/IntelPython/dpnp/pull/2550)
1819

1920
### Changed
2021

dpnp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ endfunction()
5858
add_subdirectory(backend)
5959
add_subdirectory(backend/extensions/blas)
6060
add_subdirectory(backend/extensions/fft)
61+
add_subdirectory(backend/extensions/functional)
6162
add_subdirectory(backend/extensions/indexing)
6263
add_subdirectory(backend/extensions/lapack)
6364
add_subdirectory(backend/extensions/statistics)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# *****************************************************************************
2+
# Copyright (c) 2025, Intel Corporation
3+
# All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
# - Redistributions of source code must retain the above copyright notice,
8+
# this list of conditions and the following disclaimer.
9+
# - Redistributions in binary form must reproduce the above copyright notice,
10+
# this list of conditions and the following disclaimer in the documentation
11+
# and/or other materials provided with the distribution.
12+
#
13+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23+
# THE POSSIBILITY OF SUCH DAMAGE.
24+
# *****************************************************************************
25+
26+
27+
set(python_module_name _functional_impl)
28+
set(_module_src
29+
${CMAKE_CURRENT_SOURCE_DIR}/piecewise.cpp
30+
${CMAKE_CURRENT_SOURCE_DIR}/functional_py.cpp
31+
)
32+
33+
pybind11_add_module(${python_module_name} MODULE ${_module_src})
34+
add_sycl_to_target(TARGET ${python_module_name} SOURCES ${_module_src})
35+
36+
if(_dpnp_sycl_targets)
37+
# make fat binary
38+
target_compile_options(
39+
${python_module_name}
40+
PRIVATE
41+
${_dpnp_sycl_target_compile_options}
42+
)
43+
target_link_options(
44+
${python_module_name}
45+
PRIVATE
46+
${_dpnp_sycl_target_link_options}
47+
)
48+
endif()
49+
50+
if (WIN32)
51+
if (${CMAKE_VERSION} VERSION_LESS "3.27")
52+
# this is a work-around for target_link_options inserting option after -link option, cause
53+
# linker to ignore it.
54+
set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -fsycl-device-code-split=per_kernel")
55+
endif()
56+
endif()
57+
58+
set_target_properties(${python_module_name} PROPERTIES CMAKE_POSITION_INDEPENDENT_CODE ON)
59+
60+
target_include_directories(${python_module_name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../include)
61+
target_include_directories(${python_module_name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../src)
62+
63+
target_include_directories(${python_module_name} PUBLIC ${Dpctl_INCLUDE_DIR})
64+
target_include_directories(${python_module_name} PUBLIC ${Dpctl_TENSOR_INCLUDE_DIR})
65+
66+
if (WIN32)
67+
target_compile_options(${python_module_name} PRIVATE
68+
/clang:-fno-approx-func
69+
/clang:-fno-finite-math-only
70+
)
71+
else()
72+
target_compile_options(${python_module_name} PRIVATE
73+
-fno-approx-func
74+
-fno-finite-math-only
75+
)
76+
endif()
77+
78+
target_link_options(${python_module_name} PUBLIC -fsycl-device-code-split=per_kernel)
79+
80+
if (DPNP_GENERATE_COVERAGE)
81+
target_link_options(${python_module_name} PRIVATE -fprofile-instr-generate -fcoverage-mapping)
82+
endif()
83+
84+
if (DPNP_WITH_REDIST)
85+
set_target_properties(${python_module_name} PROPERTIES INSTALL_RPATH "$ORIGIN/../../../../../../")
86+
endif()
87+
88+
install(TARGETS ${python_module_name}
89+
DESTINATION "dpnp/backend/extensions/functional"
90+
)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//*****************************************************************************
2+
// Copyright (c) 2025, Intel Corporation
3+
// All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are met:
7+
// - Redistributions of source code must retain the above copyright notice,
8+
// this list of conditions and the following disclaimer.
9+
// - Redistributions in binary form must reproduce the above copyright notice,
10+
// this list of conditions and the following disclaimer in the documentation
11+
// and/or other materials provided with the distribution.
12+
//
13+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23+
// THE POSSIBILITY OF SUCH DAMAGE.
24+
//*****************************************************************************
25+
//
26+
// This file defines functions of dpnp.backend._functional_impl extensions
27+
//
28+
//*****************************************************************************
29+
30+
#include <pybind11/pybind11.h>
31+
#include <pybind11/stl.h>
32+
33+
#include "piecewise.hpp"
34+
35+
namespace functional_ns = dpnp::extensions::functional;
36+
namespace py = pybind11;
37+
38+
PYBIND11_MODULE(_functional_impl, m)
39+
{
40+
{
41+
functional_ns::init_piecewise_dispatch_vectors();
42+
43+
m.def("_piecewise", functional_ns::py_piecewise,
44+
"Call piecewise kernel", py::arg("sycl_queue"), py::arg("value"),
45+
py::arg("condition"), py::arg("result"),
46+
py::arg("depends") = py::list());
47+
}
48+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
//*****************************************************************************
2+
// Copyright (c) 2025, Intel Corporation
3+
// All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are met:
7+
// - Redistributions of source code must retain the above copyright notice,
8+
// this list of conditions and the following disclaimer.
9+
// - Redistributions in binary form must reproduce the above copyright notice,
10+
// this list of conditions and the following disclaimer in the documentation
11+
// and/or other materials provided with the distribution.
12+
//
13+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23+
// THE POSSIBILITY OF SUCH DAMAGE.
24+
//*****************************************************************************
25+
26+
#include "piecewise.hpp"
27+
28+
#include "utils/output_validation.hpp"
29+
#include "utils/type_dispatch.hpp"
30+
#include "utils/type_utils.hpp"
31+
32+
#include <pybind11/numpy.h>
33+
#include <sycl/sycl.hpp>
34+
35+
namespace dpnp::extensions::functional
36+
{
37+
namespace dpctl_td_ns = dpctl::tensor::type_dispatch;
38+
39+
typedef sycl::event (*piecewise_fn_ptr_t)(sycl::queue &,
40+
const py::object &,
41+
const std::size_t,
42+
const char *,
43+
char *,
44+
const std::vector<sycl::event> &);
45+
46+
static piecewise_fn_ptr_t piecewise_dispatch_vector[dpctl_td_ns::num_types];
47+
48+
template <typename T>
49+
class PiecewiseFunctor
50+
{
51+
private:
52+
const T val;
53+
const bool *cond = nullptr;
54+
T *res = nullptr;
55+
56+
public:
57+
PiecewiseFunctor(const T val, const bool *cond, T *res)
58+
: val(val), cond(cond), res(res)
59+
{
60+
}
61+
62+
void operator()(sycl::id<1> id) const
63+
{
64+
const auto i = id.get(0);
65+
if (cond[i]) {
66+
res[i] = val;
67+
}
68+
}
69+
};
70+
71+
template <typename T>
72+
sycl::event piecewise_impl(sycl::queue &exec_q,
73+
const py::object &value,
74+
const std::size_t nelems,
75+
const char *condition,
76+
char *result,
77+
const std::vector<sycl::event> &depends)
78+
{
79+
dpctl::tensor::type_utils::validate_type_for_device<T>(exec_q);
80+
81+
py::object type_obj = py::type::of(value);
82+
std::string type_name = py::str(type_obj.attr("__name__"));
83+
84+
T *res = reinterpret_cast<T *>(result);
85+
const bool *cond = reinterpret_cast<const bool *>(condition);
86+
T val = py::cast<const T>(value);
87+
88+
sycl::event piecewise_ev = exec_q.submit([&](sycl::handler &cgh) {
89+
cgh.depends_on(depends);
90+
91+
using PiecewiseKernel = PiecewiseFunctor<T>;
92+
cgh.parallel_for<PiecewiseKernel>(sycl::range<1>(nelems),
93+
PiecewiseKernel(val, cond, res));
94+
});
95+
96+
return piecewise_ev;
97+
}
98+
99+
/**
100+
* @brief A factory to define pairs of supported types for which
101+
* piecewise function is available.
102+
*
103+
* @tparam T Type of input vector `a` and of result vector `y`.
104+
*/
105+
template <typename T>
106+
struct PiecewiseOutputType
107+
{
108+
using value_type = typename std::disjunction<
109+
dpctl_td_ns::TypeMapResultEntry<T, bool>,
110+
dpctl_td_ns::TypeMapResultEntry<T, std::uint8_t>,
111+
dpctl_td_ns::TypeMapResultEntry<T, std::int8_t>,
112+
dpctl_td_ns::TypeMapResultEntry<T, std::uint16_t>,
113+
dpctl_td_ns::TypeMapResultEntry<T, std::int16_t>,
114+
dpctl_td_ns::TypeMapResultEntry<T, std::uint32_t>,
115+
dpctl_td_ns::TypeMapResultEntry<T, std::int32_t>,
116+
dpctl_td_ns::TypeMapResultEntry<T, std::uint64_t>,
117+
dpctl_td_ns::TypeMapResultEntry<T, std::int64_t>,
118+
dpctl_td_ns::TypeMapResultEntry<T, sycl::half>,
119+
dpctl_td_ns::TypeMapResultEntry<T, float>,
120+
dpctl_td_ns::TypeMapResultEntry<T, double>,
121+
dpctl_td_ns::TypeMapResultEntry<T, std::complex<float>>,
122+
dpctl_td_ns::TypeMapResultEntry<T, std::complex<double>>,
123+
dpctl_td_ns::DefaultResultEntry<void>>::result_type;
124+
};
125+
126+
template <typename fnT, typename T>
127+
struct PiecewiseFactory
128+
{
129+
fnT get()
130+
{
131+
if constexpr (std::is_same_v<
132+
typename PiecewiseOutputType<T>::value_type, void>) {
133+
return nullptr;
134+
}
135+
else {
136+
return piecewise_impl<T>;
137+
}
138+
}
139+
};
140+
141+
std::pair<sycl::event, sycl::event>
142+
py_piecewise(sycl::queue &exec_q,
143+
const py::object &value,
144+
const dpctl::tensor::usm_ndarray &condition,
145+
const dpctl::tensor::usm_ndarray &result,
146+
const std::vector<sycl::event> &depends)
147+
{
148+
dpctl::tensor::validation::CheckWritable::throw_if_not_writable(result);
149+
150+
const int res_nd = result.get_ndim();
151+
const int cond_nd = condition.get_ndim();
152+
if (res_nd != cond_nd) {
153+
throw py::value_error(
154+
"Condition and result arrays must have the same dimension.");
155+
}
156+
157+
if (!dpctl::utils::queues_are_compatible(
158+
exec_q, {condition.get_queue(), result.get_queue()}))
159+
{
160+
throw py::value_error(
161+
"Execution queue is not compatible with allocation queue.");
162+
}
163+
164+
const bool is_result_c_contig = result.is_c_contiguous();
165+
if (!is_result_c_contig) {
166+
throw py::value_error("The result array is not c-contiguous.");
167+
}
168+
169+
const py::ssize_t *res_shape = result.get_shape_raw();
170+
const py::ssize_t *cond_shape = condition.get_shape_raw();
171+
172+
const bool shapes_equal =
173+
std::equal(res_shape, res_shape + res_nd, cond_shape);
174+
if (!shapes_equal) {
175+
throw py::value_error(
176+
"Condition and result arrays must have the same shape.");
177+
}
178+
179+
const std::size_t nelems = result.get_size();
180+
if (nelems == 0) {
181+
return std::make_pair(sycl::event{}, sycl::event{});
182+
}
183+
184+
const int result_typenum = result.get_typenum();
185+
auto array_types = dpctl_td_ns::usm_ndarray_types();
186+
const int result_type_id = array_types.typenum_to_lookup_id(result_typenum);
187+
auto piecewise_fn = piecewise_dispatch_vector[result_type_id];
188+
189+
if (piecewise_fn == nullptr) {
190+
throw std::runtime_error("Type of given array is not supported");
191+
}
192+
193+
const char *condition_typeless_ptr = condition.get_data();
194+
char *result_typeless_ptr = result.get_data();
195+
196+
sycl::event piecewise_ev =
197+
piecewise_fn(exec_q, value, nelems, condition_typeless_ptr,
198+
result_typeless_ptr, depends);
199+
sycl::event args_ev =
200+
dpctl::utils::keep_args_alive(exec_q, {result}, {piecewise_ev});
201+
202+
return std::make_pair(args_ev, piecewise_ev);
203+
}
204+
205+
void init_piecewise_dispatch_vectors(void)
206+
{
207+
dpctl_td_ns::DispatchVectorBuilder<piecewise_fn_ptr_t, PiecewiseFactory,
208+
dpctl_td_ns::num_types>
209+
contig;
210+
contig.populate_dispatch_vector(piecewise_dispatch_vector);
211+
212+
return;
213+
}
214+
215+
} // namespace dpnp::extensions::functional

0 commit comments

Comments
 (0)