Merge pull request #14827 from YashasSamaga:cuda4dnn-csl-low
[platform/upstream/opencv.git] / modules / dnn / src / cuda4dnn / csl / tensor.hpp
1 // This file is part of OpenCV project.
2 // It is subject to the license terms in the LICENSE file found in the top-level directory
3 // of this distribution and at http://opencv.org/license.html.
4
5 #ifndef OPENCV_DNN_SRC_CUDA4DNN_CSL_TENSOR_HPP
6 #define OPENCV_DNN_SRC_CUDA4DNN_CSL_TENSOR_HPP
7
8 #include "nvcc_defs.hpp"
9 #include "memory.hpp"
10 #include "cublas.hpp"
11 #include "cudnn.hpp"
12 #include "span.hpp"
13
14 #include "../cxx_utils/resizable_static_array.hpp"
15 #include "../cxx_utils/is_iterator.hpp"
16
17 #include <opencv2/core.hpp>
18
19 #include <cstddef>
20 #include <cstdint>
21 #include <type_traits>
22 #include <array>
23 #include <functional>
24 #include <algorithm>
25 #include <numeric>
26 #include <iterator>
27 #include <vector>
28 #include <utility>
29
30 #ifndef CSL_MAX_TENSOR_RANK
31     #define CSL_MAX_TENSOR_RANK 6
32 #endif
33
34 namespace cv { namespace dnn { namespace cuda4dnn { namespace csl {
35
36     /** \file tensor.hpp
37      *
38      *     TYPE     | OWNERSHIP | MUTABLE
39      * ------------ + --------- + --------
40      *    Tensor    |    Yes    |   Yes
41      *  TensorSpan  |    No     |   Yes
42      *  TensorView  |    No     |   No
43      *
44      * Tensor is implicitly convertible to TensorSpan and TensorView
45      * TensorSpan is implicitly convertible to TensorView
46      *
47      * Concepts and template parameter naming convention:
48      * - "MutableTensorType" can refer to a Tensor or TensorSpan
49      * - "ImmutableTensorType" can refer to a Tensor, TensorSpan or TensorView
50      * - "TensorType" can refer to a Tensor, TensorSpan or TensorView
51      *
52      * "ImmutableTensorType" is used when the tensor data might be used.
53      * "TensorType" is used when only meta-information such as the size or shape is required, i.e. the data won't be touched
54      */
55
56     /** if the \p axis is a negative index, the equivalent postive index is returned; otherwise, returns \p axis */
57     CUDA4DNN_HOST_DEVICE constexpr std::size_t clamp_axis(int axis, std::size_t rank) {
58         return axis < 0 ? axis + rank : axis;
59     }
60
61     /** @brief multi-dimensional contiguous non-copyable GPU tensor
62      *
63      * \tparam  T       type of data stored
64      *
65      * @note scalars or zero rank tensors are not supported
66      * @note the maximum rank supported is controlled by the `CSL_MAX_TENSOR_RANK` preprocessor symbol
67      */
68     template <class T>
69     class Tensor {
70         static_assert(std::is_standard_layout<T>::value, "T must staisfy StandardLayoutType");
71
72     public:
73         using value_type    = typename ManagedPtr<T>::element_type;
74         using pointer       = typename ManagedPtr<value_type>::pointer;
75         using const_pointer = typename ManagedPtr<value_type>::const_pointer;
76         using size_type     = typename ManagedPtr<value_type>::size_type;
77
78         Tensor() noexcept { }
79         Tensor(const Tensor&) = delete;
80         Tensor(Tensor&& other) noexcept {
81             data = std::move(other.data);
82             shape = other.shape;
83             other.shape.clear();
84         }
85
86         /** @brief constructs a tensor of a specific shape
87          *
88          * Whatever arguments are accepted by the resize methods are accepted here.
89          */
90         template <class ...Args>
91         Tensor(Args&&... sizes) { resize(std::forward<Args>(sizes)...); }
92
93         Tensor& operator=(const Tensor&) = delete;
94         Tensor& operator=(Tensor&& other) noexcept {
95             data = std::move(other.data);
96             shape = other.shape;
97             other.shape.clear();
98             return *this;
99         }
100
101         /** returns true if the tensor is empty (or uninitialized) */
102         bool empty() const noexcept { return shape.size() == 0; }
103
104         /** returns the total number of elements in the tensor
105          *
106          * Pre-conditions:
107          * - tensor must be non-empty
108          */
109         size_type size() const noexcept {
110             CV_Assert(!empty());
111             return std::accumulate(std::begin(shape), std::end(shape), 1, std::multiplies<size_type>());
112         }
113
114         /** returns the rank of the tensor
115          *
116          * Pre-conditions:
117          * - tensor must be non-empty
118          */
119         size_type rank() const noexcept {
120             CV_Assert(!empty());
121             return shape.size();
122         }
123
124         /** @brief returns the length of the axis
125          *
126          * Every axis is assigned a zero-based index which can be used to select an axis.
127          * Negative index can be used to select an axis from the end.
128          *
129          * Examples:
130          * > -1 represents the last axis
131          * > 0 represents the first axis
132          * > 1 represents the second axis
133          *
134          * Pre-conditions:
135          * - tensor must be non-empty
136          * - the axis must be in the range [-rank(), rank())
137          */
138         size_type get_axis_size(int axis) const noexcept {
139             axis = clamp_axis(axis, rank());
140             CV_Assert(axis >= 0 && axis < rank());
141             return shape[axis];
142         }
143
144         /** @brief returns the combined size of the axes in an axis range
145          *
146          * if the shape is [3 x 5 x 7 x 11]
147          * - `size_range(0, 2)` will return 3 x 5 = 15
148          * - `size_range(1, 3)` will return 5 x 7 = 35
149          * - `size_range(0, 4)` will return 3 x 5 x 7 x 11 = 1155
150          *
151          * Pre-conditions:
152          * - tensor must be non-empty
153          * - `axis_start` must be less than or equal to `axis_end`
154          * - `axis_end` must be less than or equal to the rank
155          *
156          * returns one if the two `axis_start` and `axis_end` are equal
157          */
158         size_type size_range(size_type axis_start, size_type axis_end) const noexcept {
159             CV_Assert(!empty());
160             CV_Assert(axis_start <= axis_end);
161             CV_Assert(axis_end <= rank());
162             auto start = std::begin(shape) + axis_start;
163             auto end = std::begin(shape) + axis_end;
164             return std::accumulate(start, end, 1, std::multiplies<size_type>());
165         }
166
167         /** returns an std::vector containing axis lengths starting from axis zero
168          *
169          * Pre-conditions:
170          * - tensor must be non-empty
171          *
172          * Exception Guarantee: Strong
173          */
174         std::vector<size_type> shape_as_vector() const {
175             CV_Assert(!empty());
176             return std::vector<size_type>(std::begin(shape), std::end(shape));
177         }
178
179         /** returns a pointer to mutable device memory owned by the tensor */
180         pointer get() noexcept { return data.get(); }
181
182         /** returns a pointer to immutable device memory owned by the tensor */
183         const_pointer get() const noexcept { return data.get(); }
184
185         /** @brief releases the memory owned by the tensor
186          *
187          * Pre-conditions:
188          * - tensor must be non-empty
189          *
190          * Exception Guarantee: Strong
191          */
192         void clear() {
193             CV_Assert(!empty());
194             data.reset();
195             shape.clear();
196         }
197
198         /** @brief resizes the tensor
199          *
200          * Pre-conditions:
201          * - [start, end) represents a forward range containing the length of the axes in order starting from axis zero
202          * - number of lengths provided must not exceed the maximum tensor rank (CSL_MAX_TENSOR_RANK)
203          * - the sizes must be positive integers
204          *
205          * Exception Guarantee: Strong
206          */
207         template <class ForwardItr>
208         typename std::enable_if<cxx_utils::is_forward_iterator<ForwardItr>::value, void>
209         ::type resize(ForwardItr start, ForwardItr end) {
210             CV_Assert(start != end);
211             CV_Assert(std::distance(start, end) <= CSL_MAX_TENSOR_RANK);
212
213             using ItrValueType = typename std::iterator_traits<ForwardItr>::value_type;
214             auto total = std::accumulate(start, end, 1, std::multiplies<ItrValueType>());
215             data.reset(total);
216
217             shape.assign(start, end);
218         }
219
220         /** @brief resizes the tensor
221          * constructs a range out of the arguments and invokes the range-based resize method
222          */
223         template <class ...Sizes>
224         void resize(Sizes... new_sizes_) {
225             static_assert(sizeof...(Sizes) <= CSL_MAX_TENSOR_RANK, "required rank exceeds maximum supported rank");
226             static_assert(sizeof...(Sizes) > 0, "no sizes provided");
227             std::array<size_type, sizeof...(Sizes)> new_sizes = { static_cast<size_type>(new_sizes_)... };
228             resize(std::begin(new_sizes), std::end(new_sizes));
229         }
230
231         /** @brief resizes the tensor
232          *
233          * Pre-conditions:
234          * - the reference tensor must be non-empty
235          *
236          * Exception Guarantee: Strong
237          */
238         template <class TensorType>
239         void resize_as(const TensorType& tensor) {
240             CV_Assert(!tensor.empty());
241             cxx_utils::resizable_static_array<size_type, CSL_MAX_TENSOR_RANK> new_sizes(tensor.rank());
242             for (int i = 0; i < new_sizes.size(); i++)
243                 new_sizes[i] = tensor.get_axis_size(i);
244             resize(std::begin(new_sizes), std::end(new_sizes));
245         }
246
247         /** @brief reshapes the tensor
248          *
249          * Length deduction:
250          * The length of at most one axis can be deduced using the total size constraint. The axis can
251          * be marked for deduction by specifying the size as -1.
252          *
253          * The axes for which no size was provided (excluding -1) will be assumed to be one.
254          *
255          * Pre-conditions:
256          * - the tensor must be non-empty
257          * - [start, end) represents a forward range containing the length of the axes starting from axis zero
258          * - the number of lengths provided must be less than or equal to the tensor rank
259          * - at most one axis length is allowed for length deduction
260          * - the lengths provided must ensure that the total number of elements remains unchanged
261          *
262          * Exception Guarantee: Strong
263          */
264         template <class ForwardItr>
265         typename std::enable_if<cxx_utils::is_forward_iterator<ForwardItr>::value, void>
266         ::type reshape(ForwardItr start, ForwardItr end) {
267             CV_Assert(start != end);
268             CV_Assert(std::distance(start, end) <= rank());
269
270             using ItrValueType = typename std::iterator_traits<ForwardItr>::value_type;
271
272             /* the user may leave at most one axis size for deduction by specifying -1 */
273             auto sizes_to_deduce = std::count(start, end, -1);
274             if (sizes_to_deduce > 1) { CV_Error(Error::StsBadArg, "only one axis size can be deduced"); }
275
276             /* sizes must be positive numbers with the exception of -1 */
277             auto invalid_sizes = std::count_if(start, end, [](ItrValueType x) {
278                 return !(x > 0 || x == -1);
279             });
280             if (invalid_sizes) { CV_Error(Error::StsBadArg, "invalid axis size"); }
281
282             /* compute the total number of elements in the new tensor */
283             size_type unknown_size = 0;
284             auto total = std::accumulate(start, end, 1, std::multiplies<ItrValueType>());
285             if (total < 0) {
286                 /* there is an unknown size */
287                 if (std::abs(total) <= size()) {
288                     unknown_size = size() / std::abs(total);
289                     total = size();
290                 }
291                 /* Edge case: if `total` is already more than size(), skip the deduction as it's impossible
292                 ** Since `total` is negative, the size check which follows will fail and throw an error
293                 */
294             }
295
296             /* the number of elements before and after reshape must be exactly same */
297             if (total != size()) {
298                 CV_Error(Error::StsBadArg, "new axes do not preserve the tensor element count");
299             }
300
301             /* we assume the size of the unspecified axes to be one */
302             std::fill(std::begin(shape), std::end(shape), 1);
303             std::copy_backward(start, end, std::end(shape));
304
305             /* replace the unknown axis with the correct value */
306             std::replace(std::begin(shape), std::end(shape), size_type(-1), unknown_size);
307         }
308
309         /** @brief reshapes the tensor
310          * constructs a range out of the arguments and invokes range-based reshape method
311          */
312         template <class ...Sizes>
313         void reshape(Sizes... new_sizes_) {
314             static_assert(sizeof...(Sizes) <= CSL_MAX_TENSOR_RANK, "required rank exceeds maximum supported rank");
315             static_assert(sizeof...(Sizes) > 0, "no sizes provided");
316             std::array<std::int64_t, sizeof...(Sizes)> new_sizes = { static_cast<std::int64_t>(new_sizes_)... };
317             reshape(std::begin(new_sizes), std::end(new_sizes));
318         }
319
320         /** @brief reshapes the tensor
321          *
322          * Pre-conditions:
323          * - the reference tensor must be a non-empty tensor
324          * - the reference tensor's rank must be lesser than or equal to the rank of target tensor
325          *
326          * Exception Guarantee: Strong
327          */
328         template <class TensorType>
329         void reshape_as(const TensorType& tensor) {
330             CV_Assert(!tensor.empty());
331             cxx_utils::resizable_static_array<size_type, CSL_MAX_TENSOR_RANK> new_sizes(tensor.rank());
332             for (int i = 0; i < new_sizes.size(); i++)
333                 new_sizes[i] = tensor.get_axis_size(i);
334             reshape(std::begin(new_sizes), std::end(new_sizes));
335         }
336
337         /** @brief squeezes the tensor
338          *
339          * removes all axes of unit size
340          *
341          * Pre-conditions:
342          * - the tensor must be non-empty
343          * - the tensor's rank must be at least two
344          *
345          * Exception Guarantee: Strong
346          */
347         void squeeze() {
348             CV_Assert(!empty());
349             CV_Assert(rank() >= 2);
350             auto itr = std::remove(std::begin(shape), std::end(shape), 1);
351             shape.resize(itr - std::begin(shape));
352         }
353
354         /** @brief squeezes the tensor
355          *
356          * removes the specified axis if the axis length is one; otherwise, ignores the request
357          *
358          * Pre-conditions:
359          * - the tensor must be non-empty
360          * - the tensor's rank must be at least two
361          *
362          * Exception Guarantee: Strong
363          */
364         void squeeze(int axis) {
365             CV_Assert(!empty());
366             CV_Assert(rank() >= 2);
367             axis = clamp_axis(axis, rank());
368             CV_Assert(axis >= 0 && axis < rank());
369             shape.erase(std::begin(shape) + axis);
370         }
371
372         /** @brief unsqueezes the tensor
373          *
374          * adds a axis of unit size at the requested before the specified axis
375          *
376          * Pre-conditions:
377          * - the tensor must be non-empty
378          * - the tensor's rank must be less than the maximum supported rank (CSL_MAX_TENSOR_RANK)
379          *
380          * Exception Guarantee: Strong
381          */
382         void unsqueeze(int axis = 0) {
383             CV_Assert(!empty());
384             CV_Assert(rank() < CSL_MAX_TENSOR_RANK);
385             axis = clamp_axis(axis, rank());
386             CV_Assert(axis >= 0 && axis < rank());
387             shape.insert(std::begin(shape) + axis, 1);
388         }
389
390         operator Span<T>() noexcept { return Span<T>(data.get(), size()); }
391         operator View<T>() const noexcept { return View<T>(data.get(), size()); }
392
393         friend void swap(Tensor& lhs, Tensor& rhs) noexcept {
394             using std::swap;
395             swap(lhs.data, rhs.data);
396             swap(lhs.shape, rhs.shape);
397         }
398
399     private:
400         cxx_utils::resizable_static_array<size_type, CSL_MAX_TENSOR_RANK> shape;
401         ManagedPtr<value_type> data;
402     };
403
404     /** @brief provides a non-owning mutable span of a Tensor
405      *
406      * \tparam  T       type of data stored by the tensor
407      *
408      * A span is valid if and only if the following hold true:
409      * - span is non-empty
410      * - spanned memory is still allocated
411      *
412      * A span may be used if and only if it is valid.
413      */
414     template <class T>
415     class TensorSpan {
416     public:
417         using value_type    = typename Tensor<T>::value_type;
418         using pointer       = typename Tensor<T>::pointer;
419         using const_pointer = typename Tensor<T>::const_pointer;
420         using size_type     = typename Tensor<T>::size_type;
421
422         TensorSpan() noexcept : ptr{ nullptr } { }
423         TensorSpan(const TensorSpan&) noexcept = default;
424         TensorSpan(Tensor<T>& tensor) noexcept : ptr{ tensor.get() } {
425             const auto rank = tensor.rank();
426             shape.resize(rank);
427             for (int i = 0; i < rank; i++)
428                 shape[i] = tensor.get_axis_size(i);
429         }
430
431         template <class ForwardItr>
432         TensorSpan(pointer ptr_, ForwardItr start, ForwardItr end) : ptr{ ptr_ } {
433             CV_Assert(start != end);
434             CV_Assert(std::distance(start, end) <= CSL_MAX_TENSOR_RANK);
435
436             using ItrValueType = typename std::iterator_traits<ForwardItr>::value_type;
437             if (std::any_of(start, end, [](ItrValueType x) { return x <= 0; })) {
438                 CV_Error(Error::StsBadArg, "the given shape contains negative or zero size");
439             }
440
441             shape.assign(start, end);
442         }
443
444         /** creates a subspan of a tensor (or span); refer to subspan method for more details */
445         template <class... Args>
446         TensorSpan(TensorSpan other, size_type offset, Args&&... args)
447             : TensorSpan(other.subspan(offset, std::forward<Args>(args)...)) { }
448
449         /** returns true if the span is empty */
450         bool empty() const noexcept { return shape.size() == 0; }
451
452         /** returns the total number of elements in the span
453          *
454          * Pre-conditions:
455          * - span must be non-empty
456          */
457         size_type size() const noexcept {
458             CV_Assert(!empty());
459             return std::accumulate(std::begin(shape), std::end(shape), 1, std::multiplies<size_type>());
460         }
461
462         /** returns the rank of the span
463          *
464          * Pre-conditions:
465          * - span must be non-empty
466          */
467         size_type rank() const noexcept {
468             CV_Assert(!empty());
469             return shape.size();
470         }
471
472         /** @brief returns the length of the axis
473          *
474          * Every axis is assigned a zero-based index which can be used to select an axis.
475          * Negative index can be used to select an axis from the end.
476          *
477          * Examples:
478          * > -1 represents the last axis
479          * > 0 represents the first axis
480          * > 1 represents the second axis
481          *
482          * Pre-conditions:
483          * - span must be non-empty
484          * - the axis must be in the range [-rank(), rank())
485          */
486         size_type get_axis_size(int axis) const noexcept {
487             axis = clamp_axis(axis, rank());
488             CV_Assert(axis >= 0 && axis < rank());
489             return shape[axis];
490         }
491
492         /** @brief returns the combined size of the axes in an axis range
493          *
494          * if the shape is [3 x 5 x 7 x 11]
495          * - `size_range(0, 2)` will return 3 x 5 = 15
496          * - `size_range(1, 3)` will return 5 x 7 = 35
497          * - `size_range(0, 4)` will return 3 x 5 x 7 x 11 = 1155
498          *
499          * Pre-conditions:
500          * - span must be non-empty
501          * - `axis_start` must be less than or equal to `axis_end`
502          * - `axis_end` must be less than or equal to the rank
503          *
504          * returns one if the two `axis_start` and `axis_end` are equal
505          */
506         size_type size_range(size_type axis_start, size_type axis_end) const noexcept {
507             CV_Assert(!empty());
508             CV_Assert(axis_start <= axis_end);
509             CV_Assert(axis_end <= rank());
510             auto start = std::begin(shape) + axis_start;
511             auto end = std::begin(shape) + axis_end;
512             return std::accumulate(start, end, 1, std::multiplies<size_type>());
513         }
514
515         /** returns an std::vector containing axis lengths starting from axis zero
516          *
517          * Pre-conditions:
518          * - span must be non-empty
519          *
520          * Exception Guarantee: Strong
521          */
522         std::vector<size_type> shape_as_vector() const {
523             CV_Assert(!empty());
524             return std::vector<size_type>(std::begin(shape), std::end(shape));
525         }
526
527         /** returns a pointer to mutable device memory */
528         pointer get() const noexcept { return ptr; }
529
530         /** @brief clears the span
531          *
532          * Pre-conditions:
533          * - span must be non-empty
534          *
535          * Exception Guarantee: Strong
536          */
537         void clear() noexcept {
538             CV_Assert(!empty());
539             ptr = nullptr;
540             shape.clear();
541         }
542
543         /** @brief reshapes the span
544          *
545          * Length deduction:
546          * The length of at most one axis can be deduced using the total size constraint. The axis can
547          * be marked for deduction by specifying the corresponding size as -1.
548          *
549          * The axes for which no size was provided (excluding -1) will be assumed to be one.
550          *
551          * Pre-conditions:
552          * - the span must be non-empty
553          * - [start, end) represents a forward range containing the length of the axes in order
554          * - the number of axis lengths must be less than or equal to the rank
555          * - at most one axis length is allowed for length deduction
556          * - the lengths provided must ensure that the total number of elements remains unchnged
557          *
558          * Exception Guarantee: Strong
559          */
560         template <class ForwardItr>
561         typename std::enable_if<cxx_utils::is_forward_iterator<ForwardItr>::value, void>
562         ::type reshape(ForwardItr start, ForwardItr end) {
563             CV_Assert(start != end);
564             CV_Assert(std::distance(start, end) <= rank());
565
566             using ItrValueType = typename std::iterator_traits<ForwardItr>::value_type;
567
568             /* the user may leave at most one axis size for deduction by specifying -1 */
569             auto sizes_to_deduce = std::count(start, end, -1);
570             if (sizes_to_deduce > 1) { CV_Error(Error::StsBadArg, "only one axis size can be deduced"); }
571
572             /* sizes must be positive numbers with the exception of -1 */
573             auto invalid_sizes = std::count_if(start, end, [](ItrValueType x) {
574                 return !(x > 0 || x == -1);
575             });
576             if (invalid_sizes) { CV_Error(Error::StsBadArg, "invalid axis size"); }
577
578             /* compute the total number of elements in the new tensor */
579             size_type unknown_size = 0;
580             auto total = std::accumulate(start, end, 1, std::multiplies<ItrValueType>());
581             if (total < 0) {
582                 /* there is an unknown size */
583                 if (std::abs(total) <= size()) {
584                     unknown_size = size() / std::abs(total);
585                     total = size();
586                 }
587                 /* Edge case: if `total` is already more than size(), skip the deduction as it's impossible
588                 ** Since `total` is negative, the size check which follows will fail and throw an error
589                 */
590             }
591
592             /* the number of elements before and after reshape must be exactly same */
593             if (total != size()) {
594                CV_Error(Error::StsBadArg, "new axes do not preserve the tensor element count");
595             }
596
597             /* we assume the size of the unspecified axes to be one */
598             std::fill(std::begin(shape), std::end(shape), 1);
599             std::copy_backward(start, end, std::end(shape));
600
601             /* replace the unknown axis with the correct value */
602             std::replace(std::begin(shape), std::end(shape), size_type(-1), unknown_size);
603         }
604
605         /** @brief reshapes the tensor
606          * constructs a range out of the arguments and invokes the range-based reshape method
607          */
608         template <class ...Sizes>
609         void reshape(Sizes... new_sizes_) {
610             static_assert(sizeof...(Sizes) <= CSL_MAX_TENSOR_RANK, "unsupported tensor rank");
611             static_assert(sizeof...(Sizes) > 0, "no sizes provided");
612             std::array<std::int64_t, sizeof...(Sizes)> new_sizes = { static_cast<std::int64_t>(new_sizes_)... };
613             reshape(std::begin(new_sizes), std::end(new_sizes));
614         }
615
616         /** @brief reshapes the span
617          *
618          * Pre-conditions:
619          * - the reference tensor/span/view must be non-empty
620          * - the reference tensor/span/view's rank must be less than or equal to the rank of the span
621          *
622          * Exception Guarantee: Strong
623          */
624         template <class TensorType>
625         void reshape_as(const TensorType& tensor) {
626             CV_Assert(!tensor.empty());
627             cxx_utils::resizable_static_array<size_type, CSL_MAX_TENSOR_RANK> new_sizes(tensor.rank());
628             for (int i = 0; i < new_sizes.size(); i++)
629                 new_sizes[i] = tensor.get_axis_size(i);
630             reshape(std::begin(new_sizes), std::end(new_sizes));
631         }
632
633         /** @brief squeezes the tensor
634          *
635          * removes all axes of unit size
636          *
637          * Pre-conditions:
638          * - the span must be non-empty
639          * - the span's rank must be at least two
640          *
641          * Exception Guarantee: Strong
642          */
643         void squeeze() {
644             CV_Assert(!empty());
645             CV_Assert(rank() >= 2);
646             auto itr = std::remove(std::begin(shape), std::end(shape), 1);
647             shape.resize(itr - std::begin(shape));
648         }
649
650         /** @brief squeezes the tensor
651          *
652          * removes the specified axis if the axis length is one; otherwise, ignores the request
653          *
654          * Pre-conditions:
655          * - the span must be non-empty
656          * - the span's rank must be at least two
657          *
658          * Exception Guarantee: Strong
659          */
660         void squeeze(int axis) {
661             CV_Assert(!empty());
662             CV_Assert(rank() >= 2);
663             axis = clamp_axis(axis, rank());
664             CV_Assert(axis >= 0 && axis < rank());
665             shape.erase(std::begin(shape) + axis);
666         }
667
668         /** @brief unsqueezes the tensor
669          *
670          * adds a axis of unit size at the requested before the specified axis
671          *
672          * Pre-conditions:
673          * - the span must be non-empty
674          * - the span's rank must be less than the maximum supported rank (CSL_MAX_TENSOR_RANK)
675          *
676          * Exception Guarantee: Strong
677          */
678         void unsqueeze(int axis = 0) {
679             CV_Assert(!empty());
680             CV_Assert(rank() < CSL_MAX_TENSOR_RANK);
681             axis = clamp_axis(axis, rank());
682             CV_Assert(axis >= 0 && axis < rank());
683             shape.insert(std::begin(shape) + axis, 1);
684         }
685
686         /** @brief obtains a subspan of the span
687          *
688          * Pre-conditions:
689          * - the span must be non-empty
690          * - the `offset` must be less than the size of the span
691          * - [start, end) represents a forward range containing length of the subspan axes
692          * - the lengths provided must ensure that the number of elements does not exceed (old size - offset)
693          *
694          * Exception Guarantee: Strong
695          */
696         template <class ForwardItr>
697         typename std::enable_if<cxx_utils::is_forward_iterator<ForwardItr>::value, TensorSpan>
698         ::type subspan(size_type offset, ForwardItr start, ForwardItr end) const {
699             CV_Assert(start != end);
700             CV_Assert(std::distance(start, end) <= rank());
701
702             auto cur_size = size();
703             CV_Assert(offset < cur_size);
704
705             using ItrValueType = typename std::iterator_traits<ForwardItr>::value_type;
706
707             /* sizes must be positive numbers */
708             auto invalid_sizes = std::count_if(start, end, [](ItrValueType x) {
709                 return !(x > 0);
710             });
711             if (invalid_sizes) { CV_Error(Error::StsBadArg, "invalid axis size"); }
712
713             /* the number of elements must be equal to the new size */
714             auto max_size = (cur_size - offset);
715             auto total = std::accumulate(start, end, 1, std::multiplies<ItrValueType>());
716             if (total > max_size) {
717                 CV_Error(Error::StsBadArg, "axis lengths lead to OOB accesses");
718             }
719
720             TensorSpan temp;
721             temp.shape.assign(start, end);
722             temp.ptr = ptr + offset;
723             return temp;
724         }
725
726         /** @brief obtains a subspan of the span
727          * constructs a range out of the size arguments and invokes the range-based subspan method
728          */
729         template <class ...Sizes>
730         TensorSpan subspan(size_type offset, Sizes... new_sizes_) const {
731             static_assert(sizeof...(Sizes) <= CSL_MAX_TENSOR_RANK, "required rank exceeds maximum supported rank");
732             static_assert(sizeof...(Sizes) > 0, "no sizes provided");
733             std::array<std::int64_t, sizeof...(Sizes)> new_sizes = { static_cast<std::int64_t>(new_sizes_)... };
734             return subspan(offset, std::begin(new_sizes), std::end(new_sizes));
735         }
736
737         operator Span<T>() noexcept { return Span<T>(ptr, size()); }
738         operator View<T>() const noexcept { return View<T>(ptr, size()); }
739
740         friend void swap(TensorSpan& lhs, TensorSpan& rhs) noexcept {
741             using std::swap;
742             swap(lhs.ptr, rhs.ptr);
743             swap(lhs.shape, rhs.shape);
744         }
745
746     private:
747         cxx_utils::resizable_static_array<size_type, CSL_MAX_TENSOR_RANK> shape;
748         pointer ptr;
749     };
750
751     /** @brief view of a tensor
752      *
753      * \tparam  T       type of data stored by the tensor
754      *
755      * A view is valid if and only if the following hold true:
756      * - view is non-empty
757      * - viewed memory is still allocated
758      */
759     template <class T>
760     class TensorView {
761     public:
762         using value_type    = typename Tensor<T>::value_type;
763         using pointer       = typename Tensor<T>::pointer;
764         using const_pointer = typename Tensor<T>::const_pointer;
765         using size_type     = typename Tensor<T>::size_type;
766
767         TensorView() noexcept : ptr{ nullptr } { }
768         TensorView(const TensorView&) noexcept = default;
769         TensorView(TensorSpan<T> other) noexcept : ptr{ other.get() } {
770             const auto rank = other.rank();
771             shape.resize(rank);
772             for (int i = 0; i < rank; i++)
773                 shape[i] = other.get_axis_size(i);
774         }
775         TensorView(const Tensor<T>& tensor) noexcept : ptr{ tensor.get() } {
776             const auto rank = tensor.rank();
777             shape.resize(rank);
778             for (int i = 0; i < rank; i++)
779                 shape[i] = tensor.get_axis_size(i);
780         }
781
782         template <class ForwardItr>
783         TensorView(pointer ptr_, ForwardItr start, ForwardItr end) : ptr{ ptr_ } {
784             CV_Assert(start != end);
785             CV_Assert(std::distance(start, end) <= CSL_MAX_TENSOR_RANK);
786
787             using ItrValueType = typename std::iterator_traits<ForwardItr>::value_type;
788             if (std::any_of(start, end, [](ItrValueType x) { return x <= 0; })) {
789                 CV_Error(Error::StsBadArg, "the given shape contains negative or zero size");
790             }
791
792             shape.assign(start, end);
793         }
794
795         /** creates a subview of a tensor (or span or view); refer to subview method for more details */
796         template <class... Args>
797         TensorView(TensorView other, size_type offset, Args&&... args) noexcept
798             : TensorView(other.subview(offset, std::forward<Args>(args)...)) { }
799
800         TensorView& operator=(const TensorView&) = default;
801         TensorView& operator=(TensorSpan<T> other) noexcept {
802             TensorView tmp(other);
803             swap(*this, tmp);
804             return *this;
805         }
806
807         /** returns true if the view is empty */
808         bool empty() const noexcept { return shape.size() == 0; }
809
810         /** returns the total number of elements in the view
811          *
812          * Pre-conditions:
813          * - view must be non-empty
814          */
815         size_type size() const noexcept {
816             CV_Assert(!empty());
817             return std::accumulate(std::begin(shape), std::end(shape), 1, std::multiplies<size_type>());
818         }
819
820         /** returns the rank of the view
821          *
822          * Pre-conditions:
823          * - view must be non-empty
824          */
825         size_type rank() const noexcept {
826             CV_Assert(!empty());
827             return shape.size();
828         }
829
830         /** @brief returns the length of the axis
831          *
832          * Every axis is assigned a zero-based index which can be used to select an axis.
833          * Negative index can be used to select an axis from the end.
834          *
835          * Examples:
836          * > -1 represents the last axis
837          * > 0 represents the first axis
838          * > 1 represents the second axis
839          *
840          * Pre-conditions:
841          * - view must be non-empty
842          * - the axis must be in the range [-rank(), rank())
843          */
844         size_type get_axis_size(int axis) const noexcept {
845             axis = clamp_axis(axis, rank());
846             CV_Assert(axis >= 0 && axis < rank());
847             return shape[axis];
848         }
849
850         /** @brief returns the combined size of the axes in an axis range
851          *
852          * if the shape is [3 x 5 x 7 x 11]
853          * - `size_range(0, 2)` will return 3 x 5 = 15
854          * - `size_range(1, 3)` will return 5 x 7 = 35
855          * - `size_range(0, 4)` will return 3 x 5 x 7 x 11 = 1155
856          *
857          * Pre-conditions:
858          * - view must be non-empty
859          * - `axis_start` must be less than or equal to `axis_end`
860          * - `axis_end` must be less than or equal to the rank
861          *
862          * returns one if the two `axis_start` and `axis_end` are equal
863          */
864         size_type size_range(size_type axis_start, size_type axis_end) const noexcept {
865             CV_Assert(!empty());
866             CV_Assert(axis_start <= axis_end);
867             CV_Assert(axis_end <= rank());
868             auto start = std::begin(shape) + axis_start;
869             auto end = std::begin(shape) + axis_end;
870             return std::accumulate(start, end, 1, std::multiplies<size_type>());
871         }
872
873         /** returns an std::vector containing axis lengths starting from axis zero
874          *
875          * Pre-conditions:
876          * - view must be non-empty
877          *
878          * Exception Guarantee: Strong
879          */
880         std::vector<size_type> shape_as_vector() const {
881             CV_Assert(!empty());
882             return std::vector<size_type>(std::begin(shape), std::end(shape));
883         }
884
885         /** returns a device pointer to immutable device memory */
886         const_pointer get() const noexcept { return ptr; }
887
888         /** @brief reshapes the view
889          *
890          * Length deduction:
891          * The length of at most one axis can be deduced using the total size constraint. The axis can
892          * be marked for deduction by specifying the size as -1.
893          *
894          * The axes for which no size was provided (excluding -1) will be assumed to be one.
895          *
896          * Pre-conditions:
897          * - view must be non-empty
898          * - [start, end) represents a forward range containing length of the axes in order starting from axis zero
899          * - the number of axis lengths must be less than or equal to the tensor rank
900          * - at most one axis length is allowed for length deduction
901          * - the lengths provided must ensure that the total number of elements remains unchnged
902          *
903          * Exception Guarantee: Strong
904          */
905         template <class ForwardItr>
906         typename std::enable_if<!std::is_integral<ForwardItr>::value, void>
907         ::type reshape(ForwardItr start, ForwardItr end) {
908             CV_Assert(start != end);
909             CV_Assert(std::distance(start, end) <= rank());
910
911             using ItrValueType = typename std::iterator_traits<ForwardItr>::value_type;
912
913             /* the user may leave at most one axis size for deduction by specifying -1 */
914             auto sizes_to_deduce = std::count(start, end, -1);
915             if (sizes_to_deduce > 1) { CV_Error(Error::StsBadArg, "only one axis size can be deduced"); }
916
917             /* sizes must be positive numbers with the exception of -1 */
918             auto invalid_sizes = std::count_if(start, end, [](ItrValueType x) {
919                 return !(x > 0 || x == -1);
920             });
921             if (invalid_sizes) { CV_Error(Error::StsBadArg, "invalid axis size"); }
922
923             /* compute the total number of elements in the new tensor */
924             size_type unknown_size = 0;
925             auto total = std::accumulate(start, end, 1, std::multiplies<ItrValueType>());
926             if (total < 0) {
927                 /* there is an unknown size */
928                 if (std::abs(total) <= size()) {
929                     unknown_size = size() / std::abs(total);
930                     total = size();
931                 }
932                 /* Edge case: if `total` is already more than size(), skip the deduction as it's impossible
933                 ** Since `total` is negative, the size check which follows will fail and throw an error
934                 */
935             }
936
937             /* the number of elements before and after reshape must be exactly same */
938             if (total != size()) {
939                 CV_Error(Error::StsBadArg, "new axes do not preserve the tensor element count");
940             }
941
942             /* we assume the size of the unspecified axes to be one */
943             std::fill(std::begin(shape), std::end(shape), 1);
944             std::copy_backward(start, end, std::end(shape));
945
946             /* replace the unknown axis with the correct value */
947             std::replace(std::begin(shape), std::end(shape), size_type(-1), unknown_size);
948         }
949
950         /** @brief reshapes the view
951          * constructs a range out of the arguments and invokes the range-based reshape method
952          */
953         template <class ...Sizes>
954         void reshape(Sizes... new_sizes_) {
955             static_assert(sizeof...(Sizes) <= CSL_MAX_TENSOR_RANK, "required rank exceeds maximum supported rank");
956             static_assert(sizeof...(Sizes) > 0, "no sizes provided");
957             std::array<std::int64_t, sizeof...(Sizes)> new_sizes = { static_cast<std::int64_t>(new_sizes_)... };
958             reshape(std::begin(new_sizes), std::end(new_sizes));
959         }
960
961         /** @brief reshapes the view
962          *
963          * Pre-conditions:
964          * - the reference tensor/span/view must be non-empty
965          * - the reference tensor/span/view's rank must be less than or equal to the rank of the view
966          *
967          * Exception Guarantee: Strong
968          */
969         template <class TensorType>
970         void reshape_as(const TensorType& tensor) {
971             CV_Assert(!tensor.empty());
972             cxx_utils::resizable_static_array<size_type, CSL_MAX_TENSOR_RANK> new_sizes(tensor.rank());
973             for (int i = 0; i < new_sizes.size(); i++)
974                 new_sizes[i] = tensor.get_axis_size(i);
975             reshape(std::begin(new_sizes), std::end(new_sizes));
976         }
977
978         /** @brief squeezes the tensor
979          *
980          * removes all axes of unit size
981          *
982          * Pre-conditions:
983          * - the view must be non-empty
984          * - the view's rank must be at least two
985          *
986          * Exception Guarantee: Strong
987          */
988         void squeeze() {
989             CV_Assert(!empty());
990             CV_Assert(rank() >= 2);
991             auto itr = std::remove(std::begin(shape), std::end(shape), 1);
992             shape.resize(itr - std::begin(shape));
993         }
994
995         /** @brief squeezes the tensor
996          *
997          * removes the specified axis if the axis length is one; otherwise, ignores the request
998          *
999          * Pre-conditions:
1000          * - the view must be non-empty
1001          * - the view's rank must be at least two
1002          *
1003          * Exception Guarantee: Strong
1004          */
1005         void squeeze(int axis) {
1006             CV_Assert(!empty());
1007             CV_Assert(rank() >= 2);
1008             axis = clamp_axis(axis, rank());
1009             CV_Assert(axis >= 0 && axis < rank());
1010             shape.erase(std::begin(shape) + axis);
1011         }
1012
1013         /** @brief unsqueezes the tensor
1014          *
1015          * adds a axis of unit size at the requested before the specified axis
1016          *
1017          * Pre-conditions:
1018          * - the view must be non-empty
1019          * - the view's rank must be less than the maximum supported rank (CSL_MAX_TENSOR_RANK)
1020          *
1021          * Exception Guarantee: Strong
1022          */
1023         void unsqueeze(int axis = 0) {
1024             CV_Assert(!empty());
1025             CV_Assert(rank() < CSL_MAX_TENSOR_RANK);
1026             axis = clamp_axis(axis, rank());
1027             CV_Assert(axis >= 0 && axis < rank());
1028             shape.insert(std::begin(shape) + axis, 1);
1029         }
1030
1031         /** @brief obtains a subview of the view
1032          *
1033          * The axes for which no size was provided will be assumed to be one.
1034          *
1035          * Pre-conditions:
1036          * - the view must be non-empty
1037          * - the `offset` must be less than the size of the view
1038          * - [start, end) represents a forward range containing length of the subview axes in order
1039          * - the number of axis lengths provided must be less than or equal to the tensor rank
1040          * - the lengths provided must ensure that the number of elements does not exceed (old size - offset)
1041          *
1042          * Exception Guarantee: Strong
1043          */
1044         template <class ForwardItr>
1045         typename std::enable_if<cxx_utils::is_forward_iterator<ForwardItr>::value, TensorView>
1046         ::type subview(size_type offset, ForwardItr start, ForwardItr end) const {
1047             CV_Assert(start != end);
1048             CV_Assert(std::distance(start, end) <= rank());
1049
1050             auto cur_size = size();
1051             CV_Assert(offset < cur_size);
1052
1053             using ItrValueType = typename std::iterator_traits<ForwardItr>::value_type;
1054
1055             /* sizes must be positive numbers */
1056             auto invalid_sizes = std::count_if(start, end, [](ItrValueType x) {
1057                 return !(x > 0);
1058             });
1059             if (invalid_sizes) { CV_Error(Error::StsBadArg, "invalid axis size"); }
1060
1061             /* the number of elements must be equal to the new size */
1062             auto max_size = (cur_size - offset);
1063             auto total = std::accumulate(start, end, 1, std::multiplies<ItrValueType>());
1064             if (total > max_size) {
1065                 CV_Error(Error::StsBadArg, "axes lengths lead to OOB accesses");
1066             }
1067
1068             TensorView temp;
1069             temp.shape.assign(start, end);
1070             temp.ptr = ptr + offset;
1071             return temp;
1072         }
1073
1074         /** @brief obtains a subview of the view
1075          * constructs a range out of the size arguments and invokes the range-based subview method
1076          */
1077         template <class ...Sizes>
1078         TensorView subview(size_type offset, Sizes... new_sizes_) const {
1079             static_assert(sizeof...(Sizes) <= CSL_MAX_TENSOR_RANK, "required rank exceeds maximum supported rank");
1080             static_assert(sizeof...(Sizes) > 0, "no sizes provided");
1081             std::array<std::int64_t, sizeof...(Sizes)> new_sizes = { static_cast<std::int64_t>(new_sizes_)... };
1082             return subview(offset, std::begin(new_sizes), std::end(new_sizes));
1083         }
1084
1085         operator View<T>() const noexcept { return View<T>(ptr, size()); }
1086
1087         friend void swap(TensorView& lhs, TensorView& rhs) noexcept {
1088             using std::swap;
1089             swap(lhs.ptr, rhs.ptr);
1090             swap(lhs.shape, rhs.shape);
1091         }
1092
1093     private:
1094         cxx_utils::resizable_static_array<size_type, CSL_MAX_TENSOR_RANK> shape;
1095         const_pointer ptr;
1096     };
1097
1098     /** returns true if the two TensorType objects have the same shape */
1099     template <class TensorType1, class TensorType2>
1100     bool is_shape_same(const TensorType1& x, const TensorType2& y) noexcept {
1101         auto rank1 = x.rank();
1102         auto rank2 = y.rank();
1103
1104         if (rank1 != rank2)
1105             return false;
1106
1107         for (int i = 0; i < rank1; i++)
1108             if (x.get_axis_size(i) != y.get_axis_size(i))
1109                 return false;
1110         return true;
1111     }
1112
1113     /** returns true if the two TensorType objects are compatible */
1114     template <class TensorType1, class TensorType2>
1115     bool is_shape_compatible(const TensorType1& x, const TensorType2& y) noexcept {
1116         const auto rank1 = x.rank();
1117         const auto rank2 = y.rank();
1118
1119         /* mathematically not required but is a technically required */
1120         if (rank1 != rank2)
1121             return false;
1122
1123         for (int i = 0; i < rank1; i++)
1124             if (x.get_axis_size(i) != y.get_axis_size(i) &&
1125                 x.get_axis_size(i) != 1 && y.get_axis_size(i) != 1)
1126                 return false;
1127         return true;
1128     }
1129
1130     /** returns the rank to which the given tensor can be squeezed to */
1131     template <class TensorType>
1132     std::size_t get_effective_rank(const TensorType& x) noexcept {
1133         const auto rank = x.rank();
1134         auto effective_rank = rank;
1135         for (int i = 0; i < rank; i++, effective_rank--)
1136             if (x.get_axis_size(i) != 1)
1137                 break;
1138         return effective_rank;
1139     }
1140
1141 }}}} /* namespace cv::dnn::cuda4dnn::csl */
1142
1143 #endif /* OPENCV_DNN_SRC_CUDA4DNN_CSL_TENSOR_HPP */