Matrix.h
1 /* Copyright (C) 2020 IBM Corp.
2  * This program is Licensed under the Apache License, Version 2.0
3  * (the "License"); you may not use this file except in compliance
4  * with the License. You may obtain a copy of the License at
5  * http://www.apache.org/licenses/LICENSE-2.0
6  * Unless required by applicable law or agreed to in writing, software
7  * distributed under the License is distributed on an "AS IS" BASIS,
8  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9  * See the License for the specific language governing permissions and
10  * limitations under the License. See accompanying LICENSE file.
11  */
12 #ifndef HELIB_MATRIX_H
13 #define HELIB_MATRIX_H
14 
15 #include <iostream>
16 #include <utility> // swap
17 #include <vector>
18 #include <numeric>
19 #include <array>
20 #include <string>
21 #include <algorithm>
22 #include <functional>
23 #include <type_traits>
24 #include <initializer_list>
25 
26 #include <NTL/BasicThreadPool.h>
27 
28 #include "assertions.h"
29 #include "helib.h"
30 #include "zeroValue.h"
31 
32 /*
33  * The code in this file should be considered very alpha.
34  * It is in flux, so not advised for public use, yet.
35  * These APIs differ from matmul; here we think of vectors of Ctxts
36  * not a Ctxt encoding a vector.
37  */
38 
39 namespace helib {
40 
41 template <std::size_t N>
43 {
44  std::array<std::size_t, N> lengths; // the dims.
45  std::array<std::size_t, N> strides;
46  std::vector<long> start = {0};
47  std::size_t size = 0;
48 
49  // Constructor used when taking a 'view'
50  template <typename Iter1, typename Iter2>
51  TensorSlice(const Iter1& firstLength,
52  const Iter1& lastLength,
53  const Iter2& firstStride,
54  const Iter2& lastStride,
55  const std::vector<long>& st) :
56  start(st)
57  {
58  std::copy(firstLength, lastLength, this->lengths.begin());
59  std::copy(firstStride, lastStride, this->strides.begin());
60  this->size = (std::accumulate(lengths.begin(),
61  lengths.end(),
62  1,
63  std::multiplies<std::size_t>()));
64  }
65 
66  // Constructor used when taking a 'view'
67  template <typename Iter1, typename Iter2>
68  TensorSlice(const Iter1& firstLength,
69  const Iter1& lastLength,
70  const Iter2& firstStride,
71  const Iter2& lastStride,
72  unsigned long pos) :
73  start(1, pos)
74  {
75  std::copy(firstLength, lastLength, this->lengths.begin());
76  std::copy(firstStride, lastStride, this->strides.begin());
77  this->size = (std::accumulate(lengths.begin(),
78  lengths.end(),
79  1,
80  std::multiplies<std::size_t>()));
81  }
82 
83  // Constructor for setting up a Tensor.
84  template <typename... Dims>
85  TensorSlice(Dims... dims) :
86  lengths{std::size_t(dims)...},
87  size(std::accumulate(lengths.begin(),
88  lengths.end(),
89  1,
90  std::multiplies<std::size_t>()))
91  {
92  // TODO add helib::assert or static checkhere w.r.t. N
93  this->strides.back() = 1;
94  if (N > 1) {
95  std::copy(lengths.begin() + 1, lengths.end(), strides.begin());
96  }
97  }
98 
99  template <typename... Dims>
100  std::size_t operator()(Dims... dims) const
101  {
102  static_assert(sizeof...(Dims) == N, "Wrong number of indices given.");
103 
104  std::array<std::size_t, N> args{std::size_t(dims)...};
105  // Check indices are within bounds.
106  for (long i = 0; i < long(N); ++i) {
107  if (args[i] >= this->lengths[i]) {
109  "Index given: " + std::to_string(args[i]) +
110  ". Max value is: " + std::to_string(this->lengths[i]));
111  }
112  }
113 
114  if (this->start.size() == 1) {
115  return std::inner_product(args.begin(),
116  args.end(),
117  this->strides.begin(),
118  this->start.at(0));
119  } else {
120  // FIXME - this will only work with Matrices
121  return std::inner_product(args.begin(),
122  args.end(),
123  this->strides.begin(),
124  this->start.at(args.at(1)) * strides.back());
125  }
126  }
127 
128  std::size_t order() const { return N; }
129 
130  bool operator==(const TensorSlice& rhs) const
131  {
132  if (this == &rhs)
133  return true;
134  else if (this->size == rhs.size && this->start == rhs.start &&
135  this->lengths == rhs.lengths && this->strides == rhs.strides)
136  return true;
137  else
138  return false;
139  }
140 
141  bool operator!=(const TensorSlice& rhs) const { return !(*this == rhs); }
142 };
143 
144 // N is the dimension of the tensor
145 // At the moment we care about N = 1 or 2 only.
146 
147 template <typename T, std::size_t N>
148 class Tensor
149 {
150 
151 private:
152  TensorSlice<N> subscripts;
153  std::shared_ptr<std::vector<T>> elements_ptr;
154  bool full_view = true;
155 
156 public:
157  // Construct with all entries equal to obj, often useful if T does not have a
158  // default constructor or if default-constructed Ts are invalid.
159  // This has to be disabled if dims is convertible to size_t for now,
160  // otherwise a call to e.g. Tensor(obj, 5) will use the below version
161  // instead of this. TODO: think of a way around this.
162  template <typename U = T,
163  typename... Dims,
164  typename std::enable_if_t<
165  !std::is_convertible<U, std::size_t>::value>* = nullptr>
166  Tensor(const T& obj, Dims... dims) :
167  subscripts{dims...},
168  elements_ptr(std::make_shared<std::vector<T>>(subscripts.size, obj))
169  {}
170 
171  template <typename... Dims>
172  Tensor(Dims... dims) :
173  subscripts{std::size_t(dims)...},
174  elements_ptr(std::make_shared<std::vector<T>>(subscripts.size))
175  {}
176 
177  // TODO generalise this
178  Tensor(std::initializer_list<std::vector<T>> lst) :
179  subscripts{lst.size(), lst.begin()->size()},
180  elements_ptr(
181  std::make_shared<std::vector<T>>(lst.size() * lst.begin()->size()))
182  {
183  int column_length = lst.begin()->size();
184  int cnt = 0;
185  for (const auto& v : lst) {
186  if (column_length != long(v.size()))
187  throw helib::LogicError(
188  "Column dimensions do not match on initializer list.");
189 
190  std::copy(v.begin(),
191  v.end(),
192  this->elements_ptr->begin() + (column_length * cnt++));
193  }
194  }
195 
197  const std::shared_ptr<std::vector<T>>& elems) :
198  subscripts(ts), elements_ptr(elems), full_view(false)
199  {}
200 
201  Tensor(const Tensor& other) = default;
202  Tensor(Tensor&& other) = default;
203 
204  Tensor& operator=(const Tensor& rhs) = default;
205  Tensor& operator=(Tensor&& rhs) = default;
206 
207  ~Tensor() = default;
208 
209  // This returns a deep copy as the default copy constructor returns a shallow
210  // copy
212  {
213  // Shallow copy
214  auto copy = *this;
215 
216  // Now we do a deep copy of the data
217  copy.elements_ptr = std::make_shared<std::vector<T>>(*elements_ptr);
218 
219  return copy;
220  }
221 
222  std::size_t order() const { return N; }
223 
224  template <typename... Args>
225  T& operator()(Args... args)
226  {
227  return this->elements_ptr->at(subscripts(args...));
228  }
229 
230  template <typename... Args>
231  const T& operator()(Args... args) const
232  {
233  return this->elements_ptr->at(subscripts(args...));
234  }
235 
236  std::size_t size() const { return this->subscripts.size; }
237 
238  std::size_t dims(int i) const { return this->subscripts.lengths.at(i); }
239 
240  bool fullView() const { return this->full_view; }
241 
242  bool operator==(const Tensor& rhs) const
243  {
244  if (this == &rhs) {
245  return true;
246  } else if (this->subscripts != rhs.subscripts) {
247  return false;
248  } else if (this->fullView() && rhs.fullView() &&
249  *elements_ptr == *(rhs.elements_ptr)) {
250  return true;
251  } else {
252  for (size_t i = 0; i < dims(0); ++i)
253  for (size_t j = 0; j < dims(1); ++j)
254  if (this->operator()(i, j) != rhs(i, j)) {
255  return false;
256  }
257  return true;
258  }
259  }
260 
261  bool operator!=(const Tensor& rhs) const { return !(*this == rhs); }
262 
263  Tensor<T, N - 1> row(std::size_t i) const
264  {
265  TensorSlice<N - 1> ts(this->subscripts.lengths.begin() + 1,
266  this->subscripts.lengths.end(),
267  this->subscripts.strides.begin() + 1,
268  this->subscripts.strides.end(),
269  /*row X strides*/ i * this->subscripts.strides.at(0));
270  return Tensor<T, N - 1>(ts, this->elements_ptr);
271  }
272 
273  Tensor<T, N - 1> column(std::size_t j) const
274  {
275  // TODO - can only exist with N > 1
276  TensorSlice<N - 1> ts(this->subscripts.lengths.begin(),
277  this->subscripts.lengths.end() - 1,
278  this->subscripts.strides.begin(),
279  this->subscripts.strides.end() - 1,
280  /*col*/ j);
281  return Tensor<T, N - 1>(ts, this->elements_ptr);
282  }
283 
284  Tensor<T, 2> getRow(std::size_t i) const
285  {
286  if (i >= this->dims(0)) {
287  throw OutOfRangeError("Index given: " + std::to_string(i) +
288  ". Max value is: " + std::to_string(this->dims(0)));
289  }
290  TensorSlice<2> ts(this->subscripts);
291  ts.lengths.front() = 1;
292  ts.start = {/*row X strides*/ static_cast<long>(i * ts.strides.at(0))};
293  ts.size = ts.lengths[1];
294 
295  return Tensor<T, 2>(ts, this->elements_ptr);
296  }
297 
298  Tensor<T, 2> getColumn(std::size_t j) const
299  {
300  if (j >= this->dims(1)) {
301  throw OutOfRangeError("Index given: " + std::to_string(j) +
302  ". Max value is: " + std::to_string(this->dims(1)));
303  }
304  TensorSlice<2> ts(this->subscripts);
305  ts.lengths.back() = 1;
306  ts.start = {static_cast<long>(j)};
307  ts.size = ts.lengths[0];
308 
309  return Tensor<T, 2>(ts, this->elements_ptr);
310  }
311 
312  // FIXME - returns several columns, same order as this. Only Matrices.
313  Tensor<T, N> columns(const std::vector<long>& js) const
314  {
315  // Check the js are valid
316  for (const auto& j : js)
317  assertInRange<LogicError>(
318  j,
319  0l,
320  static_cast<long>(this->dims(1)),
321  "Index for column does not exist. Given index " + std::to_string(j) +
322  ". Expected index in " + "range [0, " +
323  std::to_string(this->dims(1)) + ").");
324 
325  // FIXME: lengths - works only for matrices at mo.
326  std::vector<std::size_t> lengths = {this->dims(0), js.size()};
327 
328  std::vector<long> offsets(js);
329  for (std::size_t i = 0; i < offsets.size(); ++i) {
330  offsets[i] -= i;
331  }
332 
333  TensorSlice<N> ts(lengths.begin(),
334  lengths.end(),
335  this->subscripts.strides.begin(),
336  this->subscripts.strides.end(),
337  offsets);
338 
339  return Tensor<T, N>(ts, this->elements_ptr);
340  }
341 
342  template <typename T2>
344  std::function<T&(T&, const T2&)> operation)
345  {
346  // rhs is not of the same type thus need the dims.
347  std::array<std::size_t, N> rhs_subscripts;
348  for (std::size_t i = 0; i < N; ++i) {
349  rhs_subscripts[i] = rhs.dims(i);
350  }
351 
352  // Sanity Check: Are they the same dimensions?
353  if (!std::equal(this->subscripts.lengths.begin(),
354  this->subscripts.lengths.end(),
355  rhs_subscripts.begin())) {
356  throw helib::LogicError("Matrix dimensions do not match.");
357  }
358 
359  // TODO For now, we do not allow views to operate on views to the same data.
360  if (static_cast<const void*>(&this->data()) ==
361  static_cast<const void*>(&rhs.data())) {
362  throw helib::LogicError("Views point to same underlying data.");
363  }
364 
365  // Optimisation if they have full view of underlying memmory.
366  if (this->full_view && rhs.fullView()) {
367  const std::vector<T2>& rhs_v = rhs.data();
368  for (std::size_t i = 0; i < this->elements_ptr->size(); ++i) {
369  operation((*this->elements_ptr)[i], rhs_v[i]);
370  }
371  } else {
372  // TODO - again will only work for Matrices.
373  for (std::size_t j = 0; j < this->dims(1); ++j) {
374  for (std::size_t i = 0; i < this->dims(0); ++i) {
375  operation(this->operator()(i, j), rhs(i, j));
376  }
377  }
378  }
379 
380  return *this;
381  }
382 
383  template <typename T2>
385  {
386  return entrywiseOperation<T2>(
387  rhs,
388  [](auto& lhs, const auto& rhs) -> decltype(auto) {
389  return lhs += rhs;
390  });
391  }
392 
393  template <typename T2>
395  {
396  return entrywiseOperation<T2>(
397  rhs,
398  [](auto& lhs, const auto& rhs) -> decltype(auto) {
399  return lhs -= rhs;
400  });
401  }
402 
403  template <typename T2>
405  {
406  return entrywiseOperation<T2>(
407  rhs,
408  [](auto& lhs, const auto& rhs) -> decltype(auto) {
409  return lhs *= rhs;
410  });
411  }
412 
413  Tensor<T, N>& apply(std::function<void(T& x)> fn)
414  {
415  // Optimisation if they have full view of underlying memory.
416  if (this->full_view) {
417  NTL_EXEC_RANGE(long(this->elements_ptr->size()), first, last)
418  for (long i = first; i < last; ++i)
419  fn((*elements_ptr)[i]);
420  NTL_EXEC_RANGE_END
421 
422  } else {
423  // TODO - again will only work for Matrices.
424  NTL_EXEC_RANGE(this->dims(1), first, last)
425  for (long j = first; j < last; ++j)
426  for (std::size_t i = 0; i < this->dims(0); ++i)
427  fn(this->operator()(i, j));
428  NTL_EXEC_RANGE_END
429  }
430  return *this;
431  }
432 
433  // Matrix special
435  {
436  auto ret = this->deepCopy();
437  ret.inPlaceTranspose();
438  return ret;
439  }
440 
441  // Matrix special
443  {
444  if (fullView()) {
445  // In this case we can perform a transpose copy-free using swaps
446  // First express the desired permutation as a list of numbers
447  std::vector<int> permutation(size());
448  std::iota(permutation.begin(), permutation.end(), 0);
449  for (int& num : permutation)
450  num = (num % subscripts.lengths[1]) * subscripts.lengths[0] +
451  num / subscripts.lengths[1];
452  // Now write the permutation as a list of disjoint cycles
453  std::vector<std::vector<int>> cycles;
454  std::vector<bool> seen(size(), false);
455  int num_processed = 0;
456  int current_pos = 0;
457  while (num_processed < long(size())) {
458  // Build the cycle starting at current_pos
459  std::vector<int> cycle = {current_pos};
460  seen[current_pos] = true;
461  while (permutation.at(cycle.back()) != cycle.front()) {
462  seen[permutation.at(cycle.back())] = true;
463  cycle.push_back(permutation.at(cycle.back()));
464  }
465  num_processed += cycle.size();
466  cycles.push_back(std::move(cycle));
467  // Move current_pos to the next non-seen value
468  while (current_pos < long(size()) && seen[current_pos])
469  ++current_pos;
470  }
471  // Now turn this product of disjoint cycles into a list of pairs
472  std::vector<std::pair<int, int>> swaps;
473  for (const auto& cycle : cycles)
474  if (cycle.size() >= 2)
475  for (int i = cycle.size() - 1; i > 0; --i)
476  swaps.emplace_back(cycle[i], cycle[i - 1]);
477  // Now do the swaps
478  for (const auto& swap : swaps)
479  std::swap(elements_ptr->at(swap.first), elements_ptr->at(swap.second));
480  } else {
481  auto new_elements =
482  std::make_shared<std::vector<T>>(size(), data().front());
483  int count = size();
484  int i = 0;
485  int j = 0;
486  int new_i = 0;
487  int new_j = 0;
488  while (count--) {
489  // Put the new element in the correct place
490  new_elements->at(new_i * subscripts.lengths[0] + new_j) = operator()(i,
491  j);
492 
493  // Increment everything.
494  // (i,j) wraps around as per the current configuration.
495  j = (j + 1) % subscripts.lengths[1];
496  if (j == 0)
497  i = (i + 1) % subscripts.lengths[0];
498 
499  // (new_i,new_j) increments and wraps around as per the new, transposed
500  // configuration.
501  new_i = (new_i + 1) % subscripts.lengths[1];
502  if (new_i == 0)
503  new_j = (new_j + 1) % subscripts.lengths[0];
504  }
505  elements_ptr = new_elements;
506  full_view = true;
507  }
508  std::reverse(subscripts.lengths.begin(), subscripts.lengths.end());
509  subscripts.strides.front() = subscripts.lengths.back();
510  subscripts.start = {0};
511  return *this;
512  }
513 
514  const std::vector<T>& data() const { return *this->elements_ptr; }
515 };
516 
517 // Matrix special - Different types
518 template <typename T,
519  typename T2,
520  typename std::enable_if_t<
521  std::is_convertible<T, std::size_t>::value>* = nullptr>
522 inline Tensor<T, 2> operator*(const Tensor<T, 2>& M1, const Tensor<T2, 2>& M2)
523 {
524  HELIB_NTIMER_START(MatrixMultiplicationConv);
525  // rows from M1 x cols from M2
526  Tensor<T, 2> R(M1.dims(0), M2.dims(1));
527 
528  // Sanity check: M1 cols should match M2 rows
529  if (M1.dims(1) != M2.dims(0)) {
530  throw helib::LogicError("Matrix inner dimensions do not match.");
531  }
532 
533  // TODO add NTL thread pool.
534  // TODO swap += for some binary sum.
535  NTL_EXEC_RANGE(M1.dims(0), first, last)
536  for (long i = first; i < last; ++i)
537  // for (std::size_t i = 0; i < M1.dims(0); ++i)
538  for (std::size_t j = 0; j < M2.dims(1); ++j)
539  for (std::size_t k = 0; k < M2.dims(0); ++k) {
540  T R_tmp = M1(i, k);
541  R_tmp *= M2(k, j);
542  R(i, j) += R_tmp;
543  }
544  NTL_EXEC_RANGE_END
545 
546  HELIB_NTIMER_STOP(MatrixMultiplicationConv);
547  return R;
548 }
549 
550 // Matrix special - Different types
551 template <typename T,
552  typename T2,
553  typename std::enable_if_t<
554  !std::is_convertible<T, std::size_t>::value>* = nullptr>
555 inline Tensor<T, 2> operator*(const Tensor<T, 2>& M1, const Tensor<T2, 2>& M2)
556 {
557  HELIB_NTIMER_START(MatrixMultiplicationNotConv);
558  // rows from M1 x cols from M2
559  Tensor<T, 2> R(helib::zeroValue(M1(0, 0)), M1.dims(0), M2.dims(1));
560 
561  // Sanity check: M1 cols should match M2 rows
562  if (M1.dims(1) != M2.dims(0)) {
563  throw helib::LogicError(
564  "The number of columns in left matrix (" + std::to_string(M1.dims(1)) +
565  ") do not match the number of rows of the right matrix (" +
566  std::to_string(M2.dims(0)) + ").");
567  }
568 
569  // TODO swap += for some binary sum.
570  NTL_EXEC_RANGE(M1.dims(0), first, last)
571  for (long i = first; i < last; ++i)
572  // For reference before NTL threads: for (std::size_t i = 0; i < M1.dims(0);
573  // ++i)
574  for (std::size_t j = 0; j < M2.dims(1); ++j)
575  for (std::size_t k = 0; k < M2.dims(0); ++k) {
576  T R_tmp = M1(i, k);
577  R_tmp *= M2(k, j);
578  R(i, j) += R_tmp;
579  }
580  NTL_EXEC_RANGE_END
581 
582  HELIB_NTIMER_STOP(MatrixMultiplicationNotConv);
583  return R;
584 }
585 
586 template <typename T, typename T2>
587 inline Tensor<T, 2> operator-(const Tensor<T, 2>& M1, const Tensor<T2, 2>& M2)
588 {
589  auto M1_copy = M1.deepCopy();
590  M1_copy -= M2;
591  return M1_copy;
592 }
593 
594 template <typename T, typename T2>
595 inline Tensor<T, 2> operator+(const Tensor<T, 2>& M1, const Tensor<T2, 2>& M2)
596 {
597  auto M1_copy = M1.deepCopy();
598  M1_copy += M2;
599  return M1_copy;
600 }
601 
602 // These are the alias we will actually use.
603 template <typename T>
605 
606 template <typename T>
608 
609 // TODO Columns Tuple
610 template <typename T>
612 {
613 
614 private:
615  std::vector<Matrix<T>> columns;
616 
617 public:
618  MatrixView(const std::initializer_list<Matrix<T>> lst) : columns(lst) {}
619 };
620 
621 template <typename T>
622 void printMatrix(const Matrix<T>& M, std::ostream& out = std::cout)
623 {
624  for (std::size_t i = 0; i < M.dims(0); ++i) {
625  for (std::size_t j = 0; j < M.dims(1); ++j)
626  out << M(i, j) << " ";
627  out << "\n";
628  }
629 }
630 
631 } // namespace helib
632 
633 #endif // ifndef HELIB_MATRIX_H
Inherits from Exception and std::logic_error.
Definition: exceptions.h:68
Definition: Matrix.h:612
MatrixView(const std::initializer_list< Matrix< T >> lst)
Definition: Matrix.h:618
Inherits from Exception and std::out_of_range.
Definition: exceptions.h:86
Definition: Matrix.h:149
Tensor< T, N > & entrywiseOperation(const Tensor< T2, N > &rhs, std::function< T &(T &, const T2 &)> operation)
Definition: Matrix.h:343
Tensor< T, 2 > transpose() const
Definition: Matrix.h:434
Tensor< T, N > & operator+=(const Tensor< T2, N > &rhs)
Definition: Matrix.h:384
~Tensor()=default
Tensor< T, 2 > & inPlaceTranspose()
Definition: Matrix.h:442
std::size_t order() const
Definition: Matrix.h:222
Tensor(Tensor &&other)=default
Tensor< T, N > columns(const std::vector< long > &js) const
Definition: Matrix.h:313
bool operator!=(const Tensor &rhs) const
Definition: Matrix.h:261
Tensor(Dims... dims)
Definition: Matrix.h:172
Tensor< T, 2 > getColumn(std::size_t j) const
Definition: Matrix.h:298
bool fullView() const
Definition: Matrix.h:240
std::size_t dims(int i) const
Definition: Matrix.h:238
Tensor< T, 2 > getRow(std::size_t i) const
Definition: Matrix.h:284
const std::vector< T > & data() const
Definition: Matrix.h:514
Tensor(std::initializer_list< std::vector< T >> lst)
Definition: Matrix.h:178
Tensor< T, N > & operator-=(const Tensor< T2, N > &rhs)
Definition: Matrix.h:394
std::size_t size() const
Definition: Matrix.h:236
T & operator()(Args... args)
Definition: Matrix.h:225
Tensor< T, N > deepCopy() const
Definition: Matrix.h:211
Tensor & operator=(Tensor &&rhs)=default
Tensor< T, N > & hadamard(const Tensor< T2, N > &rhs)
Definition: Matrix.h:404
Tensor(const Tensor &other)=default
Tensor< T, N - 1 > column(std::size_t j) const
Definition: Matrix.h:273
const T & operator()(Args... args) const
Definition: Matrix.h:231
Tensor< T, N > & apply(std::function< void(T &x)> fn)
Definition: Matrix.h:413
bool operator==(const Tensor &rhs) const
Definition: Matrix.h:242
Tensor(const TensorSlice< N > &ts, const std::shared_ptr< std::vector< T >> &elems)
Definition: Matrix.h:196
Tensor(const T &obj, Dims... dims)
Definition: Matrix.h:166
Tensor & operator=(const Tensor &rhs)=default
Tensor< T, N - 1 > row(std::size_t i) const
Definition: Matrix.h:263
Definition: apiAttributes.h:21
void printMatrix(const Matrix< T > &M, std::ostream &out=std::cout)
Definition: Matrix.h:622
Tensor< T, 2 > operator*(const Tensor< T, 2 > &M1, const Tensor< T2, 2 > &M2)
Definition: Matrix.h:522
Tensor< T, 2 > operator+(const Tensor< T, 2 > &M1, const Tensor< T2, 2 > &M2)
Definition: Matrix.h:595
T zeroValue(const T &x)
Given an object x return a zero object of the same type.
Definition: zeroValue.h:29
Tensor< T, 2 > operator-(const Tensor< T, 2 > &M1, const Tensor< T2, 2 > &M2)
Definition: Matrix.h:587
Definition: io.h:50
Definition: Matrix.h:43
bool operator!=(const TensorSlice &rhs) const
Definition: Matrix.h:141
std::array< std::size_t, N > strides
Definition: Matrix.h:45
std::array< std::size_t, N > lengths
Definition: Matrix.h:44
bool operator==(const TensorSlice &rhs) const
Definition: Matrix.h:130
std::vector< long > start
Definition: Matrix.h:46
TensorSlice(const Iter1 &firstLength, const Iter1 &lastLength, const Iter2 &firstStride, const Iter2 &lastStride, const std::vector< long > &st)
Definition: Matrix.h:51
std::size_t operator()(Dims... dims) const
Definition: Matrix.h:100
std::size_t order() const
Definition: Matrix.h:128
TensorSlice(const Iter1 &firstLength, const Iter1 &lastLength, const Iter2 &firstStride, const Iter2 &lastStride, unsigned long pos)
Definition: Matrix.h:68
TensorSlice(Dims... dims)
Definition: Matrix.h:85
std::size_t size
Definition: Matrix.h:47