Minsky
tensorOp.cc
Go to the documentation of this file.
1 /*
2  @copyright Russell Standish 2019
3  @author Russell Standish
4  This file is part of Civita.
5 
6  Civita is free software: you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Civita is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with Civita. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "tensorOp.h"
21 #include <exception>
22 #include <set>
23 #include <ecolab_epilogue.h>
24 using namespace std;
25 
26 namespace civita
27 {
28  void BinOp::setArguments(const TensorPtr& a1, const TensorPtr& a2, const Args&)
29  {
30  arg1=a1; arg2=a2;
31  if (arg1 && arg1->rank()!=0)
32  {
33  hypercube(arg1->hypercube());
34  if (arg2 && arg2->rank()!=0 && arg1->hypercube().dims()!=arg2->hypercube().dims())
35  throw std::runtime_error("arguments not conformal");
36 
37  }
38  else if (arg2)
39  hypercube(arg2->hypercube());
40  else
41  hypercube(Hypercube());
42  set<size_t> indices;
43  if (arg1) indices.insert(arg1->index().begin(), arg1->index().end());
44  if (arg2) indices.insert(arg2->index().begin(), arg2->index().end());
45  m_index=indices;
46  }
47 
48 
49  void ReduceArguments::setArguments(const vector<TensorPtr>& a,const Args&)
50  {
51  hypercube({});
52  if (!a.empty())
53  {
54  auto hc=a[0]->hypercube();
55  hypercube(hc);
56  size_t cnt=0;
57  set<size_t> idx;
58  for (const auto& i: a)
59  {
60  if (i->rank()>0 && i->hypercube()!=hc)
61  throw runtime_error("arguments not conformal");
62  idx.insert(i->index().begin(), i->index().end());
63  }
64  m_index=idx;
65  }
66  args=a;
67  }
68 
69  double ReduceArguments::operator[](size_t i) const
70  {
71  if (args.empty()) return init;
72  assert(i<size());
73  double r=init;
74  for (const auto& j: args)
75  {
76  auto x=j->rank()==0? (*j)[0]: (*j)[i];
77  if (!isnan(x)) f(r, x);
78  }
79  return r;
80  }
81 
82  ITensor::Timestamp ReduceArguments::timestamp() const
83  {
84  Timestamp t;
85  for (const auto& i: args)
86  t=max(t, i->timestamp());
87  return t;
88  }
89 
90  double ReduceAllOp::operator[](size_t) const
91  {
92  double r=init;
93  for (size_t i=0; i<arg->size(); ++i)
94  {
95  double x=(*arg)[i];
96  if (!isnan(x)) f(r,x,i);
97  }
98  return r;
99  }
100 
101  void ReductionOp::setArgument(const TensorPtr& a, const Args& args)
102  {
103  arg=a;
104  dimension=std::numeric_limits<size_t>::max();
105  if (arg)
106  {
107  const auto& ahc=arg->hypercube();
108  m_hypercube=ahc;
109  auto& xv=m_hypercube.xvectors;
110  for (auto i=xv.begin(); i!=xv.end(); ++i)
111  if (i->name==args.dimension)
112  dimension=i-xv.begin();
113  if (dimension<arg->rank())
114  {
115  xv.erase(xv.begin()+dimension);
116  // compute index - enter index elements that have any in the argument
117  set<size_t> indices;
118  for (size_t i=0; i<arg->size(); ++i)
119  {
120  auto splitIdx=ahc.splitIndex(arg->index()[i]);
121  SOI soi{i,splitIdx[dimension]};
122  splitIdx.erase(splitIdx.begin()+dimension);
123  auto idx=m_hypercube.linealIndex(splitIdx);
124  sumOverIndices[idx].emplace_back(soi);
125  indices.insert(idx);
126  }
127  m_index=indices;
128  }
129  else
130  m_hypercube.xvectors.clear(); //reduce all, return scalar
131  }
132  else
133  m_hypercube.xvectors.clear();
134  }
135 
136 
137  double ReductionOp::operator[](size_t i) const
138  {
139  assert(i<size());
140  if (!arg) return init;
141  if (dimension>arg->rank())
142  return ReduceAllOp::operator[](i);
143 
144  double r=init;
145  if (index().empty())
146  {
147  auto argDims=arg->shape();
148  size_t stride=1;
149  for (size_t j=0; j<dimension; ++j)
150  stride*=argDims[j];
151  auto quotRem=ldiv(i, stride); // quotient and remainder calc in one hit
152  auto start=quotRem.quot*stride*argDims[dimension] + quotRem.rem;
153  assert(stride*argDims[dimension]>0);
154  for (size_t j=0; j<argDims[dimension]; ++j)
155  {
156  double x=arg->atHCIndex(j*stride+start);
157  if (!isnan(x)) f(r,x,j);
158  }
159  }
160  else
161  {
162  auto soi=sumOverIndices.find(index()[i]);
163  assert(soi!=sumOverIndices.end());
164  if (soi!=sumOverIndices.end())
165  for (auto j: soi->second)
166  {
167  double x=(*arg)[j.index];
168  if (!isnan(x)) f(r,x,j.dimIndex);
169  }
170  }
171  return r;
172  }
173 
174  double CachedTensorOp::operator[](size_t i) const
175  {
176  assert(i<size());
177  if (m_timestamp<timestamp()) {
178  computeTensor();
179  m_timestamp=Timestamp::clock::now();
180  }
181  return cachedResult[i];
182  }
183 
184  void DimensionedArgCachedOp::setArgument(const TensorPtr& a, const Args& args)
185  {
186  arg=a;
187  argVal=args.val;
188  if (!arg) {m_hypercube.xvectors.clear(); return;}
189  dimension=std::numeric_limits<size_t>::max();
190  auto hc=arg->hypercube();
191  auto& xv=hc.xvectors;
192  for (auto i=xv.begin(); i!=xv.end(); ++i)
193  if (i->name==args.dimension)
194  dimension=i-xv.begin();
195  hypercube(move(hc));
196  }
197 
198 
199  void Scan::computeTensor() const
200  {
201  if (dimension<rank())
202  {
203  auto argDims=arg->hypercube().dims();
204  size_t stride=1;
205  for (size_t j=0; j<dimension; ++j)
206  stride*=argDims[j];
207  if (argVal>=1 && argVal<argDims[dimension])
208  // argVal is interpreted as the binning window. -ve argVal ignored
209  for (size_t i=0; i<hypercube().numElements(); i+=stride*argDims[dimension])
210  for (size_t j=0; j<stride; ++j)
211  for (size_t j1=0; j1<argDims[dimension]*stride; j1+=stride)
212  {
213  size_t k=i+j+max(ssize_t(0), ssize_t(j1-ssize_t(argVal-1)*stride));
214  cachedResult[i+j+j1]=arg->atHCIndex(i+j+j1);
215  for (; k<i+j+j1; k+=stride)
216  {
217  f(cachedResult[i+j+j1], arg->atHCIndex(k), k);
218  }
219  }
220  else // scan over whole dimension
221  for (size_t i=0; i<hypercube().numElements(); i+=stride*argDims[dimension])
222  for (size_t j=0; j<stride; ++j)
223  {
224  cachedResult[i+j]=arg->atHCIndex(i+j);
225  for (size_t k=i+j+stride; k<i+j+stride*argDims[dimension]; k+=stride)
226  {
227  cachedResult[k] = cachedResult[k-stride];
228  f(cachedResult[k], arg->atHCIndex(k), k);
229  }
230  }
231  }
232  else
233  {
234  cachedResult[0]=arg->atHCIndex(0);
235  for (size_t i=1; i<hypercube().numElements(); ++i)
236  {
237  cachedResult[i]=cachedResult[i-1];
238  f(cachedResult[i], arg->atHCIndex(i), i);
239  }
240  }
241  }
242 
243  // void Slice::setArgument(const TensorPtr& a,const string& axis, double index)
244  void Slice::setArgument(const TensorPtr& a,const Args& args)
245  {
246  arg=a;
247  sliceIndex=args.val;
248  if (arg)
249  {
250  auto& xv=arg->hypercube().xvectors;
251  Hypercube hc;
252  // find axis where slicing along
253  split=1;
254  size_t splitAxis=0;
255  auto i=xv.begin();
256  for (; i!=xv.end(); ++i)
257  if (i->name==args.dimension)
258  {
259  stride=split*i->size();
260  break;
261  }
262  else
263  {
264  hc.xvectors.push_back(*i);
265  split*=i->size();
266  splitAxis++;
267  }
268 
269  if (i==xv.end())
270  split=stride=1;
271  else
272  for (i++; i!=xv.end(); ++i)
273  // finish building hypercube
274  hc.xvectors.push_back(*i);
275  hypercube(hc);
276 
277  // set up index vector
278  auto& ahc=arg->hypercube();
279  map<size_t, size_t> ai;
280  for (size_t i=0; i<arg->index().size(); ++i)
281  {
282  auto splitIdx=ahc.splitIndex(arg->index()[i]);
283  if (splitIdx[splitAxis]==sliceIndex)
284  {
285  splitIdx.erase(splitIdx.begin()+splitAxis);
286  auto l=hc.linealIndex(splitIdx);
287  ai[l]=i;
288  }
289  }
290  m_index=ai;
291  arg_index.resize(ai.size());
292  // convert into lineal addressing
293  size_t j=0;
294  for (auto& i: ai) arg_index[j++]=i.second;
295  }
296  }
297 
298  double Slice::operator[](size_t i) const
299  {
300  assert(i<size());
301  if (m_index.empty())
302  {
303  auto res=div(ssize_t(i), ssize_t(split));
304  return arg->atHCIndex(res.quot*stride + sliceIndex*split + res.rem);
305  }
306  return (*arg)[arg_index[i]];
307  }
308 
309  void Pivot::setArgument(const TensorPtr& a,const Args&)
310  {
311  arg=a;
312  vector<string> axes;
313  for (auto& i: arg->hypercube().xvectors)
314  axes.push_back(i.name);
315  setOrientation(axes);
316  }
317 
318 
319  void Pivot::setOrientation(const vector<string>& axes)
320  {
321  map<string,size_t> pMap;
322  map<string,XVector> xVectorMap;
323  auto& ahc=arg->hypercube();
324  for (size_t i=0; i<ahc.xvectors.size(); ++i)
325  {
326  pMap[ahc.xvectors[i].name]=i;
327  xVectorMap[ahc.xvectors[i].name]=ahc.xvectors[i];
328  }
329  Hypercube hc;
330  permutation.clear();
331  set<string> axisSet;
332  map<size_t, size_t> invPermutation;
333  for (auto& i: axes)
334  {
335  axisSet.insert(i);
336  auto v=pMap.find(i);
337  if (v==pMap.end())
338  throw runtime_error("axis "+i+" not found in argument");
339  invPermutation[v->second]=permutation.size();
340  permutation.push_back(v->second);
341  hc.xvectors.push_back(xVectorMap[i]);
342  }
343  // add remaining axes to permutation in found order
344  for (size_t i=0; i<ahc.xvectors.size(); ++i)
345  if (!axisSet.count(ahc.xvectors[i].name))
346  {
347  invPermutation[i]=permutation.size();
348  permutation.push_back(i);
349  hc.xvectors.push_back(ahc.xvectors[i]);
350  }
351 
352  assert(hc.rank()==arg->rank());
353  hypercube(move(hc));
354  // permute the index vector
355  map<size_t, size_t> pi;
356  for (size_t i=0; i<arg->index().size(); ++i)
357  {
358  auto idx=arg->hypercube().splitIndex(arg->index()[i]);
359  auto pidx=idx;
360  for (size_t j=0; j<idx.size(); ++j)
361  {
362  assert(invPermutation.count(j));
363  assert(invPermutation[j]<pidx.size());
364  pidx[invPermutation[j]]=idx[j];
365  }
366  auto l=hypercube().linealIndex(pidx);
367  assert(pi.count(l)==0);
368  pi[l]=i;
369  }
370  m_index=pi;
371  // convert to lineal indexing
372  permutedIndex.clear();
373  for (auto& i: pi) permutedIndex.push_back(i.second);
374  if (!permutedIndex.empty()) permutation.clear(); // not used in sparse case
375  }
376 
377  size_t Pivot::pivotIndex(size_t i) const
378  {
379  auto idx=hypercube().splitIndex(i);
380  auto pidx=idx;
381  for (size_t i=0; i<idx.size(); ++i)
382  pidx[permutation[i]]=idx[i];
383  return arg->hypercube().linealIndex(pidx);
384  }
385 
386  double Pivot::operator[](size_t i) const
387  {
388  assert(i<size());
389  if (index().empty())
390  return arg->atHCIndex(pivotIndex(i));
391  return (*arg)[permutedIndex[i]];
392  }
393 
394 
395  namespace
396  {
399  {
400  switch (op)
401  {
402  case ravel::Op::sum: return make_shared<Sum>();
403  case ravel::Op::prod: return make_shared<Product>();
404  case ravel::Op::av: return make_shared<civita::Average>();
405  case ravel::Op::stddev: return make_shared<civita::StdDeviation>();
406  case ravel::Op::min: return make_shared<Min>();
407  case ravel::Op::max: return make_shared<Max>();
408  default: throw runtime_error("Reduction "+to_string(op)+" not understood");
409  }
410  }
411  }
412 
413  void PermuteAxis::setArgument(const TensorPtr& a,const Args& args)
414  {
415  arg=a;
416  hypercube(arg->hypercube());
417  m_index=arg->index();
418  for (m_axis=0; m_axis<m_hypercube.xvectors.size(); ++m_axis)
419  if (m_hypercube.xvectors[m_axis].name==args.dimension)
420  break;
421  if (m_axis==m_hypercube.xvectors.size())
422  throw runtime_error("axis "+args.dimension+" not found");
423  for (size_t i=0; i<m_hypercube.xvectors[m_axis].size(); ++i)
424  m_permutation.push_back(i);
425  }
426 
427  void PermuteAxis::setPermutation(vector<size_t>&& p)
428  {
429  m_permutation=move(p);
430  auto& xv=m_hypercube.xvectors[m_axis];
431  xv.clear();
432  auto& axv=arg->hypercube().xvectors[m_axis];
433  for (auto i: m_permutation)
434  xv.push_back(axv[i]);
435  map<unsigned,unsigned> reverseIndex;
436  for (size_t i=0; i<m_permutation.size(); ++i)
437  reverseIndex[m_permutation[i]]=i;
438  map<size_t,size_t> indices;
439  for (size_t i=0; i<arg->index().size(); ++i)
440  {
441  auto splitted=arg->hypercube().splitIndex(arg->index()[i]);
442  auto ri=reverseIndex.find(splitted[m_axis]);
443  if (ri!=reverseIndex.end() && (splitted[m_axis]=ri->second)<axv.size())
444  indices[hypercube().linealIndex(splitted)]=i;
445  }
446  m_index=indices;
447  permutedIndex.clear();
448  for (auto& i: indices) permutedIndex.push_back(i.second);
449  }
450 
451  double PermuteAxis::operator[](size_t i) const
452  {
453  assert(i<size());
454  if (index().empty())
455  {
456  auto splitted=hypercube().splitIndex(i);
457  splitted[m_axis]=m_permutation[splitted[m_axis]];
458  return arg->atHCIndex(arg->hypercube().linealIndex(splitted));
459  }
460  return (*arg)[permutedIndex[i]];
461  }
462 
463 
464  void SortByValue::computeTensor() const
465  {
466  assert(arg->rank()==1);
467  vector<size_t> idx; idx.reserve(arg->size());
468  vector<double> tmp;
469  for (size_t i=0; i<arg->size(); ++i)
470  {
471  idx.push_back(i);
472  tmp.push_back((*arg)[i]);
473  }
474  switch (order)
475  {
476  case ravel::HandleSort::forward:
477  sort(idx.begin(), idx.end(), [&](size_t i, size_t j){return tmp[i]<tmp[j];});
478  break;
479  case ravel::HandleSort::reverse:
480  sort(idx.begin(), idx.end(), [&](size_t i, size_t j){return tmp[i]>tmp[j];});
481  break;
482  default:
483  break;
484  }
485  // reorder labels
486  auto hc=arg->hypercube();
487  XVector xv(hc.xvectors[0]);
488  for (size_t i=0; i<idx.size(); ++i)
489  xv[i]=hc.xvectors[0][idx[i]];
490  hc.xvectors[0].swap(xv);
491  cachedResult.hypercube(move(hc));
492  for (size_t i=0; i<idx.size(); ++i)
493  cachedResult[i]=tmp[idx[i]];
494  }
495 
496 
497  vector<TensorPtr> createRavelChain(const ravel::RavelState& state, const TensorPtr& arg)
498  {
499  set<string> outputHandles(state.outputHandles.begin(), state.outputHandles.end());
500  vector<TensorPtr> chain{arg};
501  // TODO sorts and calipers
502  for (auto& i: state.handleStates)
503  {
504  if (i.order!=ravel::HandleSort::none || i.displayFilterCaliper)
505  {
506  //apply sorting/calipers
507  auto permuteAxis=make_shared<PermuteAxis>();
508  permuteAxis->setArgument(chain.back(), {i.description,0});
509  auto& xv=chain.back()->hypercube().xvectors[permuteAxis->axis()];
510  vector<size_t> perm;
511  for (size_t i=0; i<xv.size(); ++i)
512  perm.push_back(i);
513  switch (i.order)
514  {
515  case ravel::HandleSort::none: break;
516  case ravel::HandleSort::forward:
517  case ravel::HandleSort::numForward:
518  case ravel::HandleSort::timeForward:
519  sort(perm.begin(), perm.end(),
520  [&](size_t i, size_t j) {return diff(xv[i],xv[j])<0;});
521  break;
522  case ravel::HandleSort::reverse:
523  case ravel::HandleSort::numReverse:
524  case ravel::HandleSort::timeReverse:
525  sort(perm.begin(), perm.end(),
526  [&](size_t i, size_t j) {return diff(xv[i],xv[j])>0;});
527  break;
528  case ravel::HandleSort::custom:
529  {
530  map<string, size_t> offsets;
531  for (size_t i=0; i<xv.size(); ++i)
532  offsets[str(xv[i], xv.dimension.units)]=i;
533  perm.clear();
534  for (auto& j: i.customOrder)
535  if (offsets.count(j))
536  perm.push_back(offsets[j]);
537  break;
538  }
539 // case ravel::HandleSort::numForward: case ravel::HandleSort::numReverse:
540 // case ravel::HandleSort::timeForward: case ravel::HandleSort::timeReverse:
541 // throw runtime_error("deprecated sort order used");
542  }
543  if (i.displayFilterCaliper)
544  {
545  // remove any permutation items outside calipers
546  if (!i.minLabel.empty())
547  for (auto j=perm.begin(); j!=perm.end(); ++j)
548  if (str(xv[*j],xv.dimension.units) == i.minLabel)
549  {
550  perm.erase(perm.begin(), j);
551  break;
552  }
553  if (!i.maxLabel.empty())
554  for (auto j=perm.begin(); j!=perm.end(); ++j)
555  if (str(xv[*j],xv.dimension.units) == i.maxLabel)
556  {
557  perm.erase(j+1, perm.end());
558  break;
559  }
560  }
561  permuteAxis->setPermutation(move(perm));
562  chain.push_back(permuteAxis);
563  }
564  if (!outputHandles.count(i.description))
565  {
566  auto arg=chain.back();
567  if (i.collapsed)
568  {
569  chain.emplace_back(createReductionOp(i.reductionOp));
570  chain.back()->setArgument(arg, {i.description,0});
571  }
572  else
573  {
574  chain.emplace_back(new Slice);
575  auto& xv=arg->hypercube().xvectors;
576  auto axisIt=find_if(xv.begin(), xv.end(),
577  [&](const XVector& j){return j.name==i.description;});
578  if (axisIt==xv.end()) throw runtime_error("axis "+i.description+" not found");
579  auto sliceIt=find_if(axisIt->begin(), axisIt->end(),
580  [&](const boost::any& j){return str(j,axisIt->dimension.units)==i.sliceLabel;});
581  // determine slice index
582  size_t sliceIdx=0;
583  if (sliceIt!=axisIt->end())
584  sliceIdx=sliceIt-axisIt->begin();
585  chain.back()->setArgument(arg, {i.description, double(sliceIdx)});
586  }
587  }
588  }
589 
590  if (chain.back()->rank()>1)
591  {
592  auto finalPivot=make_shared<Pivot>();
593  finalPivot->setArgument(chain.back());
594  finalPivot->setOrientation(state.outputHandles);
595  chain.push_back(finalPivot);
596  }
597  return chain;
598  }
599 
600 }
function f
Definition: canvas.m:1
vector< TensorPtr > createRavelChain(const ravel::RavelState &state, const TensorPtr &arg)
creates a chain of tensor operations that represents a Ravel in state state, operating on arg ...
Definition: tensorOp.cc:497
STL namespace.
struct TCLcmd::trap::init_t init
std::chrono::time_point< std::chrono::high_resolution_clock > Timestamp
arguments relevant for tensor expressions, not always meaningful. Exception thrown if not...
std::string str(T x)
utility function to create a string representation of a numeric type
Definition: str.h:33
std::shared_ptr< ITensor > TensorPtr
corresponds to OLAP slice operation
Definition: tensorOp.h:225
string to_string(CONST84 char *x)
Definition: minskyTCLObj.h:33
TensorPtr createReductionOp(ravel::Op::ReductionOp op)
factory method for creating reduction operations
Definition: tensorOp.cc:398
ravel::Op::ReductionOp ReductionOp
Definition: ravelWrap.cc:59