TiledArray  0.7.0
kernels.h
Go to the documentation of this file.
1 /*
2  * This file is a part of TiledArray.
3  * Copyright (C) 2015 Virginia Tech
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Justus Calvin
19  * Department of Chemistry, Virginia Tech
20  *
21  * kernels.h
22  * Jun 1, 2015
23  *
24  */
25 
26 #ifndef TILEDARRAY_TENSOR_KENERLS_H__INCLUDED
27 #define TILEDARRAY_TENSOR_KENERLS_H__INCLUDED
28 
31 #include <TiledArray/math/eigen.h>
32 
33 namespace TiledArray {
34 
35  template <typename, typename> class Tensor;
36 
37  namespace detail {
38 
40  template <typename T>
41  struct transform;
42 
43  // -------------------------------------------------------------------------
44  // Tensor kernel operations that generate a new tensor
45 
47 
57  template <typename TR, typename Op, typename T1, typename... Ts,
58  typename std::enable_if<is_tensor<TR, T1, Ts...>::value
59  || is_tensor_of_tensor<TR, T1, Ts...>::value>::type* = nullptr>
60  inline TR tensor_op(Op&& op, const T1& tensor1, const Ts&... tensors) {
61  return TiledArray::detail::transform<TR>()(std::forward<Op>(op), tensor1, tensors...);
62  }
63 
64 
66 
78  template <typename TR, typename Op, typename T1, typename... Ts,
79  typename std::enable_if<(is_tensor<T1, Ts...>::value
80  || is_tensor_of_tensor<TR, T1, Ts...>::value)
81  && is_contiguous_tensor<T1, Ts...>::value>::type* = nullptr>
82  inline TR tensor_op(Op&& op, const Permutation& perm, const T1& tensor1,
83  const Ts&... tensors)
84  {
85  return TiledArray::detail::transform<TR>()(std::forward<Op>(op), perm, tensor1, tensors...);
86  }
87 
89  template <typename T>
90  struct transform {
92  template <typename Op, typename Tensor, typename ... Tensors>
93  T operator()(Op&& op, Tensor&& tensor, Tensors&& ... tensors) const {
94  TA_ASSERT(! empty(tensor, tensors...));
95  TA_ASSERT(is_range_set_congruent(tensor, tensors...));
96 
97  const auto& range = tensor.range();
98  T result(range);
99  this->operator()(result, std::forward<Op>(op), std::forward<Tensor>(tensor), std::forward<Tensors>(tensors)...);
100  return result;
101  }
102 
105  template <typename Op, typename Tensor, typename ... Tensors>
106  void operator()(T& result, Op&& op, Tensor&& tensor, Tensors&& ... tensors) const {
107  TA_ASSERT(! empty(result, tensor, tensors...));
108  TA_ASSERT(is_range_set_congruent(result, tensor, tensors...));
109 
110  const auto& range = result.range();
111  for (auto&& i : range)
112  result[std::forward<decltype(i)>(i)] = std::forward<Op>(op)(
113  std::forward<Tensor>(tensor)[std::forward<decltype(i)>(i)],
114  std::forward<Tensors>(tensors)[std::forward<decltype(i)>(i)]...);
115  }
116 
117  template <typename Op, typename Tensor, typename ... Tensors>
118  T operator()(Op&& op, const Permutation& perm, Tensor&& tensor, Tensors&& ... tensors) const {
119 
120  TA_ASSERT(! empty(tensor, tensors...));
121  TA_ASSERT(is_range_set_congruent(tensor, tensors...));
122  TA_ASSERT(perm);
123  TA_ASSERT(perm.dim() == tensor.range().rank());
124 
125  const auto& range = tensor.range();
126  T result(perm ^ range);
127  this->operator()(result, std::forward<Op>(op), perm, std::forward<Tensor>(tensor), std::forward<Tensors>(tensors)...);
128  return result;
129  }
130 
131  template <typename Op, typename Tensor, typename ... Tensors>
132  void operator()(T& result, Op&& op, const Permutation& perm, Tensor&& tensor, Tensors&& ... tensors) const {
133  TA_ASSERT(! empty(result, tensor, tensors...));
134  TA_ASSERT(is_range_congruent(result, tensor, perm));
135  TA_ASSERT(is_range_set_congruent(tensor, tensors...));
136  TA_ASSERT(perm);
137  TA_ASSERT(perm.dim() == tensor.range().rank());
138 
139  const auto& range = tensor.range();
140  for (auto&& i : range)
141  result[perm ^ std::forward<decltype(i)>(i)] = std::forward<Op>(op)(
142  std::forward<Tensor>(tensor)[std::forward<decltype(i)>(i)],
143  std::forward<Tensors>(tensors)[std::forward<decltype(i)>(i)]...);
144  }
145  };
146 
147  // -------------------------------------------------------------------------
148  // Tensor kernel operations with in-place memory operations
149 
151 
160  template <typename Op, typename TR, typename... Ts,
161  typename std::enable_if<is_tensor<TR, Ts...>::value
162  && is_contiguous_tensor<TR, Ts...>::value>::type* = nullptr>
163  inline void inplace_tensor_op(Op&& op, TR& result, const Ts&... tensors) {
164  TA_ASSERT(! empty(result, tensors...));
165  TA_ASSERT(is_range_set_congruent(result, tensors...));
166 
167  const auto volume = result.range().volume();
168 
169  math::inplace_vector_op(op, volume, result.data(),
170  tensors.data()...);
171  }
172 
174 
183  template <typename Op, typename TR, typename... Ts,
184  typename std::enable_if<is_tensor_of_tensor<TR, Ts...>::value
185  && is_contiguous_tensor<TR, Ts...>::value>::type* = nullptr>
186  inline void inplace_tensor_op(Op&& op, TR& result, const Ts&... tensors) {
187  TA_ASSERT(! empty(result, tensors...));
188  TA_ASSERT(is_range_set_congruent(result, tensors...));
189 
190  const auto volume = result.range().volume();
191 
192  for(decltype(result.range().volume()) i = 0ul; i < volume; ++i) {
193  inplace_tensor_op(op, result[i], tensors[i]...);
194  }
195  }
196 
198 
222  template <typename InputOp, typename OutputOp, typename TR, typename T1, typename... Ts,
223  typename std::enable_if<is_tensor<TR, T1, Ts...>::value
224  && is_contiguous_tensor<TR, T1, Ts...>::value>::type* = nullptr>
225  inline void inplace_tensor_op(InputOp&& input_op, OutputOp&& output_op,
226  const Permutation& perm, TR& result, const T1& tensor1,
227  const Ts&... tensors)
228  {
229  TA_ASSERT(! empty(result, tensor1, tensors...));
230  TA_ASSERT(is_range_congruent(result, tensor1, perm));
231  TA_ASSERT(is_range_set_congruent(tensor1, tensors...));
232  TA_ASSERT(perm);
233  TA_ASSERT(perm.dim() == tensor1.range().rank());
234 
235  permute(input_op, output_op, result, perm, tensor1, tensors...);
236  }
237 
238 
240 
264  template <typename InputOp, typename OutputOp, typename TR, typename T1, typename... Ts,
265  typename std::enable_if<is_tensor_of_tensor<TR, T1, Ts...>::value
266  && is_contiguous_tensor<TR, T1, Ts...>::value>::type* = nullptr>
267  inline void inplace_tensor_op(InputOp&& input_op, OutputOp&& output_op,
268  const Permutation& perm, TR& result, const T1& tensor1,
269  const Ts&... tensors)
270  {
271  TA_ASSERT(! empty(result, tensor1, tensors...));
272  TA_ASSERT(is_range_congruent(result, tensor1, perm));
273  TA_ASSERT(is_range_set_congruent(tensor1, tensors...));
274  TA_ASSERT(perm);
275  TA_ASSERT(perm.dim() == tensor1.range().rank());
276 
277  auto wrapper_input_op = [=] (typename T1::const_reference MADNESS_RESTRICT value1,
278  typename Ts::const_reference MADNESS_RESTRICT... values) ->
279  typename T1::value_type
280  { return tensor_op<TR::value_type>(input_op, value1, values...); };
281 
282  auto wrapper_output_op = [=] (typename T1::pointer MADNESS_RESTRICT const result_value,
283  const typename TR::value_type value)
284  { inplace_tensor_op(output_op, *result_value, value); };
285 
286  permute(wrapper_input_op, wrapper_output_op, result, perm, tensor1,
287  tensors...);
288  }
289 
291 
300  template <typename Op, typename TR, typename... Ts,
301  typename std::enable_if<is_tensor<TR, Ts...>::value
302  && ! (is_contiguous_tensor<TR, Ts...>::value)>::type* = nullptr>
303  inline void inplace_tensor_op(Op&& op, TR& result, const Ts&... tensors) {
304  TA_ASSERT(! empty(result, tensors...));
305  TA_ASSERT(is_range_set_congruent(result, tensors...));
306 
307  const auto stride = inner_size(result, tensors...);
308  const auto volume = result.range().volume();
309 
310  for(decltype(result.range().volume()) i = 0ul; i < volume; i += stride)
311  math::inplace_vector_op(op, stride, result.data() + result.range().ordinal(i),
312  (tensors.data() + tensors.range().ordinal(i))...);
313  }
314 
316 
325  template <typename Op, typename TR, typename... Ts,
326  typename std::enable_if<is_tensor_of_tensor<TR, Ts...>::value
327  && ! (is_contiguous_tensor<TR, Ts...>::value)>::type* = nullptr>
328  inline void inplace_tensor_op(Op&& op, TR& result, const Ts&... tensors) {
329  TA_ASSERT(! empty(result, tensors...));
330  TA_ASSERT(is_range_set_congruent(result, tensors...));
331 
332  const auto stride = inner_size(result, tensors...);
333  const auto volume = result.range().volume();
334 
335  auto inplace_tensor_range =
336  [=] (typename TR::pointer MADNESS_RESTRICT const result_data,
337  typename Ts::const_pointer MADNESS_RESTRICT const... tensors_data)
338  {
339  for(decltype(result.range().volume()) i = 0ul; i < stride; ++i)
340  inplace_tensor_op(op, result_data[i], tensors_data[i]...);
341  };
342 
343  for(decltype(result.range().volume()) i = 0ul; i < volume; i += stride)
344  inplace_tensor_range(result.data() + result.range().ordinal(i),
345  (tensors.data() + tensors.range().ordinal(i))...);
346  }
347 
348  // -------------------------------------------------------------------------
349  // Tensor initialization functions for argument tensors with contiguous
350  // memory layout
351 
353 
363  template <typename Op, typename TR, typename... Ts,
364  typename std::enable_if<is_tensor<TR, Ts...>::value
365  && is_contiguous_tensor<TR, Ts...>::value>::type* = nullptr>
366  inline void tensor_init(Op&& op, TR& result, const Ts&... tensors) {
367  TA_ASSERT(! empty(result, tensors...));
368  TA_ASSERT(is_range_set_congruent(result, tensors...));
369 
370  const auto volume = result.range().volume();
371 
372  auto wrapper_op = [=] (typename TR::pointer MADNESS_RESTRICT result,
373  typename Ts::const_reference MADNESS_RESTRICT... ts)
374  { new(result) typename TR::value_type(op(ts...)); };
375 
376  math::vector_ptr_op(wrapper_op, volume, result.data(), tensors.data()...);
377  }
378 
380 
390  template <typename Op, typename TR, typename... Ts,
391  typename std::enable_if<is_tensor_of_tensor<TR, Ts...>::value
392  && is_contiguous_tensor<TR, Ts...>::value>::type* = nullptr>
393  inline void tensor_init(Op&& op, TR& result, const Ts&... tensors) {
394  TA_ASSERT(! empty(result, tensors...));
395  TA_ASSERT(is_range_set_congruent(result, tensors...));
396 
397  const auto volume = result.range().volume();
398 
399  for(decltype(result.range().volume()) i = 0ul; i < volume; ++i) {
400  new(result.data() + i)
401  typename TR::value_type(tensor_op<typename TR::value_type>(op, tensors[i]...));
402  }
403  }
404 
405 
407 
420  template <typename Op, typename TR, typename T1, typename... Ts,
421  typename std::enable_if<is_tensor<TR, T1, Ts...>::value
422  && is_contiguous_tensor<TR, T1, Ts...>::value>::type* = nullptr>
423  inline void tensor_init(Op&& op, const Permutation& perm, TR& result,
424  const T1& tensor1, const Ts&... tensors)
425  {
426  TA_ASSERT(! empty(result, tensor1, tensors...));
427  TA_ASSERT(is_range_set_congruent(perm, result, tensor1, tensors...));
428  TA_ASSERT(perm);
429  TA_ASSERT(perm.dim() == result.range().rank());
430 
431  auto output_op = [=] (typename TR::pointer MADNESS_RESTRICT result,
432  typename TR::const_reference MADNESS_RESTRICT temp)
433  { new(result) typename TR::value_type(temp); };
434 
435  permute(op, output_op, result, perm, tensor1, tensors...);
436  }
437 
438 
440 
453  template <typename Op, typename TR, typename T1, typename... Ts,
454  typename std::enable_if<is_tensor_of_tensor<TR, T1, Ts...>::value
455  && is_contiguous_tensor<TR, T1, Ts...>::value>::type* = nullptr>
456  inline void tensor_init(Op&& op, const Permutation& perm, TR& result,
457  const T1& tensor1, const Ts&... tensors)
458  {
459  TA_ASSERT(! empty(result, tensor1, tensors...));
460  TA_ASSERT(is_range_set_congruent(perm, result, tensor1, tensors...));
461  TA_ASSERT(perm);
462  TA_ASSERT(perm.dim() == result.range().rank());
463 
464  auto output_op = [=] (typename TR::pointer MADNESS_RESTRICT result,
465  typename TR::const_reference MADNESS_RESTRICT temp)
466  { new(result) typename TR::value_type(temp); };
467  auto tensor_input_op = [=] (typename T1::const_reference MADNESS_RESTRICT value1,
468  typename Ts::const_reference MADNESS_RESTRICT... values) ->
469  typename TR::value_type
470  { return tensor_op<typename TR::value_type>(op, value1, values...); };
471 
472  permute(tensor_input_op, output_op, result, perm, tensor1, tensors...);
473  }
474 
475 
477 
488  template <typename Op, typename TR, typename T1, typename... Ts,
489  typename std::enable_if<is_tensor<TR, T1, Ts...>::value
490  && is_contiguous_tensor<TR>::value
491  && ! is_contiguous_tensor<T1, Ts...>::value>::type* = nullptr>
492  inline void tensor_init(Op&& op, TR& result, const T1& tensor1,
493  const Ts&... tensors)
494  {
495  TA_ASSERT(! empty(result, tensor1, tensors...));
496  TA_ASSERT(is_range_set_congruent(result, tensor1, tensors...));
497 
498  const auto stride = inner_size(tensor1, tensors...);
499  const auto volume = tensor1.range().volume();
500 
501  auto wrapper_op = [=] (typename TR::pointer MADNESS_RESTRICT result_ptr,
502  const typename T1::value_type value1,
503  const typename Ts::value_type... values)
504  { new(result_ptr) typename T1::value_type(op(value1, values...)); };
505 
506  for(decltype(tensor1.range().volume()) i = 0ul; i < volume; i += stride)
507  math::vector_ptr_op(wrapper_op, stride, result.data() + i,
508  (tensor1.data() + tensor1.range().ordinal(i)),
509  (tensors.data() + tensors.range().ordinal(i))...);
510  }
511 
513 
524  template <typename Op, typename TR, typename T1, typename... Ts,
525  typename std::enable_if<is_tensor_of_tensor<TR, T1, Ts...>::value
526  && is_contiguous_tensor<TR>::value
527  && ! is_contiguous_tensor<T1, Ts...>::value>::type* = nullptr>
528  inline void tensor_init(Op&& op, TR& result, const T1& tensor1,
529  const Ts&... tensors)
530  {
531  TA_ASSERT(! empty(result, tensor1, tensors...));
532  TA_ASSERT(is_range_set_congruent(result, tensor1, tensors...));
533 
534  const auto stride = inner_size(tensor1, tensors...);
535  const auto volume = tensor1.range().volume();
536 
537 
538  auto inplace_tensor_range =
539  [=] (typename TR::pointer MADNESS_RESTRICT const result_data,
540  typename T1::const_pointer MADNESS_RESTRICT const tensor1_data,
541  typename Ts::const_pointer MADNESS_RESTRICT const... tensors_data)
542  {
543  for(decltype(result.range().volume()) i = 0ul; i < stride; ++i)
544  new(result_data + i)
545  typename TR::value_type(tensor_op<typename TR::value_type>(op,
546  tensor1_data[i], tensors_data[i]...));
547  };
548 
549  for(decltype(volume) i = 0ul; i < volume; i += stride)
550  inplace_tensor_range(result.data() + i,
551  (tensor1.data() + tensor1.range().ordinal(i)),
552  (tensors.data() + tensors.range().ordinal(i))...);
553  }
554 
555 
556  // -------------------------------------------------------------------------
557  // Reduction kernels for argument tensors
558 
560 
576  template <typename ReduceOp, typename JoinOp, typename Scalar, typename T1, typename... Ts,
577  typename std::enable_if_t<is_tensor<T1, Ts...>::value
578  && is_contiguous_tensor<T1, Ts...>::value>* = nullptr>
579  Scalar tensor_reduce(ReduceOp&& reduce_op, JoinOp&& join_op,
580  Scalar identity, const T1& tensor1, const Ts&... tensors)
581  {
582  TA_ASSERT(! empty(tensor1, tensors...));
583  TA_ASSERT(is_range_set_congruent(tensor1, tensors...));
584 
585  const auto volume = tensor1.range().volume();
586 
587  math::reduce_op(reduce_op, join_op, identity, volume, identity,
588  tensor1.data(), tensors.data()...);
589 
590  return identity;
591  }
592 
594 
611  template <typename ReduceOp, typename JoinOp, typename Scalar, typename T1, typename... Ts,
612  typename std::enable_if<is_tensor_of_tensor<T1, Ts...>::value
613  && is_contiguous_tensor<T1, Ts...>::value>::type* = nullptr>
614  Scalar tensor_reduce(ReduceOp&& reduce_op, JoinOp&& join_op,
615  Scalar identity, const T1& tensor1, const Ts&... tensors)
616  {
617  TA_ASSERT(! empty(tensor1, tensors...));
618  TA_ASSERT(is_range_set_congruent(tensor1, tensors...));
619 
620  const auto volume = tensor1.range().volume();
621 
622  auto result = identity;
623  for(decltype(tensor1.range().volume()) i = 0ul; i < volume; ++i) {
624  auto temp = tensor_reduce(reduce_op, join_op, identity, tensor1[i],
625  tensors[i]...);
626  join_op(result, temp);
627  }
628 
629  return result;
630  }
631 
633 
650  template <typename ReduceOp, typename JoinOp, typename Scalar, typename T1, typename... Ts,
651  typename std::enable_if<is_tensor<T1, Ts...>::value
652  && ! is_contiguous_tensor<T1, Ts...>::value>::type* = nullptr>
653  Scalar tensor_reduce(ReduceOp&& reduce_op, JoinOp&& join_op,
654  const Scalar identity, const T1& tensor1, const Ts&... tensors)
655  {
656  TA_ASSERT(! empty(tensor1, tensors...));
657  TA_ASSERT(is_range_set_congruent(tensor1, tensors...));
658 
659  const auto stride = inner_size(tensor1, tensors...);
660  const auto volume = tensor1.range().volume();
661 
662  Scalar result = identity;
663  for(decltype(tensor1.range().volume()) i = 0ul; i < volume; i += stride) {
664  Scalar temp = identity;
665  math::reduce_op(reduce_op,join_op, identity, stride, temp,
666  tensor1.data() + tensor1.range().ordinal(i),
667  (tensors.data() + tensors.range().ordinal(i))...);
668  join_op(result, temp);
669  }
670 
671  return result;
672  }
673 
675 
692  template <typename ReduceOp, typename JoinOp, typename Scalar, typename T1, typename... Ts,
693  typename std::enable_if<
694  is_tensor_of_tensor<T1, Ts...>::value
695  && ! is_contiguous_tensor<T1, Ts...>::value>::type* = nullptr>
696  Scalar tensor_reduce(ReduceOp&& reduce_op, JoinOp&& join_op,
697  const Scalar identity, const T1& tensor1, const Ts&... tensors)
698  {
699  TA_ASSERT(! empty(tensor1, tensors...));
700  TA_ASSERT(is_range_set_congruent(tensor1, tensors...));
701 
702  const auto stride = inner_size(tensor1, tensors...);
703  const auto volume = tensor1.range().volume();
704 
705  auto tensor_reduce_range =
706  [=] (Scalar& MADNESS_RESTRICT result,
707  typename T1::const_pointer MADNESS_RESTRICT const tensor1_data,
708  typename Ts::const_pointer MADNESS_RESTRICT const... tensors_data)
709  {
710  for(decltype(result.range().volume()) i = 0ul; i < stride; ++i) {
711  Scalar temp = tensor_reduce(reduce_op, join_op, identity,
712  tensor1_data[i], tensors_data[i]...);
713  join_op(result, temp);
714  }
715  };
716 
717  Scalar result = identity;
718  for(decltype(tensor1.range().volume()) i = 0ul; i < volume; i += stride) {
719  Scalar temp = tensor_reduce_range(result,
720  tensor1.data() + tensor1.range().ordinal(i),
721  (tensors.data() + tensors.range().ordinal(i))...);
722  join_op(result, temp);
723  }
724 
725  return identity;
726  }
727 
728  } // namespace detail
729 } // namespace TiledArray
730 
731 #endif // TILEDARRAY_TENSOR_KENERLS_H__INCLUDED
void inplace_tensor_op(Op &&op, TR &result, const Ts &... tensors)
In-place tensor operations with contiguous data.
Definition: kernels.h:163
T operator()(Op &&op, const Permutation &perm, Tensor &&tensor, Tensors &&... tensors) const
Definition: kernels.h:118
void permute(InputOp &&input_op, OutputOp &&output_op, Result &result, const Permutation &perm, const Arg0 &arg0, const Args &... args)
Construct a permuted tensor copy.
Definition: permute.h:122
constexpr bool is_range_set_congruent(const Permutation &perm, const T &tensor)
Test that the ranges of a permuted tensor is congruent with itself.
Definition: utility.h:179
bool is_range_congruent(const Left &left, const ShiftWrapper< Right > &right)
Check for congruent range objects with a shifted tensor.
An N-dimensional tensor object.
Definition: foreach.h:40
T operator()(Op &&op, Tensor &&tensor, Tensors &&... tensors) const
creates a result tensor in which element i is obtained by op(tensor[i], tensors[i]...)
Definition: kernels.h:93
void tensor_init(Op &&op, TR &result, const Ts &... tensors)
Initialize tensor with contiguous tensor arguments.
Definition: kernels.h:366
T1::size_type inner_size(const T1 &tensor1, const T2 &)
Get the inner size of two tensors.
Definition: utility.h:313
customization point transform functionality to tensor class T, useful for nonintrusive extension of T...
Definition: kernels.h:41
index_type dim() const
Domain size accessor.
Definition: permutation.h:206
constexpr bool empty()
Test for empty tensors in an empty list.
Definition: utility.h:374
#define TA_ASSERT(a)
Definition: error.h:107
T identity()
identity for group of objects of type T
void reduce_op(ReduceOp &&reduce_op, JoinOp &&join_op, const Result &identity, const std::size_t n, Result &result, const Args *const ... args)
Definition: vector_op.h:640
void operator()(T &result, Op &&op, const Permutation &perm, Tensor &&tensor, Tensors &&... tensors) const
Definition: kernels.h:132
void inplace_vector_op(Op &&op, const std::size_t n, Result *const result, const Args *const ... args)
Definition: vector_op.h:397
Permutation of a sequence of objects indexed by base-0 indices.
Definition: permutation.h:119
TR tensor_op(Op &&op, const T1 &tensor1, const Ts &... tensors)
Tensor operations with contiguous data.
Definition: kernels.h:60
Scalar tensor_reduce(ReduceOp &&reduce_op, JoinOp &&join_op, Scalar identity, const T1 &tensor1, const Ts &... tensors)
Reduction operation for contiguous tensors.
Definition: kernels.h:579
void vector_ptr_op(Op &&op, const std::size_t n, Result *const result, const Args *const ... args)
Definition: vector_op.h:546
void operator()(T &result, Op &&op, Tensor &&tensor, Tensors &&... tensors) const
Definition: kernels.h:106