DPNP C++ backend kernel library 0.18.0dev0
Data Parallel Extension for NumPy*
Loading...
Searching...
No Matches
gesvd_common_utils.hpp
1//*****************************************************************************
2// Copyright (c) 2024-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#pragma once
27#include <oneapi/mkl.hpp>
28#include <pybind11/pybind11.h>
29
30// dpctl tensor headers
31#include "utils/memory_overlap.hpp"
32#include "utils/output_validation.hpp"
33#include "utils/type_dispatch.hpp"
34
35#include "common_helpers.hpp"
36
37namespace dpnp::extensions::lapack::gesvd_utils
38{
39namespace dpctl_td_ns = dpctl::tensor::type_dispatch;
40namespace py = pybind11;
41
42// Converts a given character code (ord) to the corresponding
43// oneapi::mkl::jobsvd enumeration value
44inline oneapi::mkl::jobsvd process_job(const std::int8_t job_val)
45{
46 switch (job_val) {
47 case 'A':
48 return oneapi::mkl::jobsvd::vectors;
49 case 'S':
50 return oneapi::mkl::jobsvd::somevec;
51 case 'O':
52 return oneapi::mkl::jobsvd::vectorsina;
53 case 'N':
54 return oneapi::mkl::jobsvd::novec;
55 default:
56 throw std::invalid_argument("Unknown value for job");
57 }
58}
59
60inline void common_gesvd_checks(sycl::queue &exec_q,
61 const dpctl::tensor::usm_ndarray &a_array,
62 const dpctl::tensor::usm_ndarray &out_s,
63 const dpctl::tensor::usm_ndarray &out_u,
64 const dpctl::tensor::usm_ndarray &out_vt,
65 const std::int8_t jobu_val,
66 const std::int8_t jobvt_val,
67 const int expected_a_u_vt_ndim,
68 const int expected_s_ndim)
69{
70 const int a_array_nd = a_array.get_ndim();
71 const int out_u_array_nd = out_u.get_ndim();
72 const int out_s_array_nd = out_s.get_ndim();
73 const int out_vt_array_nd = out_vt.get_ndim();
74
75 if (a_array_nd != expected_a_u_vt_ndim) {
76 throw py::value_error(
77 "The input array has ndim=" + std::to_string(a_array_nd) +
78 ", but a " + std::to_string(expected_a_u_vt_ndim) +
79 "-dimensional array is expected.");
80 }
81
82 if (out_s_array_nd != expected_s_ndim) {
83 throw py::value_error("The output array of singular values has ndim=" +
84 std::to_string(out_s_array_nd) + ", but a " +
85 std::to_string(expected_s_ndim) +
86 "-dimensional array is expected.");
87 }
88
89 if (jobu_val == 'N' && jobvt_val == 'N') {
90 if (out_u_array_nd != 0) {
91 throw py::value_error(
92 "The output array of the left singular vectors has ndim=" +
93 std::to_string(out_u_array_nd) +
94 ", but it is not used and should have ndim=0.");
95 }
96 if (out_vt_array_nd != 0) {
97 throw py::value_error(
98 "The output array of the right singular vectors has ndim=" +
99 std::to_string(out_vt_array_nd) +
100 ", but it is not used and should have ndim=0.");
101 }
102 }
103 else {
104 if (out_u_array_nd != expected_a_u_vt_ndim) {
105 throw py::value_error(
106 "The output array of the left singular vectors has ndim=" +
107 std::to_string(out_u_array_nd) + ", but a " +
108 std::to_string(expected_a_u_vt_ndim) +
109 "-dimensional array is expected.");
110 }
111 if (out_vt_array_nd != expected_a_u_vt_ndim) {
112 throw py::value_error(
113 "The output array of the right singular vectors has ndim=" +
114 std::to_string(out_vt_array_nd) + ", but a " +
115 std::to_string(expected_a_u_vt_ndim) +
116 "-dimensional array is expected.");
117 }
118 }
119
120 // check compatibility of execution queue and allocation queue
121 if (!dpctl::utils::queues_are_compatible(exec_q,
122 {a_array, out_s, out_u, out_vt}))
123 {
124 throw py::value_error(
125 "Execution queue is not compatible with allocation queues.");
126 }
127
128 auto const &overlap = dpctl::tensor::overlap::MemoryOverlap();
129 if (overlap(a_array, out_s) || overlap(a_array, out_u) ||
130 overlap(a_array, out_vt) || overlap(out_s, out_u) ||
131 overlap(out_s, out_vt) || overlap(out_u, out_vt))
132 {
133 throw py::value_error("Arrays have overlapping segments of memory");
134 }
135
136 dpctl::tensor::validation::CheckWritable::throw_if_not_writable(a_array);
137 dpctl::tensor::validation::CheckWritable::throw_if_not_writable(out_s);
138 dpctl::tensor::validation::CheckWritable::throw_if_not_writable(out_u);
139 dpctl::tensor::validation::CheckWritable::throw_if_not_writable(out_vt);
140
141 const bool is_a_array_f_contig = a_array.is_f_contiguous();
142 if (!is_a_array_f_contig) {
143 throw py::value_error("The input array must be F-contiguous");
144 }
145
146 const bool is_out_u_array_f_contig = out_u.is_f_contiguous();
147 const bool is_out_vt_array_f_contig = out_vt.is_f_contiguous();
148
149 if (!is_out_u_array_f_contig || !is_out_vt_array_f_contig) {
150 throw py::value_error("The output arrays of the left and right "
151 "singular vectors must be F-contiguous");
152 }
153
154 const bool is_out_s_array_c_contig = out_s.is_c_contiguous();
155
156 if (!is_out_s_array_c_contig) {
157 throw py::value_error("The output array of singular values "
158 "must be C-contiguous");
159 }
160
161 auto array_types = dpctl_td_ns::usm_ndarray_types();
162 const int a_array_type_id =
163 array_types.typenum_to_lookup_id(a_array.get_typenum());
164 const int out_u_type_id =
165 array_types.typenum_to_lookup_id(out_u.get_typenum());
166 const int out_vt_type_id =
167 array_types.typenum_to_lookup_id(out_vt.get_typenum());
168
169 if (a_array_type_id != out_u_type_id || a_array_type_id != out_vt_type_id) {
170 throw py::type_error(
171 "Input array, output left singular vectors array, "
172 "and outpuy right singular vectors array must have "
173 "the same data type");
174 }
175}
176
177// Check if the shape of input arrays for gesvd has any non-zero dimension.
178inline bool check_zeros_shape_gesvd(const dpctl::tensor::usm_ndarray &a_array,
179 const dpctl::tensor::usm_ndarray &out_s,
180 const dpctl::tensor::usm_ndarray &out_u,
181 const dpctl::tensor::usm_ndarray &out_vt,
182 const std::int8_t jobu_val,
183 const std::int8_t jobvt_val)
184{
185
186 const int a_array_nd = a_array.get_ndim();
187 const int out_u_array_nd = out_u.get_ndim();
188 const int out_s_array_nd = out_s.get_ndim();
189 const int out_vt_array_nd = out_vt.get_ndim();
190
191 const py::ssize_t *a_array_shape = a_array.get_shape_raw();
192 const py::ssize_t *s_out_shape = out_s.get_shape_raw();
193 const py::ssize_t *u_out_shape = out_u.get_shape_raw();
194 const py::ssize_t *vt_out_shape = out_vt.get_shape_raw();
195
196 bool is_zeros_shape = helper::check_zeros_shape(a_array_nd, a_array_shape);
197 if (jobu_val == 'N' && jobvt_val == 'N') {
198 is_zeros_shape = is_zeros_shape || helper::check_zeros_shape(
199 out_vt_array_nd, vt_out_shape);
200 }
201 else {
202 is_zeros_shape =
203 is_zeros_shape ||
204 helper::check_zeros_shape(out_u_array_nd, u_out_shape) ||
205 helper::check_zeros_shape(out_s_array_nd, s_out_shape) ||
206 helper::check_zeros_shape(out_vt_array_nd, vt_out_shape);
207 }
208
209 return is_zeros_shape;
210}
211
212inline void handle_lapack_exc(const std::int64_t scratchpad_size,
213 const oneapi::mkl::lapack::exception &e,
214 std::stringstream &error_msg)
215{
216 const std::int64_t info = e.info();
217 if (info < 0) {
218 error_msg << "Parameter number " << -info << " had an illegal value.";
219 }
220 else if (info == scratchpad_size && e.detail() != 0) {
221 error_msg << "Insufficient scratchpad size. Required size is at least "
222 << e.detail();
223 }
224 else if (info > 0) {
225 error_msg << "The algorithm computing SVD failed to converge; " << info
226 << " off-diagonal elements of an intermediate "
227 << "bidiagonal form did not converge to zero.\n";
228 }
229 else {
230 error_msg
231 << "Unexpected MKL exception caught during gesv() call:\nreason: "
232 << e.what() << "\ninfo: " << e.info();
233 }
234}
235} // namespace dpnp::extensions::lapack::gesvd_utils