planc
Parallel Lowrank Approximation with Non-negativity Constraints
tensor.hpp
Go to the documentation of this file.
1 /* Copyright 2017 Ramakrishnan Kannan */
2 
3 #ifndef COMMON_TENSOR_HPP_
4 #define COMMON_TENSOR_HPP_
5 
6 #include <cblas.h>
7 #include <armadillo>
8 #include <fstream>
9 #include <ios>
10 #include <random>
11 #include <string>
12 #include <type_traits>
13 #include <utility>
14 #include <vector>
15 #include "common/utils.h"
16 
17 namespace planc {
26 // sgemm (TRANSA, TRANSB, M, N, K, ALPHA, A, LDA, B, LDB, BETA, C, LDC)
27 // extern "C" void dgemm_(const char*, const char*, const int*,
28 // const int*, const int*, const double*, const double*,
29 // const int*, const double*, const int*, const double*,
30 // double*, const int*);
31 
32 class Tensor {
33  private:
34  int m_modes;
35  UVEC m_dimensions;
36  UWORD m_numel;
37  UVEC m_global_idx;
38  unsigned int rand_seed;
39 
40  // for the time being it is used for debugging purposes
41  size_t sub2ind(UVEC sub, UVEC dimensions) {
42  assert(sub.n_rows == dimensions.n_rows);
43  UVEC cumprod_dims = arma::cumprod(dimensions);
44  UVEC cumprod_dims_shifted = arma::shift(cumprod_dims, 1);
45  cumprod_dims_shifted(0) = 1;
46  size_t idx = arma::dot(cumprod_dims_shifted, sub);
47  return idx;
48  }
49  UVEC ind2sub(UVEC dimensions, size_t idx) {
50  // k = [1 cumprod(siz(1 : end - 1))];
51  // n = length(siz);
52  // idx = idx - 1;
53  // for i = n : -1 : 1
54  // div = floor(idx / k(i));
55  // subs( :, i) = div + 1;
56  // idx = idx - k(i) * div;
57  // end
58  UVEC cumprod_dims = arma::cumprod(dimensions);
59  UVEC cumprod_dims_shifted = arma::shift(cumprod_dims, 1);
60  cumprod_dims_shifted(0) = 1;
61  int modes = dimensions.n_elem;
62  UVEC sub = arma::zeros<UVEC>(modes);
63  float temp;
64  for (int i = modes - 1; i >= 0; i--) {
65  temp = std::floor((idx * 1.0) / (cumprod_dims_shifted(i) * 1.0));
66  sub(i) = temp;
67  idx = idx - cumprod_dims_shifted(i) * temp;
68  }
69  return sub;
70  }
71 
72  public:
73  std::vector<double> m_data;
74 
75  Tensor() {
76  this->m_modes = 0;
77  this->m_numel = 0;
78  }
82  explicit Tensor(const UVEC &i_dimensions)
83  : m_modes(i_dimensions.n_rows),
84  m_dimensions(i_dimensions),
85  m_numel(arma::prod(i_dimensions)),
86  rand_seed(103) {
87  m_data.resize(m_numel);
88  randu();
89  }
90  Tensor(const UVEC &i_dimensions, const UVEC &i_start_idx)
91  : m_modes(i_dimensions.n_rows),
92  m_dimensions(i_dimensions),
93  m_numel(arma::prod(i_dimensions)),
94  m_global_idx(i_start_idx),
95  rand_seed(103) {
96  m_data.resize(m_numel);
97  randu();
98  }
104  Tensor(const UVEC &i_dimensions, double *i_data)
105  : m_modes(i_dimensions.n_rows),
106  m_dimensions(i_dimensions),
107  m_numel(arma::prod(i_dimensions)),
108  rand_seed(103) {
109  m_data.resize(m_numel);
110  memcpy(&this->m_data[0], i_data, sizeof(double) * this->m_numel);
111  }
112  ~Tensor() {}
113 
117  Tensor(const Tensor &src) {
118  clear();
119  this->m_numel = src.numel();
120  this->m_modes = src.modes();
121  this->m_dimensions = src.dimensions();
122  this->m_global_idx = src.global_idx();
123  this->rand_seed = 103;
124  this->m_data = src.m_data;
125  }
126 
127  Tensor &operator=(const Tensor &other) { // copy assignment
128  if (this != &other) { // self-assignment check expected
129  clear();
130  // this->m_data = new double[other.numel()]; // create storage in this
131  this->m_numel = other.numel();
132  this->m_modes = other.modes();
133  this->m_dimensions = other.dimensions();
134  this->m_global_idx = other.global_idx();
135  this->m_data = other.m_data;
136  }
137  return *this;
138  }
139 
140  void swap(Tensor &in) {
141  using std::swap;
142  swap(m_numel, in.m_numel);
143  swap(m_modes, in.m_modes);
144  swap(m_dimensions, in.m_dimensions);
145  swap(m_global_idx, in.m_global_idx);
146  swap(rand_seed, in.rand_seed);
147  swap(m_data, in.m_data);
148  }
153  void clear() {
154  this->m_numel = 0;
155  this->m_data.clear();
156  }
157 
159  int modes() const { return m_modes; }
161  UVEC dimensions() const { return m_dimensions; }
162  UVEC global_idx() const { return m_global_idx; }
163 
170  int dimension(int i) const { return m_dimensions[i]; }
172  UWORD numel() const { return m_numel; }
173 
174  void set_idx(const UVEC &i_start_idx) { m_global_idx = i_start_idx; }
181  UWORD rc = arma::prod(this->m_dimensions);
182  return rc - this->m_dimensions(i);
183  }
185  void zeros() {
186  for (UWORD i = 0; i < this->m_numel; i++) {
187  this->m_data[i] = 0;
188  }
189  }
191  void rand() {
192 #pragma omp parallel for
193  for (UWORD i = 0; i < this->m_numel; i++) {
194  unsigned int *temp = const_cast<unsigned int *>(&rand_seed);
195  this->m_data[i] =
196  static_cast<double>(rand_r(temp)) / static_cast<double>(RAND_MAX);
197  }
198  }
200  void randi() {
201  std::random_device rd;
202  std::mt19937 gen(rd());
203  int max_randi = this->m_numel;
204  std::uniform_int_distribution<> dis(0, max_randi);
205 #pragma omp parallel for
206  for (UWORD i = 0; i < this->m_numel; i++) {
207  this->m_data[i] = dis(gen);
208  }
209  }
215  void randu(const int i_seed = -1) {
216  std::random_device rd;
217  std::uniform_real_distribution<> dis(0, 1);
218  if (i_seed == -1) {
219  std::mt19937 gen(rand_seed);
220 #pragma omp parallel for
221  for (unsigned int i = 0; i < this->m_numel; i++) {
222  m_data[i] = dis(gen);
223  }
224  } else {
225  std::mt19937 gen(i_seed);
226 #pragma omp parallel for
227  for (unsigned int i = 0; i < this->m_numel; i++) {
228  m_data[i] = dis(gen);
229  }
230  }
231  }
232 
242  void mttkrp(const int i_n, const MAT &i_krp, MAT *o_mttkrp) const {
243  (*o_mttkrp).zeros();
244  if (i_n == 0) {
245  // if n == 1
246  // Ur = khatrirao(U{2: N}, 'r');
247  // Y = reshape(X.data, szn, szr);
248  // V = Y * Ur;
249  // Compute number of columns of Y_n
250  // Technically, we could divide the total number of entries by n,
251  // but that seems like a bad decision
252  // size_t ncols = arma::prod(this->m_dimensions);
253  // ncols /= this->m_dimensions[0];
254  // Call matrix matrix multiply
255  // call dgemm (TRANSA, TRANSB, M, N, K, ALPHA, A, LDA, B, LDB, BETA, C,
256  // LDC) C := alpha*op( A )*op( B ) + beta*C A, B and C are matrices, with
257  // op( A ) an m by k matrix, op( B ) a k by n matrix and C an m by n
258  // matrix. matricized tensor is m x k is in column major format krp is k x
259  // n is in column major format output is m x n in column major format
260  // char transa = 'N';
261  // char transb = 'N';
262  int m = this->m_dimensions[0];
263  int n = i_krp.n_cols;
264  int k = i_krp.n_rows;
265  // int lda = m;
266  // int ldb = k;
267  // int ldc = m;
268  double alpha = 1;
269  double beta = 0;
270  // sgemm (TRANSA, TRANSB, M, N, K, ALPHA, A, LDA, B, LDB, BETA, C, LDC)
271  // dgemm_(&transa, &transb, &m, &n, &k, &alpha, this->m_data,
272  // &lda, i_krp.memptr(), &ldb, &beta, o_mttkrp->memptr() , &ldc);
273  // printf("mode=%d,i=%d,m=%d,n=%d,k=%d,alpha=%lf,T_stride=%d,lda=%d,krp_stride=%d,ldb=%d,beat=%lf,mttkrp=!!!,ldc=%d\n",0,
274  // 0, m, n, k,alpha,0*k*m,m,0*n*k,i_krp.n_rows,beta,n);
275  cblas_dgemm(CblasRowMajor, CblasTrans, CblasTrans, m, n, k, alpha,
276  &this->m_data[0], m, i_krp.memptr(), k, beta,
277  o_mttkrp->memptr(), n);
278 
279  } else {
280  int ncols = 1;
281  int nmats = 1;
282  int lowrankk = i_krp.n_cols;
283 
284  // Count the number of columns
285  for (int i = 0; i < i_n; i++) {
286  ncols *= this->m_dimensions[i];
287  }
288 
289  // Count the number of matrices
290  for (int i = i_n + 1; i < this->m_modes; i++) {
291  nmats *= this->m_dimensions[i];
292  }
293  // For each matrix...
294  for (int i = 0; i < nmats; i++) {
295  // char transa = 'T';
296  // char transb = 'N';
297  int m = this->m_dimensions[i_n];
298  int n = lowrankk;
299  int k = ncols;
300  // int lda = k; // not sure. could be m. higher confidence on k.
301  // int ldb = i_krp.n_rows;
302  // int ldc = m;
303  double alpha = 1;
304  double beta = (i == 0) ? 0 : 1;
305  // double *A = this->m_data + i * k * m;
306  // double *B = const_cast<double *>(i_krp.memptr()) + i * k;
307 
308  // for KRP move ncols*lowrankk
309  // for tensor X move as n_cols*blas_n
310  // For output matrix don't move anything as beta=1;
311  // for reference from gram while moving input tensor like
312  // Y->data()+i*nrows*ncols sgemm (TRANSA, TRANSB, M, N, K, ALPHA, A,
313  // LDA, B, LDB, BETA, C, LDC)
314  // dgemm_(&transa, &transb, &m, &n, &k, &alpha, A,
315  // &lda, B , &ldb, &beta, (*o_mttkrp).memptr() , &ldc);
316  // printf("mode=%d,i=%d,m=%d,n=%d,k=%d,alpha=%lf,T_stride=%d,lda=%d,krp_stride=%d,ldb=%d,beat=%lf,mttkrp=!!!,ldc=%d\n",i_n,
317  // i, m, n, k,alpha,i*k*m,k,i*n*k,nmats*ncols,beta,n);
318  cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasTrans, m, n, k, alpha,
319  &this->m_data[0] + i * k * m, ncols, i_krp.memptr() + i * k,
320  ncols * nmats, beta, o_mttkrp->memptr(), n);
321  }
322  }
323  }
325  void print() const {
326  INFO << "Dimensions: " << this->m_dimensions;
327  for (unsigned int i = 0; i < this->m_numel; i++) {
328  std::cout << i << " : " << this->m_data[i] << std::endl;
329  }
330  }
331 
332  void print(const UVEC &global_dims, const UVEC &global_start_sub) {
333  UVEC local_sub = arma::zeros<UVEC>(global_dims.n_elem);
334  // UVEC global_start_sub = ind2sub(global_dims, global_start_idx);
335  UVEC global_sub = arma::zeros<UVEC>(global_dims.n_elem);
336  int global_idx;
337  for (unsigned int i = 0; i < this->m_numel; i++) {
338  local_sub = ind2sub(this->m_dimensions, i);
339  global_sub = global_start_sub + local_sub;
340  global_idx = sub2ind(global_sub, global_dims);
341  std::cout << i << " : " << global_idx << " : " << this->m_data[i]
342  << std::endl;
343  }
344  }
346  double norm() const {
347  double norm_fro = 0;
348  for (unsigned int i = 0; i < this->m_numel; i++) {
349  norm_fro += (this->m_data[i] * this->m_data[i]);
350  }
351  return norm_fro;
352  }
357  double err(const Tensor &b) const {
358  double norm_fro = 0;
359  double err_diff;
360  for (unsigned int i = 0; i < this->m_numel; i++) {
361  err_diff = this->m_data[i] - b.m_data[i];
362  norm_fro += err_diff * err_diff;
363  }
364  return norm_fro;
365  }
371  template <typename NumericType>
372  void scale(NumericType scale) {
373  // static_assert(std::is_arithmetic<NumericType>::value,
374  // "NumericType for scale operation must be numeric");
375  for (unsigned int i = 0; i < this->m_numel; i++) {
376  this->m_data[i] = this->m_data[i] * scale;
377  }
378  }
384  template <typename NumericType>
385  void shift(NumericType i_shift) {
386  // static_assert(std::is_arithmetic<NumericType>::value,
387  // "NumericType for shift operation must be numeric");
388  for (unsigned int i = 0; i < this->m_numel; i++) {
389  this->m_data[i] = this->m_data[i] + i_shift;
390  }
391  }
398  template <typename NumericType>
399  void bound(NumericType min, NumericType max) {
400  // static_assert(std::is_arithmetic<NumericType>::value,
401  // "NumericType for bound operation must be numeric");
402  for (unsigned int i = 0; i < this->m_numel; i++) {
403  if (this->m_data[i] < min) this->m_data[i] = min;
404  if (this->m_data[i] > max) this->m_data[i] = max;
405  }
406  }
412  template <typename NumericType>
413  void lower_bound(NumericType min) {
414  // static_assert(std::is_arithmetic<NumericType>::value,
415  // "NumericType for bound operation must be numeric");
416  for (unsigned int i = 0; i < this->m_numel; i++) {
417  if (this->m_data[i] < min) this->m_data[i] = min;
418  }
419  }
420 
426  void write(std::string filename,
427  std::ios_base::openmode mode = std::ios_base::out) {
428  std::string filename_no_extension =
429  filename.substr(0, filename.find_last_of("."));
430  // info file always in text mode
431  filename_no_extension.append(".info");
432  std::ofstream ofs;
433  ofs.open(filename_no_extension, std::ios_base::out);
434  // write modes
435  ofs << this->m_modes << std::endl;
436  // dimension of modes
437  for (int i = 0; i < this->m_modes; i++) {
438  ofs << this->m_dimensions[i] << std::endl;
439  }
440  ofs << std::endl;
441  ofs.close();
442  FILE *fp = fopen(filename.c_str(), "wb");
443  INFO << "size of the outputfile in GB "
444  << (this->m_numel * 8.0) / (1024 * 1024 * 1024) << std::endl;
445  size_t nwrite =
446  fwrite(&this->m_data[0], sizeof(std::vector<double>::value_type),
447  this->numel(), fp);
448  if (nwrite != this->numel()) {
449  WARN << "something wrong ::write::" << nwrite
450  << "::numel::" << this->numel() << std::endl;
451  }
452  fclose(fp);
453  }
460  void read(std::string filename,
461  std::ios_base::openmode mode = std::ios_base::in) {
462  // clear existing tensor
463  if (this->m_numel > 0) {
464  this->m_data.clear(); // destroy storage in this
465  this->m_numel = 0;
466  }
467  std::string filename_no_extension =
468  filename.substr(0, filename.find_last_of("."));
469  filename_no_extension.append(".info");
470 
471  std::ifstream ifs;
472  // info file always in text mode
473  ifs.open(filename_no_extension, std::ios_base::in);
474  // write modes
475  ifs >> this->m_modes;
476  // dimension of modes
477  this->m_dimensions = arma::zeros<UVEC>(this->m_modes);
478  for (int i = 0; i < this->m_modes; i++) {
479  ifs >> this->m_dimensions[i];
480  }
481  ifs.close();
482  // ifs.open(filename, mode);
483  FILE *fp = fopen(filename.c_str(), "rb");
484  this->m_numel = arma::prod(this->m_dimensions);
485  this->m_data.resize(this->m_numel);
486  // for (int i = 0; i < this->m_numel; i++) {
487  // ifs >> this->m_data[i];
488  // }
489  // ifs.read(reinterpret_cast<char *>(this->m_data), sizeof(this->m_data));
490  size_t nread =
491  fread(&this->m_data[0], sizeof(std::vector<double>::value_type),
492  this->numel(), fp);
493  if (nread != this->numel()) {
494  WARN << "something wrong ::write::" << nread
495  << "::numel::" << this->numel() << std::endl;
496  }
497  // Close the file
498  fclose(fp);
499  }
500 
509  assert(sub.n_cols == this->m_dimensions.n_cols);
510  UVEC cumprod_dims = arma::cumprod(this->m_dimensions);
511  UVEC cumprod_dims_shifted = arma::shift(cumprod_dims, 1);
512  cumprod_dims_shifted(0) = 1;
513  size_t idx = arma::dot(cumprod_dims_shifted, sub);
514  return idx;
515  }
516  double at(UVEC sub) { return m_data[sub2ind(sub)]; }
517 }; // class Tensor
518 } // namespace planc
519 
520 void swap(planc::Tensor &x, planc::Tensor &y) { x.swap(y); }
521 
522 #endif // COMMON_TENSOR_HPP_
Tensor(const UVEC &i_dimensions)
Constructor that takes only dimensions of every mode as a vector.
Definition: tensor.hpp:82
Tensor(const UVEC &i_dimensions, double *i_data)
Need when copying from matrix to Tensor.
Definition: tensor.hpp:104
Data is stored such that the unfolding is column major.
Definition: tensor.hpp:32
Tensor(const Tensor &src)
copy constructor
Definition: tensor.hpp:117
void print(const UVEC &global_dims, const UVEC &global_start_sub)
Definition: tensor.hpp:332
void zeros()
Zeros out the entire tensor.
Definition: tensor.hpp:185
double err(const Tensor &b) const
Computes the squared error with the input tensor.
Definition: tensor.hpp:357
std::vector< double > m_data
Definition: tensor.hpp:73
void mttkrp(const int i_n, const MAT &i_krp, MAT *o_mttkrp) const
size of krp must be product of all dimensions leaving out nxk.
Definition: tensor.hpp:242
#define UVEC
Definition: utils.h:58
void lower_bound(NumericType min)
Sets only the lower bound.
Definition: tensor.hpp:413
void print() const
prints the value of the tensor.
Definition: tensor.hpp:325
void shift(NumericType i_shift)
Shifts (add or subtract) the tensor with the constant value.
Definition: tensor.hpp:385
void read(std::string filename, std::ios_base::openmode mode=std::ios_base::in)
Reads a tensor from the file.
Definition: tensor.hpp:460
void swap(planc::Tensor &x, planc::Tensor &y)
Definition: tensor.hpp:520
void randu(const int i_seed=-1)
set the tensor with uniform random values starting with a seed.
Definition: tensor.hpp:215
Tensor(const UVEC &i_dimensions, const UVEC &i_start_idx)
Definition: tensor.hpp:90
UWORD sub2ind(UVEC sub)
Given a vector of subscripts, it return the linear index in the tensor.
Definition: tensor.hpp:508
void randi()
set the tensor with uniform int values
Definition: tensor.hpp:200
void rand()
set the tensor with uniform random.
Definition: tensor.hpp:191
#define INFO
Definition: utils.h:36
UVEC global_idx() const
Definition: tensor.hpp:162
int modes() const
Return the number of modes. It is a scalar value.
Definition: tensor.hpp:159
#define UWORD
Definition: utils.h:60
double at(UVEC sub)
Definition: tensor.hpp:516
void bound(NumericType min, NumericType max)
Truncate all the value between min and max.
Definition: tensor.hpp:399
void swap(Tensor &in)
Definition: tensor.hpp:140
void set_idx(const UVEC &i_start_idx)
Definition: tensor.hpp:174
#define MAT
Definition: utils.h:52
void write(std::string filename, std::ios_base::openmode mode=std::ios_base::out)
Write the tensor to the given filename.
Definition: tensor.hpp:426
UVEC dimensions() const
Returns a vector of dimensions on every mode.
Definition: tensor.hpp:161
void scale(NumericType scale)
Scales the tensor with the constant value.
Definition: tensor.hpp:372
UWORD numel() const
Returns total number of elements.
Definition: tensor.hpp:172
int dimension(int i) const
Returns the dimension of the input mode.
Definition: tensor.hpp:170
double norm() const
returns the frobenius norm of the tensor
Definition: tensor.hpp:346
Tensor & operator=(const Tensor &other)
Definition: tensor.hpp:127
ncp_factors contains the factors of the ncp every ith factor is of size n_i * k number of factors is ...
Definition: ncpfactors.hpp:20
void clear()
Clears the data and also destroys the storage.
Definition: tensor.hpp:153
UWORD dimensions_leave_out_one(int i) const
Return the product of dimensions except mode i.
Definition: tensor.hpp:180
#define WARN
Definition: utils.h:32