xerus
a general purpose tensor library
uqAdf.cpp
Go to the documentation of this file.
1 // Xerus - A General Purpose Tensor Library
2 // Copyright (C) 2014-2017 Benjamin Huber and Sebastian Wolf.
3 //
4 // Xerus is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as published
6 // by the Free Software Foundation, either version 3 of the License,
7 // or (at your option) any later version.
8 //
9 // Xerus is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
13 //
14 // You should have received a copy of the GNU Affero General Public License
15 // along with Xerus. If not, see <http://www.gnu.org/licenses/>.
16 //
17 // For further information on Xerus visit https://libXerus.org
18 // or contact us at contact@libXerus.org.
19 
25 #include <xerus/algorithms/uqAdf.h>
26 
29 #include <xerus/misc/internal.h>
30 
31 #include <boost/math/special_functions/hermite.hpp>
32 #include <boost/math/special_functions/legendre.hpp>
33 
34 #ifdef _OPENMP
35  #include <omp.h>
36 #endif
37 
38 namespace xerus {
39 
40  Tensor randVar_to_position(const double _v, const size_t _polyDegree) {
41 // const std::vector<xerus::misc::Polynomial> stochasticBasis = xerus::misc::Polynomial::build_orthogonal_base(_polyDegree, [](const double){return 1.0;}, -1., 1.);
42 
43  Tensor p({_polyDegree});
44  for (unsigned i = 0; i < _polyDegree; ++i) {
45 // p[i] = stochasticBasis[i](_v);
46  p[i] = boost::math::hermite(i, _v/std::sqrt(2))/std::pow(2.0, i/2.0);
47 // p[i] = boost::math::legendre_p(i, _v);
48 // p[i] = boost::math::legendre_q(i, _v);
49  }
50 
51  return p;
52  }
53 
55  const size_t N;
56  const size_t d;
57 
58  const double solutionsNorm;
59 
60  const std::vector<std::vector<Tensor>> positions;
61  const std::vector<Tensor>& solutions;
62 
63  TTTensor& x;
64 
65  std::vector<std::vector<Tensor>> rightStack; // From corePosition 1 to d-1
66  std::vector<std::vector<Tensor>> leftIsStack;
67  std::vector<std::vector<Tensor>> leftOughtStack;
68 
69 
70 
71  public:
72  static std::vector<std::vector<Tensor>> create_positions(const TTTensor& _x, const std::vector<std::vector<double>>& _randomVariables) {
73  std::vector<std::vector<Tensor>> positions(_x.degree());
74 
75  for(size_t corePosition = 1; corePosition < _x.degree(); ++corePosition) {
76  positions[corePosition].reserve(_randomVariables.size());
77  for(size_t j = 0; j < _randomVariables.size(); ++j) {
78  positions[corePosition].push_back(randVar_to_position(_randomVariables[j][corePosition-1], _x.dimensions[corePosition]));
79  }
80  }
81 
82  return positions;
83  }
84 
85  static double calc_solutions_norm(const std::vector<Tensor>& _solutions) {
86  double norm = 0;
87  for(const auto& s : _solutions) {
88  norm += misc::sqr(frob_norm(s));
89  }
90 
91  return std::sqrt(norm);
92  }
93 
94 
95  InternalSolver(TTTensor& _x, const std::vector<std::vector<double>>& _randomVariables, const std::vector<Tensor>& _solutions) :
96  N(_randomVariables.size()),
97  d(_x.degree()),
98  solutionsNorm(calc_solutions_norm(_solutions)),
99  positions(create_positions(_x, _randomVariables)),
100  solutions(_solutions),
101  x(_x),
102  rightStack(d, std::vector<Tensor>(N)),
103  leftIsStack(d, std::vector<Tensor>(N)),
104  leftOughtStack(d, std::vector<Tensor>(N))
105  {
106  REQUIRE(_randomVariables.size() == _solutions.size(), "ERROR");
107  }
108 
109 
110  void calc_left_stack(const size_t _corePosition) {
111  REQUIRE(_corePosition+1 < d, "Invalid corePosition");
112 
113  if(_corePosition == 0) {
114  Tensor shuffledX = x.get_component(0);
115  shuffledX.reinterpret_dimensions({x.dimensions[0], x.rank(0)});
116 
117  #pragma omp parallel for
118  for(size_t j = 0; j < N; ++j) {
119  // NOTE: leftIsStack[0] is always an identity
120  contract(leftOughtStack[_corePosition][j], solutions[j], shuffledX, 1);
121  }
122 
123  } else { // _corePosition > 0
124  const Tensor shuffledX = reshuffle(x.get_component(_corePosition), {1, 0, 2});
125  Tensor measCmp, tmp;
126  #pragma omp parallel for firstprivate(measCmp, tmp)
127  for(size_t j = 0; j < N; ++j) {
128  contract(measCmp, positions[_corePosition][j], shuffledX, 1);
129 
130  if(_corePosition > 1) {
131  contract(tmp, measCmp, true, leftIsStack[_corePosition-1][j], false, 1);
132  contract(leftIsStack[_corePosition][j], tmp, measCmp, 1);
133  } else { // _corePosition == 1
134  contract(leftIsStack[_corePosition][j], measCmp, true, measCmp, false, 1);
135  }
136 
137  contract(leftOughtStack[_corePosition][j], leftOughtStack[_corePosition-1][j], measCmp, 1);
138  }
139  }
140  }
141 
142 
143  void calc_right_stack(const size_t _corePosition) {
144  REQUIRE(_corePosition > 0 && _corePosition < d, "Invalid corePosition");
145  Tensor shuffledX = reshuffle(x.get_component(_corePosition), {1, 0, 2});
146 
147  if(_corePosition < d-1) {
148  Tensor tmp;
149  #pragma omp parallel for firstprivate(tmp)
150  for(size_t j = 0; j < N; ++j) {
151  contract(tmp, positions[_corePosition][j], shuffledX, 1);
152  contract(rightStack[_corePosition][j], tmp, rightStack[_corePosition+1][j], 1);
153  }
154  } else { // _corePosition == d-1
155  shuffledX.reinterpret_dimensions({shuffledX.dimensions[0], shuffledX.dimensions[1]}); // Remove dangling 1-mode
156  #pragma omp parallel for
157  for(size_t j = 0; j < N; ++j) {
158  contract(rightStack[_corePosition][j], positions[_corePosition][j], shuffledX, 1);
159  }
160  }
161  }
162 
163 
164  Tensor calculate_delta(const size_t _corePosition) const {
165  Tensor delta(x.get_component(_corePosition).dimensions);
166  Tensor dyadComp, tmp;
167 
168  if(_corePosition > 0) {
169  const Tensor shuffledX = reshuffle(x.get_component(_corePosition), {1, 0, 2});
170 
171  #pragma omp parallel for firstprivate(dyadComp, tmp)
172  for(size_t j = 0; j < N; ++j) {
173  // Calculate common "dyadic part"
174  Tensor dyadicPart;
175  if(_corePosition < d-1) {
176  contract(dyadicPart, positions[_corePosition][j], rightStack[_corePosition+1][j], 0);
177  } else {
178  dyadicPart = positions[_corePosition][j];
179  dyadicPart.reinterpret_dimensions({dyadicPart.dimensions[0], 1}); // Add dangling 1-mode
180  }
181 
182 
183  // Calculate "is"
184  Tensor isPart;
185  contract(isPart, positions[_corePosition][j], shuffledX, 1);
186 
187  if(_corePosition < d-1) {
188  contract(isPart, isPart, rightStack[_corePosition+1][j], 1);
189  } else {
190  isPart.reinterpret_dimensions({isPart.dimensions[0]});
191  }
192 
193  if(_corePosition > 1) { // NOTE: For _corePosition == 1 leftIsStack is the identity
194  contract(isPart, leftIsStack[_corePosition-1][j], isPart, 1);
195  }
196 
197 
198  // Combine with ought part
199  contract(dyadComp, isPart - leftOughtStack[_corePosition-1][j], dyadicPart, 0);
200 
201  #pragma omp critical
202  { delta += dyadComp; }
203  }
204  } else { // _corePosition == 0
205  Tensor shuffledX = x.get_component(0);
206  shuffledX.reinterpret_dimensions({shuffledX.dimensions[1], shuffledX.dimensions[2]});
207 
208  #pragma omp parallel for firstprivate(dyadComp, tmp)
209  for(size_t j = 0; j < N; ++j) {
210  contract(dyadComp, shuffledX, rightStack[_corePosition+1][j], 1);
211  contract(dyadComp, dyadComp - solutions[j], rightStack[_corePosition+1][j], 0);
212  dyadComp.reinterpret_dimensions({1, dyadComp.dimensions[0], dyadComp.dimensions[1]});
213 
214  #pragma omp critical
215  { delta += dyadComp; }
216  }
217  }
218 
219  return delta;
220  }
221 
222 
223  double calculate_norm_A_projGrad(const Tensor& _delta, const size_t _corePosition) const {
224  double norm = 0.0;
225  Tensor tmp;
226 
227  if(_corePosition == 0) {
228  #pragma omp parallel for firstprivate(tmp) reduction(+:norm)
229  for(size_t j = 0; j < N; ++j) {
230  contract(tmp, _delta, rightStack[1][j], 1);
231  const double normPart = misc::sqr(frob_norm(tmp));
232  norm += normPart;
233  }
234  } else { // _corePosition > 0
235  Tensor shuffledDelta = reshuffle(_delta, {1, 0, 2});
236  if(_corePosition == d-1) {
237  shuffledDelta.reinterpret_dimensions({shuffledDelta.dimensions[0], shuffledDelta.dimensions[1]}); // Remove dangling 1-mode
238  }
239 
240  Tensor rightPart;
241  #pragma omp parallel for firstprivate(tmp, rightPart) reduction(+:norm)
242  for(size_t j = 0; j < N; ++j) {
243  // Current node
244  contract(tmp, positions[_corePosition][j], shuffledDelta, 1);
245 
246  if(_corePosition < d-1) {
247  contract(rightPart, tmp, rightStack[_corePosition+1][j], 1);
248  } else {
249  rightPart = tmp;
250  }
251 
252  if(_corePosition > 1) {
253  contract(tmp, rightPart, leftIsStack[_corePosition-1][j], 1);
254  contract(tmp, tmp, rightPart, 1);
255  } else { // NOTE: For _corePosition == 1 leftIsStack is the identity
256  contract(tmp, rightPart, rightPart, 1);
257  }
258 
259  REQUIRE(tmp.size == 1, "IE");
260  norm += tmp[0];
261  }
262  }
263 
264  return std::sqrt(norm);
265  }
266 
267 
268  double calc_residual_norm(const size_t _corePosition) const {
269  REQUIRE(_corePosition == 0, "Invalid corePosition");
270 
271  double norm = 0.0;
272 
273  Tensor tmp;
274  for(size_t j = 0; j < N; ++j) {
275  contract(tmp, x.get_component(0), rightStack[1][j], 1);
277  tmp -= solutions[j];
278  norm += misc::sqr(frob_norm(tmp));
279  }
280 
281  return std::sqrt(norm);
282  }
283 
284 
285  void solve() {
286  std::vector<double> residuals(10, 1000.0);
287  const size_t maxIterations = 1000;
288 
289  for(size_t iteration = 0; maxIterations == 0 || iteration < maxIterations; ++iteration) {
290  x.move_core(0, true);
291 
292  // Rebuild right stack
293  for(size_t corePosition = d-1; corePosition > 0; --corePosition) {
294  calc_right_stack(corePosition);
295  }
296 
297 
298  for(size_t corePosition = 0; corePosition < x.degree(); ++corePosition) {
299  if(corePosition == 0) {
300  residuals.push_back(calc_residual_norm(0)/solutionsNorm);
301 
302  if(residuals.back()/residuals[residuals.size()-10] > 0.99) {
303  LOG(ADF, "Residual decrease from " << std::scientific << residuals[10] << " to " << std::scientific << residuals.back() << " in " << residuals.size()-10 << " iterations.");
304  return; // We are done!
305  }
306  }
307 
308  const auto delta = calculate_delta(corePosition);
309  const auto normAProjGrad = calculate_norm_A_projGrad(delta, corePosition);
310  const value_t PyR = misc::sqr(frob_norm(delta));
311 
312  // Actual update
313  x.component(corePosition) -= (PyR/misc::sqr(normAProjGrad))*delta;
314 
315  // If we have not yet reached the end of the sweep we need to take care of the core and update our stacks
316  if(corePosition+1 < d) {
317  x.move_core(corePosition+1, true);
318  calc_left_stack(corePosition);
319  }
320  }
321  }
322  }
323  };
324 
325 
326 
327  void uq_adf(TTTensor& _x, const std::vector<std::vector<double>>& _randomVariables, const std::vector<Tensor>& _solutions) {
328  LOG(ADF, "Start UQ ADF");
329  InternalSolver solver(_x, _randomVariables, _solutions);
330  return solver.solve();
331  }
332 
333 
334  TTTensor uq_adf(const UQMeasurementSet& _measurments, const TTTensor& _guess) {
335  REQUIRE(_measurments.randomVectors.size() == _measurments.solutions.size(), "Invalid measurments");
336  REQUIRE(_measurments.initialRandomVectors.size() == _measurments.initialSolutions.size(), "Invalid initial measurments");
337 
338  if(_measurments.initialRandomVectors.size() > 0) {
339  LOG(UQ_ADF, "Init");
340  TTTensor x(_guess.dimensions);
341  TTTensor newX(x.dimensions);
342 
343  std::vector<std::vector<double>> randomVectors = _measurments.randomVectors;
344  std::vector<Tensor> solutions = _measurments.solutions;
345 
346  // Calc mean
347  Tensor mean({x.dimensions[0]});
348  for(const auto& sol : solutions) {
349  mean += sol;
350  }
351  mean /= double(solutions.size());
352 
353  TTTensor baseTerm(x.dimensions);
354  mean.reinterpret_dimensions({1, x.dimensions[0], 1});
355  baseTerm.set_component(0, mean);
356  for(size_t k = 0; k < _measurments.initialRandomVectors.size(); ++k) {
357  baseTerm.set_component(k+1, Tensor::dirac({1, x.dimensions[k+1], 1}, 0));
358  }
359  baseTerm.assume_core_position(0);
360  newX += baseTerm;
361 
362  mean.reinterpret_dimensions({x.dimensions[0]});
363 
364  // Calc linear terms
365  REQUIRE(_measurments.initialRandomVectors.size() == _measurments.initialRandomVectors[0].size(), "Sizes don't match.");
366  REQUIRE(_measurments.initialRandomVectors.size() == _measurments.initialSolutions.size(), "Sizes don't match.");
367  REQUIRE(_measurments.initialRandomVectors.size()+1 == x.degree(), "Sizes don't match.");
368 
369  for(size_t m = 0; m < _measurments.initialRandomVectors.size(); ++m) {
370  REQUIRE(_measurments.initialRandomVectors[m][m] > 0.0, "Invalid initial randVec");
371  TTTensor linearTerm(x.dimensions);
372  Tensor tmp = (_measurments.initialSolutions[m] - mean);
373  tmp.reinterpret_dimensions({1, x.dimensions[0], 1});
374  linearTerm.set_component(0, tmp);
375  for(size_t k = 0; k < _measurments.initialRandomVectors.size(); ++k) {
376  if(k == m) {
377  linearTerm.set_component(k+1, Tensor::dirac({1, x.dimensions[k+1], 1}, 0));
378  } else {
379  REQUIRE(misc::hard_equal(_measurments.initialRandomVectors[m][k], 0.0), "Invalid initial randVec");
380  REQUIRE(x.dimensions[k+1] >= 1, "WTF");
381  linearTerm.set_component(k+1, Tensor::dirac({1, x.dimensions[k+1], 1}, 1));
382  }
383  }
384  linearTerm.assume_core_position(0);
385  newX += linearTerm;
386  }
387 
388  // Add some noise
389 // auto noise = TTTensor::random(newX.dimensions, std::vector<size_t>(newX.degree()-1, 2));
390 // noise *= 1e-4*frob_norm(newX)/frob_norm(noise);
391 // LOG(check, frob_norm(newX) << " vs " << frob_norm(noise));
392 // newX += noise;
393 
394 
395 
396  // Add initial measurments. NOTE: must happen after mean is calculated
397  randomVectors.insert(randomVectors.end(), _measurments.initialRandomVectors.begin(), _measurments.initialRandomVectors.end());
398  solutions.insert(solutions.end(), _measurments.initialSolutions.begin(), _measurments.initialSolutions.end());
399 
400  x = newX;
401  LOG(UQ_ADF, "Pre roundign ranks: " << x.ranks());
402  x.round(0.00025);
403  LOG(UQ_ADF, "Post roundign ranks: " << x.ranks());
404  uq_adf(x, _measurments.randomVectors, _measurments.solutions);
405  return x;
406  } else {
407  auto x = _guess;
408  uq_adf(x, _measurments.randomVectors, _measurments.solutions);
409  return x;
410  }
411  }
412 
413 
414  void UQMeasurementSet::add(const std::vector<double>& _rndvec, const Tensor& _solution) {
415  randomVectors.push_back(_rndvec);
416  solutions.push_back(_solution);
417  }
418 
419  void UQMeasurementSet::add_initial(const std::vector<double>& _rndvec, const Tensor& _solution) {
420  initialRandomVectors.push_back(_rndvec);
421  initialSolutions.push_back(_solution);
422  }
423 
424 
425  std::pair<std::vector<std::vector<double>>, std::vector<Tensor>> uq_mc(const TTTensor& _x, const size_t _N, const size_t _numSpecial) {
426  std::mt19937_64 rnd;
427  std::normal_distribution<double> dist(0.0, 1.0);
428 
429  std::vector<std::vector<double>> randomVariables;
430  std::vector<Tensor> solutions;
431 
432  randomVariables.reserve(_N);
433  solutions.reserve(_N);
434  for(size_t i = 0; i < _N; ++i) {
435  randomVariables.push_back(std::vector<double>(_x.degree()-1));
436  Tensor p = Tensor::ones({1});
437  for(size_t k = _x.degree()-1; k > 0; --k) {
438  randomVariables[i][k-1] = (k <= _numSpecial?0.3:1.0)*dist(rnd);
439  contract(p, _x.get_component(k), p, 1);
440  contract(p, p, randVar_to_position(randomVariables[i][k-1], _x.dimensions[k]), 1);
441  }
442  contract(p, _x.get_component(0), p, 1);
444  solutions.push_back(p);
445  }
446 
447  return std::make_pair(randomVariables, solutions);
448  }
449 
450 
451  Tensor uq_avg(const TTTensor& _x, const size_t _N, const size_t _numSpecial) {
452  Tensor realAvg({_x.dimensions[0]});
453 
454  #pragma omp parallel
455  {
456  std::mt19937_64 rnd;
457  std::normal_distribution<double> dist(0.0, 1.0);
458  Tensor avg({_x.dimensions[0]});
459 
460  #pragma omp parallel for
461  for(size_t i = 0; i < _N; ++i) {
462  Tensor p = Tensor::ones({1});
463  for(size_t k = _x.degree()-1; k > 0; --k) {
464  contract(p, _x.get_component(k), p, 1);
465  contract(p, p, randVar_to_position((k <= _numSpecial?0.3:1.0)*dist(rnd), _x.dimensions[k]), 1);
466  }
467  contract(p, _x.get_component(0), p, 1);
469  avg += p;
470  }
471 
472  #pragma omp critical
473  { realAvg += avg; }
474  }
475 
476  return realAvg/double(_N);
477  }
478 
479 } // namespace xerus
void assume_core_position(const size_t _pos)
stores _pos as the current core position without verifying of ensuring that this is the case ...
Definition: ttNetwork.cpp:735
void calc_left_stack(const size_t _corePosition)
Definition: uqAdf.cpp:110
std::vector< Tensor > solutions
Definition: uqAdf.h:38
InternalSolver(TTTensor &_x, const std::vector< std::vector< double >> &_randomVariables, const std::vector< Tensor > &_solutions)
Definition: uqAdf.cpp:95
void reshuffle(Tensor &_out, const Tensor &_base, const std::vector< size_t > &_shuffle)
: Performs a simple reshuffle. Much less powerfull then a full evaluate, but more efficient...
void add_initial(const std::vector< double > &_rndvec, const Tensor &_solution)
Definition: uqAdf.cpp:419
double calc_residual_norm(const size_t _corePosition) const
Definition: uqAdf.cpp:268
#define REQUIRE
Definition: internal.h:33
Tensor & component(const size_t _idx)
Complete access to a specific component of the TT decomposition.
Definition: ttNetwork.cpp:457
void add(const std::vector< double > &_rndvec, const Tensor &_solution)
Definition: uqAdf.cpp:414
size_t size
Size of the Tensor – always equal to the product of the dimensions.
Definition: tensor.h:102
#define LOG
Definition: internal.h:38
DimensionTuple dimensions
Vector containing the individual dimensions of the tensor.
Definition: tensor.h:99
Tensor calculate_delta(const size_t _corePosition) const
Definition: uqAdf.cpp:164
std::vector< std::vector< double > > initialRandomVectors
Definition: uqAdf.h:40
The main namespace of xerus.
Definition: basic.h:37
Class that handles simple (non-decomposed) tensors in a dense or sparse representation.
Definition: tensor.h:70
STL namespace.
void calc_right_stack(const size_t _corePosition)
Definition: uqAdf.cpp:143
double calculate_norm_A_projGrad(const Tensor &_delta, const size_t _corePosition) const
Definition: uqAdf.cpp:223
static Tensor XERUS_warn_unused ones(DimensionTuple _dimensions)
: Returns a Tensor with all entries equal to one.
Definition: tensor.cpp:122
void reinterpret_dimensions(DimensionTuple _newDimensions)
Reinterprets the dimensions of the tensor.
Definition: tensor.cpp:620
Header file for the ADF algorithm and its variants.
const ADFVariant ADF
Default variant of the ADF algorithm.
static std::vector< std::vector< Tensor > > create_positions(const TTTensor &_x, const std::vector< std::vector< double >> &_randomVariables)
Definition: uqAdf.cpp:72
size_t degree() const
Gets the degree of the TensorNetwork.
static double calc_solutions_norm(const std::vector< Tensor > &_solutions)
Definition: uqAdf.cpp:85
Tensor uq_avg(const TTTensor &_x, const size_t _N, const size_t _numSpecial)
Definition: uqAdf.cpp:451
static Tensor XERUS_warn_unused dirac(DimensionTuple _dimensions, const MultiIndex &_position)
: Returns a Tensor with a single entry equals one and all other zero.
Definition: tensor.cpp:173
void move_core(const size_t _position, const bool _keepRank=false)
Move the core to a new position.
Definition: ttNetwork.cpp:582
void contract(Tensor &_result, const Tensor &_lhs, const bool _lhsTrans, const Tensor &_rhs, const bool _rhsTrans, const size_t _numModes)
Low-level contraction between Tensors.
Definition: tensor.cpp:1252
std::vector< size_t > ranks() const
Gets the ranks of the TTNetwork.
Definition: ttNetwork.cpp:717
Header file for several elementary numerical methods: polynomials, romberg integration and limit extr...
void set_component(const size_t _idx, Tensor _T)
Sets a specific component of the TT decomposition.
Definition: ttNetwork.cpp:471
void uq_adf(TTTensor &_x, const std::vector< std::vector< double >> &_randomVariables, const std::vector< Tensor > &_solutions)
Definition: uqAdf.cpp:327
std::vector< std::vector< double > > randomVectors
Definition: uqAdf.h:37
Header file for the low level array support function.
Header file for comfort functions and macros that should not be exported in the library.
Tensor randVar_to_position(const double _v, const size_t _polyDegree)
Definition: uqAdf.cpp:40
static XERUS_force_inline value_t frob_norm(const Tensor &_tensor)
Calculates the frobenius norm of the given tensor.
Definition: tensor.h:931
constexpr T sqr(const T &_a) noexcept
: Calculates _a*_a.
Definition: math.h:43
double value_t
The type of values to be used by xerus.
Definition: basic.h:43
std::vector< Tensor > initialSolutions
Definition: uqAdf.h:41
size_t rank(const size_t _i) const
Gets the rank of a specific egde of the TTNetwork.
Definition: ttNetwork.cpp:728
std::pair< std::vector< std::vector< double > >, std::vector< Tensor > > uq_mc(const TTTensor &_x, const size_t _N, const size_t _numSpecial)
Definition: uqAdf.cpp:425
constexpr bool hard_equal(const T _a, const T _b) noexcept
: Checks for hard equality ( == operator) without compiler warnings.
Definition: math.h:105
constexpr T pow(const T &_base, const uint32 _exp) noexcept
: Calculates _base^_exp by binary exponentiation
Definition: math.h:50
void round(const std::vector< size_t > &_maxRanks, const double _eps=EPSILON)
Reduce all ranks up to a given accuracy and maximal number.
Definition: ttNetwork.cpp:644
const Tensor & get_component(const size_t _idx) const
Read access to a specific component of the TT decomposition.
Definition: ttNetwork.cpp:464
std::vector< size_t > dimensions
Dimensions of the external indices, i.e. the dimensions of the tensor represented by the network...