• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

adc-connect / adcc / 27678296279

17 Jun 2026 09:09AM UTC coverage: 76.084% (+0.9%) from 75.165%
27678296279

Pull #124

github

web-flow
Merge 0c973e6ec into b52bb1797
Pull Request #124: Nuclear Gradients

1498 of 2302 branches covered (65.07%)

Branch coverage included in aggregate %.

1028 of 1181 new or added lines in 20 files covered. (87.04%)

2 existing lines in 2 files now uncovered.

9201 of 11760 relevant lines covered (78.24%)

119075.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.74
/libadcc_src/TensorImpl.cc
1
//
2
// Copyright (C) 2020 by the adcc authors
3
//
4
// This file is part of adcc.
5
//
6
// adcc is free software: you can redistribute it and/or modify
7
// it under the terms of the GNU General Public License as published
8
// by the Free Software Foundation, either version 3 of the License, or
9
// (at your option) any later version.
10
//
11
// adcc 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 adcc. If not, see <http://www.gnu.org/licenses/>.
18
//
19

20
#include "TensorImpl.hh"
21
#include "TensorImpl/as_bispace.hh"
22
#include "TensorImpl/as_lt_symmetry.hh"
23
#include "TensorImpl/get_block_starts.hh"
24
#include "shape_to_string.hh"
25

26
// Change visibility of libtensor singletons to public
27
#pragma GCC visibility push(default)
28
#include <libtensor/block_tensor/btod_add.h>
29
#include <libtensor/block_tensor/btod_copy.h>
30
#include <libtensor/block_tensor/btod_dotprod.h>
31
#include <libtensor/block_tensor/btod_export.h>
32
#include <libtensor/block_tensor/btod_random.h>
33
#include <libtensor/block_tensor/btod_select.h>
34
#include <libtensor/block_tensor/btod_set.h>
35
#include <libtensor/block_tensor/btod_set_diag.h>
36
#include <libtensor/block_tensor/btod_set_elem.h>
37
#include <libtensor/symmetry/print_symmetry.h>
38
#pragma GCC visibility pop
39

40
namespace libadcc {
41
namespace lt = libtensor;
42

43
#define DIMENSIONALITY_CHECK(OTHER)                                                    \
44
  {                                                                                    \
45
    if (ndim() != OTHER->ndim()) {                                                     \
46
      throw dimension_mismatch(                                                        \
47
            "Dimensionality of this tensor (" + std::to_string(ndim()) +               \
48
            ") does not agree with the dimensionality of the other tensor"             \
49
            " passed, which has dimensionality " +                                     \
50
            std::to_string(OTHER->ndim()) + ".");                                      \
51
    }                                                                                  \
52
    if (shape() != OTHER->shape()) {                                                   \
53
      throw dimension_mismatch("Shape of this tensor (" + shape_to_string(shape()) +   \
54
                               ") does not agree with the shape of the other tensor" + \
55
                               " passed, which has shape " +                           \
56
                               shape_to_string(OTHER->shape()) + ".");                 \
57
    }                                                                                  \
58
    if (axes() != OTHER->axes()) {                                                     \
59
      throw dimension_mismatch("Axes of this tensor (" + axes_to_string(axes()) +      \
60
                               ") do not agree with the axes of the other tensor "     \
61
                               "passed, which has axis labels " +                      \
62
                               axes_to_string(OTHER->axes()) + ".");                   \
63
    }                                                                                  \
64
  }
65

66
namespace {
67
std::string axes_to_string(const std::vector<AxisInfo>& axes) {
×
68
  std::string res = "";
×
69
  for (auto& ax : axes) res.append(ax.label);
×
70
  return res;
×
71
}
×
72

73
/** Build an identity permutation of length n */
74
std::vector<size_t> identity_permutation(size_t n) {
579,553✔
75
  std::vector<size_t> permutation;
579,553✔
76
  for (size_t i = 0; i < n; ++i) {
2,358,429✔
77
    permutation.push_back(i);
1,778,876✔
78
  }
79
  return permutation;
579,553✔
80
}
×
81

82
template <size_t N>
83
lt::expr::label<N> strip_safe(const std::vector<std::shared_ptr<const lt::letter>>& in) {
1,179,239✔
84
  std::vector<const lt::letter*> label_unsafe;
1,179,239✔
85
  for (size_t i = 0; i < in.size(); ++i) {
4,371,313✔
86
    label_unsafe.push_back(in[i].get());
3,192,074✔
87
  }
88
  return lt::expr::label<N>(label_unsafe);
2,358,478✔
89
}
1,179,239✔
90

91
template <size_t N>
92
std::vector<size_t> extract_expr_permutation(
857,257✔
93
      const lt::expr::expr_rhs<N, scalar_type>& expr,
94
      const std::vector<std::shared_ptr<const lt::letter>>& label) {
95
  std::vector<size_t> permutation;
857,257✔
96
  lt::permutation<N> perm = expr.get_label().permutation_of(strip_safe<N>(label));
857,257✔
97
  perm.invert();
857,257✔
98
  for (size_t i = 0; i < N; ++i) {
3,324,466✔
99
    permutation.push_back(perm[i]);
2,467,209✔
100
  }
101
  return permutation;
1,714,514✔
102
}
×
103

104
/** Merge two keepalive lists */
105
std::vector<std::shared_ptr<void>> merge(std::vector<std::shared_ptr<void>> lhs,
460,039✔
106
                                         const std::vector<std::shared_ptr<void>>& rhs) {
107
  for (auto& ptr : rhs) lhs.push_back(ptr);
1,455,644✔
108
  return lhs;
460,039✔
109
}
110

111
std::vector<std::shared_ptr<const lt::letter>> make_label(size_t n) {
1,099,922✔
112
  std::vector<std::shared_ptr<const lt::letter>> ret;
1,099,922✔
113
  for (size_t i = 0; i < n; ++i) {
4,379,753✔
114
    ret.push_back(std::make_shared<lt::letter>());
3,279,831✔
115
  }
116
  return ret;
1,099,922✔
117
}
×
118

119
template <typename T, size_t>
120
using repeat_t = T;
121

122
template <typename T, size_t... I>
123
std::tuple<repeat_t<T, I>...> tuple_array_impl(std::index_sequence<I...>);
124

125
/** Array like tuple that contains N fields of the given type */
126
template <typename T, size_t N>
127
using tuple_array_t = decltype(tuple_array_impl<T>(std::make_index_sequence<N>{}));
128

129
template <size_t PERM_SIZE, size_t N_PERMS, size_t... I>
130
tuple_array_t<lt::expr::label<N_PERMS>, PERM_SIZE> make_label_tuple(
77,491✔
131
      const std::array<std::vector<const lt::letter*>, PERM_SIZE>& sets,
132
      std::index_sequence<I...>) {
133
  return {(lt::expr::label<N_PERMS>(sets[I]))...};
77,491✔
134
}
135

136
std::string perm_to_string(const std::vector<size_t>& permutation) {
×
137
  std::ostringstream oss;
×
138
  oss << "[";
×
139
  for (size_t i{0}; i < permutation.size(); i++) {
×
140
    if (i != 0) oss << ", ";
×
141
    oss << permutation[i];
×
142
  }
143
  oss << "]";
×
144
  return oss.str();
×
145
}
×
146

147
// Using a vector as return type would not significantly simplify the
148
// code in symmetrise and antisymmetrise below.
149
// Using an array would in principle be possible and avoids the
150
// meta programming above to define the tuple_array_t, but there
151
// are additional compile time guarantees to prevent for instance
152
// out of bounds access for tuples, which is not available for arrays
153
template <size_t PERM_SIZE, size_t N_PERMS, size_t N>
154
tuple_array_t<lt::expr::label<N_PERMS>, PERM_SIZE> parse_permutation(
77,491✔
155
      const std::vector<AxisInfo>& axes, const lt::expr::label<N>& label,
156
      const std::vector<std::vector<size_t>>& permutations) {
157
  static_assert(PERM_SIZE > 1,
158
                "Permutations have to occur between at least two sets of indices.");
159
  static_assert(N_PERMS > 0, "There has to be at least 1 permutation");
160

161
  if (permutations.size() != N_PERMS)
77,491✔
162
    throw invalid_argument("Invalid number of permutations provided (" +
×
163
                           std::to_string(permutations.size()) + "). Expected " +
164
                           std::to_string(N_PERMS) + ".");
165

166
  std::array<std::vector<const lt::letter*>, PERM_SIZE> sets;
77,491✔
167
  std::vector<size_t> processed_indices;
77,491✔
168
  for (const auto& perm : permutations) {
162,590✔
169
    if (perm.size() != PERM_SIZE)
85,099✔
170
      throw invalid_argument(
×
171
            "Bad number of indices in permutation: " + perm_to_string(perm) +
172
            ". Expected " + std::to_string(PERM_SIZE) + " indices.");
173
    // Sanity check the permutation (PERM_SIZE is small -> just brute force)
174
    for (size_t i{0}; i < PERM_SIZE; i++) {
255,315✔
175
      for (size_t j{i + 1}; j < PERM_SIZE; j++) {
255,351✔
176
        if (perm[i] == perm[j])
85,135✔
177
          throw invalid_argument("Duplicate entry in permutation " +
×
178
                                 perm_to_string(perm) + " detected.");
179
        if (axes[perm[i]] != axes[perm[j]]) {
85,135✔
180
          std::ostringstream msg;
×
181
          msg << "(Anti)-Symmetrisation can only be performed over equivalent axes (not ";
×
182
          for (size_t k{0}; k < PERM_SIZE; k++) {
×
183
            if (k != 0) msg << ", ";
×
184
            msg << axes[perm[k]].label;
×
185
          }
186
          msg << ")";
×
187
          throw invalid_argument(msg.str());
×
188
        }
×
189
      }
190
    }
191
    // go through the permutation and actually parse it
192
    for (size_t i{0}; i < PERM_SIZE; i++) {
255,315✔
193
      // check if the index was already included in another permutation
194
      auto find_i =
195
            std::find(processed_indices.begin(), processed_indices.end(), perm[i]);
170,216✔
196
      if (find_i != processed_indices.end()) {
170,216✔
197
        std::ostringstream msg;
×
198
        msg << "Permutations in a permutation list have to be disjoint. Got\n";
×
199
        for (const auto& perm : permutations) {
×
200
          msg << perm_to_string(perm) << "\n";
×
201
        }
202
        throw invalid_argument(msg.str());
×
203
      }
×
204
      if (perm[i] >= N)
170,216✔
205
        throw invalid_argument("Index in permutation " + perm_to_string(perm) +
×
206
                               " cannot be larger than dimensionality (" +
207
                               std::to_string(N) + ").");
208
      sets[i].push_back(&label.letter_at(perm[i]));
170,216✔
209
      processed_indices.push_back(perm[i]);
170,216✔
210
    }
211
  }
212
  // convert the positions into libtensor labels
213
  return make_label_tuple<PERM_SIZE, N_PERMS>(sets,
214
                                              std::make_index_sequence<PERM_SIZE>{});
154,982✔
215
}
77,491✔
216

217
template <size_t N, typename T>
218
std::pair<lt::index<N>, lt::index<N>> assert_convert_tensor_index(
981,993✔
219
      lt::btensor<N, T>& tensor, const std::vector<size_t>& idx) {
220
  if (idx.size() != N) {
981,993✔
221
    throw dimension_mismatch("Tensor is of dimension " + std::to_string(N) +
×
222
                             ", but passed index has a dimennsion of " +
223
                             std::to_string(idx.size()) + ".");
224
  }
225
  const lt::dimensions<N>& dims = tensor.get_bis().get_dims();
981,993✔
226
  for (size_t i = 0; i < N; ++i) {
4,839,235✔
227
    if (idx[i] >= dims[i]) {
3,857,242✔
228
      throw invalid_argument("Passed index " + shape_to_string(idx) +
×
229
                             " overshoots Tensor at dimension " + std::to_string(i) +
230
                             " (with extent: " + std::to_string(dims[i]) + ")");
231
    }
232
  }
233

234
  lt::index<N> block_idx;
981,993✔
235
  for (size_t idim = 0; idim < N; ++idim) {
4,839,235✔
236
    // Find splits
237
    const size_t dim_type     = tensor.get_bis().get_type(idim);
3,857,242✔
238
    const lt::split_points sp = tensor.get_bis().get_splits(dim_type);
3,857,242✔
239
    block_idx[idim]           = 0;
3,857,242✔
240
    for (size_t isp = 0; isp < sp.get_num_points(); ++isp) {
5,778,512✔
241
      if (sp[isp] > idx[idim]) break;
3,857,474✔
242
      block_idx[idim] = isp + 1;
1,921,270✔
243
    }
244
  }
245

246
  lt::index<N> inblock_idx;
981,993✔
247
  lt::index<N> bstart    = tensor.get_bis().get_block_start(block_idx);
981,993✔
248
  lt::dimensions<N> bdim = tensor.get_bis().get_block_dims(block_idx);
981,993✔
249
  for (size_t idim = 0; idim < N; ++idim) {
4,839,235✔
250
    inblock_idx[idim] = idx[idim] - bstart[idim];
3,857,242✔
251
    if (inblock_idx[idim] >= bdim[idim]) {
3,857,242✔
252
      throw runtime_error(
×
253
            "Internal error: Determined in-block index overshoots block dimensionality");
254
    }
255
  }
256

257
  return std::make_pair(block_idx, inblock_idx);
1,963,986✔
258
}
259

260
template <typename Comparator, size_t N>
261
std::vector<std::pair<std::vector<size_t>, scalar_type>> execute_select_n(
1,519✔
262
      lt::btensor<N, scalar_type>& tensor, size_t n, bool unique_by_symmetry) {
263
  using btod_select_t = lt::btod_select<N, Comparator>;
264
  std::list<lt::block_tensor_element<N, scalar_type>> il;
1,519✔
265
  if (!unique_by_symmetry) {
1,519✔
266
    lt::symmetry<N, scalar_type> nosym(tensor.get_bis());
×
267
    btod_select_t(tensor, nosym).perform(il, n);
×
268
  } else {
×
269
    btod_select_t(tensor).perform(il, n);
1,519✔
270
  }
271

272
  std::vector<std::pair<std::vector<size_t>, scalar_type>> ret;
1,519✔
273
  for (auto it = il.begin(); it != il.end(); ++it) {
23,688✔
274
    std::vector<size_t> fidx(N);
22,169✔
275
    const lt::index<N> bstart = tensor.get_bis().get_block_start(it->get_block_index());
22,169✔
276
    for (size_t i = 0; i < N; ++i) {
66,507✔
277
      fidx[i] = bstart[i] + it->get_in_block_index()[i];
44,338✔
278
    }
279
    ret.emplace_back(fidx, it->get_value());
22,169✔
280
  }
281
  return ret;
3,038✔
282
}
1,519✔
283
}  // namespace
284

285
template <size_t N>
286
void TensorImpl<N>::check_state() const {
7,689,725✔
287
  if (m_expr_ptr == nullptr && m_libtensor_ptr == nullptr) {
7,689,725✔
288
    throw runtime_error(
×
289
          "Internal error: m_libtensor_ptr and m_expr_ptr cannot both be nullptr.");
290
  }
291
  if (m_expr_ptr != nullptr && m_libtensor_ptr != nullptr) {
7,689,725✔
292
    throw runtime_error(
×
293
          "Internal error: m_libtensor_ptr and m_expr_ptr cannot both be set pointers.");
294
  }
295

296
  if (N != ndim()) {
7,689,725✔
297
    throw runtime_error("Internal error: libtensor dimension (== " + std::to_string(N) +
×
298
                        ") and tensor dimension (==" + std::to_string(ndim()) +
299
                        ") differ.");
300
  }
301

302
  if (m_libtensor_ptr) {
7,689,725✔
303
    std::vector<size_t> btshape(N);
6,385,456✔
304
    const lt::dimensions<N>& dims = m_libtensor_ptr->get_bis().get_dims();
6,385,456✔
305
    for (size_t i = 0; i < N; ++i) {
28,030,987✔
306
      btshape[i] = dims.get_dim(i);
21,645,531✔
307
    }
308
    if (shape() != btshape) {
6,385,456✔
309
      throw runtime_error(
×
310
            "Internal error: libtensor shape (== " + shape_to_string(btshape) +
311
            ") and tensor shape (==" + shape_to_string(shape()) + ") differ.");
312
    }
313

314
    const std::vector<std::vector<size_t>> tensorblocks =
6,385,456✔
315
          get_block_starts(*m_libtensor_ptr);
6,385,456✔
316
    for (size_t i = 0; i < N; ++i) {
28,030,987✔
317
      if (axes()[i].block_starts != tensorblocks[i]) {
21,645,531✔
318
        throw runtime_error("Internal error: Block starts of btensor " +
×
319
                            shape_to_string(tensorblocks[i]) + " at dimension " +
×
320
                            std::to_string(i) +
321
                            " do not agree with the cached block sarts " +
322
                            shape_to_string(axes()[i].block_starts) + ".");
×
323
      }
324
    }
325
  }
6,385,456✔
326

327
  if (m_expr_ptr) {
7,689,725✔
328
    if (m_expr_ptr->permutation.size() != N) {
1,304,269✔
329
      throw runtime_error(
×
330
            "Internal error: Expression dimension (== " + std::to_string(N) +
331
            ") and tensor dimension (==" + std::to_string(ndim()) + ") differ.");
332
    }
333
  }
334
}
7,689,725✔
335

336
template <size_t N>
337
void TensorImpl<N>::reset_state(
276,559✔
338
      std::shared_ptr<lt::btensor<N, scalar_type>> libtensor_ptr) const {
339
  if (m_expr_ptr != nullptr && m_libtensor_ptr != nullptr) {
276,559✔
340
    throw runtime_error(
×
341
          "Internal error: m_libtensor_ptr and m_expr_ptr cannot both be set pointers.");
342
  }
343
  if (libtensor_ptr == nullptr) {
276,559✔
344
    throw runtime_error(
×
345
          "Internal error: libtensor_ptr to be used for reset_state is a nullptr.");
346
  }
347
  m_libtensor_ptr = libtensor_ptr;
276,559✔
348
  m_expr_ptr.reset();
276,559✔
349
  check_state();
276,559✔
350
}
276,559✔
351

352
template <size_t N>
353
void TensorImpl<N>::reset_state(std::shared_ptr<ExpressionTree> expr_ptr) const {
1,118,854✔
354
  if (m_expr_ptr != nullptr && m_libtensor_ptr != nullptr) {
1,118,854✔
355
    throw runtime_error(
×
356
          "Internal error: m_libtensor_ptr and m_expr_ptr cannot both be set pointers.");
357
  }
358
  if (expr_ptr == nullptr) {
1,118,854✔
359
    throw runtime_error(
×
360
          "Internal error: expr_ptr to be used for reset_state is a nullptr.");
361
  }
362
  m_expr_ptr = expr_ptr;
1,118,854✔
363
  m_libtensor_ptr.reset();
1,118,854✔
364
  check_state();
1,118,854✔
365
}
1,118,854✔
366

367
template <size_t N>
368
TensorImpl<N>::TensorImpl(std::shared_ptr<const AdcMemory> adcmem_ptr,
1,210,540✔
369
                          std::vector<AxisInfo> axes,
370
                          std::shared_ptr<lt::btensor<N, scalar_type>> libtensor_ptr,
371
                          std::shared_ptr<ExpressionTree> expr_ptr)
372
      : Tensor(adcmem_ptr, axes), m_libtensor_ptr(nullptr), m_expr_ptr(nullptr) {
1,210,540✔
373
  if (axes.size() != N) {
1,210,540✔
374
    throw invalid_argument("axes length (== " + std::to_string(axes.size()) +
×
375
                           ") does not agree with tensor dimensionality " +
376
                           std::to_string(N));
377
  }
378

379
  if (expr_ptr != nullptr && libtensor_ptr != nullptr) {
1,210,540✔
380
    throw invalid_argument("libtensor_ptr and expr_ptr cannot both be set pointers.");
×
381
  }
382
  if (expr_ptr == nullptr && libtensor_ptr == nullptr) {
1,210,540✔
383
    // Allocate an empty tensor.
384
    libtensor_ptr = std::make_shared<lt::btensor<N, scalar_type>>(as_bispace<N>(axes));
42✔
385
  }
386

387
  if (expr_ptr != nullptr) reset_state(expr_ptr);
1,210,540✔
388
  if (libtensor_ptr != nullptr) reset_state(libtensor_ptr);
1,210,540✔
389
}
1,210,540✔
390

391
template <size_t N>
392
void TensorImpl<N>::evaluate() const {
6,234,442✔
393
  check_state();
6,234,442✔
394
  if (!needs_evaluation()) return;
6,234,442✔
395

396
  // Allocate output tensor and evaluate
397
  auto newtensor_ptr =
184,873✔
398
        std::make_shared<lt::btensor<N, scalar_type>>(as_bispace<N>(m_axes));
184,873✔
399
  m_expr_ptr->evaluate_to(*newtensor_ptr, /* add = */ false);
184,873✔
400

401
  // Check and test new tensor, cleanup expression
402
  reset_state(newtensor_ptr);
184,873✔
403
}
184,873✔
404

405
template <size_t N>
406
std::shared_ptr<Tensor> TensorImpl<N>::empty_like() const {
59,870✔
407
  check_state();
59,870✔
408

409
  // TODO This evaluates the expression, which is probably an unexpected effect.
410

411
  // Create new btensor using the old bispace
412
  auto newtensor_ptr =
59,870✔
413
        std::make_shared<lt::btensor<N, scalar_type>>(libtensor_ptr()->get_bis());
119,740✔
414

415
  // Copy the symmetry over
416
  lt::block_tensor_ctrl<N, scalar_type> ctrl_to(*newtensor_ptr);
59,870✔
417
  lt::block_tensor_ctrl<N, scalar_type> ctrl_from(*libtensor_ptr());
59,870✔
418
  lt::so_copy<N, scalar_type>(ctrl_from.req_const_symmetry())
59,870✔
419
        .perform(ctrl_to.req_symmetry());
59,870✔
420

421
  // Enwrap inside TensorImpl and return
422
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, std::move(newtensor_ptr));
119,740✔
423
}
59,870✔
424

425
template <size_t N>
426
std::shared_ptr<Tensor> TensorImpl<N>::nosym_like() const {
26✔
427
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes);
26✔
428
}
429

430
template <size_t N>
431
void TensorImpl<N>::set_mask(std::string mask, scalar_type value) {
88,952✔
432
  if (N != mask.size()) {
88,952✔
433
    throw invalid_argument("The number of characters in the index mask (== " + mask +
×
434
                           ") does not agree with the Tensor dimensionality (== " +
435
                           std::to_string(N) + ")");
436
  }
437

438
  // Non-obviously the indices for the mask have to start with 1,
439
  // 0 gives utterly weird values
440
  size_t next_idx = 1;
88,952✔
441
  std::map<char, size_t> char_to_idx;
88,952✔
442
  lt::sequence<N, size_t> seq(0);
88,952✔
443
  for (size_t i = 0; i < mask.size(); ++i) {
338,829✔
444
    const char c  = mask[i];
249,877✔
445
    const auto it = char_to_idx.find(c);
249,877✔
446
    if (it == char_to_idx.end()) {
249,877✔
447
      char_to_idx[c] = next_idx;
248,508✔
448
      seq[i]         = next_idx;
248,508✔
449
      next_idx += 1;
248,508✔
450
    } else {
451
      seq[i] = it->second;
1,369✔
452
    }
453
  }
454

455
  if (char_to_idx.size() == N) {
88,952✔
456
    // Every character in the mask is different ... just use bto_set
457
    // TODO Optimise: This evaluates, but here there is no point ... Just allocate
458
    lt::btod_set<N>(value).perform(*libtensor_ptr());
87,616✔
459
  } else {
460
    lt::btod_set_diag<N>(seq, value).perform(*libtensor_ptr());
1,336✔
461
  }
462
}
88,952✔
463

464
template <size_t N>
465
void TensorImpl<N>::set_random() {
926✔
466
  // TODO optimise: No point in evaluating ... just allocate
467
  lt::btod_random<N>().perform(*libtensor_ptr());
926✔
468
}
926✔
469

470
namespace {
471

472
template <size_t R, size_t D, size_t N>
473
std::shared_ptr<Tensor> execute_diagonal(
11,282✔
474
      std::shared_ptr<const AdcMemory> adcmem_ptr,
475
      const std::vector<std::shared_ptr<const lt::letter>>& label_result,
476
      const std::vector<std::shared_ptr<const lt::letter>>& label_diag,
477
      const std::vector<std::shared_ptr<const lt::letter>>& label_expr,
478
      std::shared_ptr<ExpressionTree> expr, std::vector<AxisInfo> axes) {
479
  auto lthis    = expr->attach_letters<N>(label_expr);
11,282✔
480
  auto res      = lt::expr::diag(*label_diag[0], strip_safe<D>(label_diag), lthis);
11,282✔
481
  auto expr_ptr = std::make_shared<ExpressionTree>(
11,282✔
482
        res.get_expr(), extract_expr_permutation(res, label_result), expr->keepalives);
11,282✔
483
  return std::make_shared<TensorImpl<R>>(adcmem_ptr, axes, expr_ptr);
22,564✔
484
}
11,282✔
485

486
}  // namespace
487

488
template <size_t N>
489
std::shared_ptr<Tensor> TensorImpl<N>::diagonal(std::vector<size_t> axes) {
11,282✔
490
  if (axes.size() <= 1) {
11,282✔
491
    throw invalid_argument("Axes needs to have at least two entries.");
×
492
  }
493
  auto label                                = make_label(N);
11,282✔
494
  std::shared_ptr<ExpressionTree> expr_this = expression_ptr();
11,282✔
495

496
  std::vector<std::shared_ptr<const lt::letter>> diag;
11,282✔
497
  std::unique_ptr<AxisInfo> diagaxis_ptr;
11,282✔
498
  std::vector<size_t> used_indices;
11,282✔
499
  for (size_t& i : axes) {
33,846✔
500
    auto it = std::find(used_indices.begin(), used_indices.end(), i);
22,564✔
501
    if (it != used_indices.end()) {
22,564✔
502
      throw invalid_argument("Axes may not have repeated indices.");
×
503
    }
504
    if (i >= N) {
22,564✔
505
      throw invalid_argument("Axis index (== " + std::to_string(i) +
×
506
                             ") goes beyond dimensionality of tensor (" +
507
                             std::to_string(N) + ")");
508
    }
509
    if (diagaxis_ptr == nullptr) {
22,564✔
510
      diagaxis_ptr.reset(new AxisInfo(m_axes[i]));
11,282✔
511
    } else {
512
      if (*diagaxis_ptr != m_axes[i]) {
11,282✔
513
        throw invalid_argument("Cannot form diagonal over differing axes. " +
×
514
                               diagaxis_ptr->label + " versus " + m_axes[i].label + ".");
×
515
      }
516
    }
517

518
    diag.push_back(label[i]);
22,564✔
519
    used_indices.push_back(i);
22,564✔
520
  }
521

522
  // Collect letters, which are to be left unchanged.
523
  std::vector<AxisInfo> axes_result;
11,282✔
524
  std::vector<std::shared_ptr<const lt::letter>> label_result;
11,282✔
525
  for (size_t i = 0; i < N; ++i) {
41,049✔
526
    auto it = std::find(used_indices.begin(), used_indices.end(), i);
29,767✔
527
    if (it == used_indices.end()) {
29,767✔
528
      label_result.push_back(label[i]);
7,203✔
529
      axes_result.push_back(m_axes[i]);
7,203✔
530
    }
531
  }
532
  label_result.push_back(diag[0]);
11,282✔
533
  axes_result.push_back(*diagaxis_ptr);
11,282✔
534

535
#define IF_MATCHES_EXECUTE(NTHIS, DIAG)                                            \
536
  if (N == NTHIS && DIAG == diag.size()) {                                         \
537
    constexpr size_t DIMOUT = NTHIS - DIAG + 1;                                    \
538
    static_assert((DIMOUT > 0 && DIMOUT < 100),                                    \
539
                  "Internal error with DIMOUT computation");                       \
540
    return execute_diagonal<DIMOUT, DIAG, NTHIS>(m_adcmem_ptr, label_result, diag, \
541
                                                 label, expr_this, axes_result);   \
542
  }
543

544
  IF_MATCHES_EXECUTE(2, 2)  //
6,470✔
545
  IF_MATCHES_EXECUTE(3, 2)  //
2,421✔
546
  IF_MATCHES_EXECUTE(3, 3)  //
×
547
  IF_MATCHES_EXECUTE(4, 2)  //
2,391✔
548
  IF_MATCHES_EXECUTE(4, 3)  //
×
549
  IF_MATCHES_EXECUTE(4, 4)  //
×
550
  IF_MATCHES_EXECUTE(5, 2)  //
×
551
  IF_MATCHES_EXECUTE(5, 3)  //
×
552
  IF_MATCHES_EXECUTE(5, 4)  //
×
553
  IF_MATCHES_EXECUTE(5, 5)  //
×
554
  IF_MATCHES_EXECUTE(6, 2)  //
×
555
  IF_MATCHES_EXECUTE(6, 3)  //
×
556
  IF_MATCHES_EXECUTE(6, 4)  //
×
557
  IF_MATCHES_EXECUTE(6, 5)  //
×
558
  IF_MATCHES_EXECUTE(6, 6)  //
×
559

560
  throw not_implemented_error("diagonal not implemented for dimensionality " +
×
561
                              std::to_string(N) + " and " + std::to_string(diag.size()) +
562
                              " axes indices.");
563

564
#undef IF_MATCHES_EXECUTE
565
}
11,282✔
566

567
template <size_t N>
568
std::shared_ptr<Tensor> TensorImpl<N>::scale(scalar_type c) const {
308,445✔
569
  // Collect labelled expressions
570
  std::vector<std::shared_ptr<const lt::letter>> label = make_label(N);
308,445✔
571
  std::shared_ptr<ExpressionTree> expr_this            = expression_ptr();
308,445✔
572
  auto lthis = expr_this->attach_letters<N>(label);
308,445✔
573

574
  // Execute the operation
575
  auto scaled = c * lthis;
308,445✔
576

577
  auto expr_ptr = std::make_shared<ExpressionTree>(
308,445✔
578
        scaled.get_expr(), extract_expr_permutation(scaled, label),
579
        expr_this->keepalives);
308,445✔
580
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, expr_ptr);
616,890✔
581
}
308,445✔
582

583
template <size_t N>
584
std::shared_ptr<Tensor> TensorImpl<N>::add(std::shared_ptr<Tensor> other) const {
196,753✔
585
  DIMENSIONALITY_CHECK(other);
196,753✔
586

587
  // Collect labelled expressions
588
  auto label                                 = make_label(N);
196,751✔
589
  std::shared_ptr<ExpressionTree> expr_this  = expression_ptr();
196,751✔
590
  std::shared_ptr<ExpressionTree> expr_other = as_expression(other);
196,751✔
591
  auto lthis                                 = expr_this->attach_letters<N>(label);
196,751✔
592
  auto lother                                = expr_other->attach_letters<N>(label);
196,751✔
593

594
  // Execute the operation
595
  auto sum = lthis + lother;
196,751✔
596

597
  auto expr_ptr = std::make_shared<ExpressionTree>(
196,751✔
598
        sum.get_expr(), extract_expr_permutation(sum, label),
599
        merge(expr_this->keepalives, expr_other->keepalives));
196,751✔
600
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, expr_ptr);
393,502✔
601
}
196,751✔
602

603
template <size_t N>
604
void TensorImpl<N>::add_linear_combination(
40,857✔
605
      std::vector<scalar_type> scalars,
606
      std::vector<std::shared_ptr<Tensor>> tensors) const {
607
  if (scalars.size() != tensors.size()) {
40,857✔
608
    throw dimension_mismatch(
×
609
          "std::vector of scalars has size " + std::to_string(scalars.size()) +
610
          ", but passed vector of tensors has size " + std::to_string(tensors.size()));
611
  }
612
  if (scalars.size() == 0) return;
40,857✔
613

614
  std::unique_ptr<lt::btod_add<N>> operator_ptr;
40,857✔
615
  for (size_t i = 0; i < scalars.size(); ++i) {
1,093,190✔
616
    DIMENSIONALITY_CHECK(tensors[i]);
1,052,333✔
617
    if (!operator_ptr) {
1,052,333✔
618
      operator_ptr.reset(new lt::btod_add<N>(as_btensor<N>(tensors[i]), scalars[i]));
40,857✔
619
    } else {
620
      operator_ptr->add_op(as_btensor<N>(tensors[i]), scalars[i]);
1,011,476✔
621
    }
622
  }
623
  operator_ptr->perform(*libtensor_ptr(), 1.0);
40,857✔
624
}
40,857✔
625

626
template <size_t N>
627
std::shared_ptr<Tensor> TensorImpl<N>::multiply(std::shared_ptr<Tensor> other) const {
1,718✔
628
  DIMENSIONALITY_CHECK(other);
1,718✔
629

630
  // Collect labelled expressions
631
  auto label                                 = make_label(N);
1,718✔
632
  std::shared_ptr<ExpressionTree> expr_this  = expression_ptr();
1,718✔
633
  std::shared_ptr<ExpressionTree> expr_other = as_expression(other);
1,718✔
634
  auto lthis                                 = expr_this->attach_letters<N>(label);
1,718✔
635
  auto lother                                = expr_other->attach_letters<N>(label);
1,718✔
636

637
  // Execute the operation
638
  auto mult = lt::expr::mult(lthis, lother);
1,718✔
639

640
  auto expr_ptr = std::make_shared<ExpressionTree>(
1,718✔
641
        mult.get_expr(), extract_expr_permutation(mult, label),
642
        merge(expr_this->keepalives, expr_other->keepalives));
1,718✔
643
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, expr_ptr);
3,436✔
644
}
1,718✔
645

646
template <size_t N>
647
std::shared_ptr<Tensor> TensorImpl<N>::divide(std::shared_ptr<Tensor> other) const {
19,816✔
648
  DIMENSIONALITY_CHECK(other);
19,816✔
649

650
  // Collect labelled expressions
651
  auto label                                 = make_label(N);
19,816✔
652
  std::shared_ptr<ExpressionTree> expr_this  = expression_ptr();
19,816✔
653
  std::shared_ptr<ExpressionTree> expr_other = as_expression(other);
19,816✔
654
  auto lthis                                 = expr_this->attach_letters<N>(label);
19,816✔
655
  auto lother                                = expr_other->attach_letters<N>(label);
19,816✔
656

657
  // Execute the operation
658
  auto div = lt::expr::div(lthis, lother);
19,816✔
659

660
  auto expr_ptr = std::make_shared<ExpressionTree>(
19,816✔
661
        div.get_expr(), extract_expr_permutation(div, label),
662
        merge(expr_this->keepalives, expr_other->keepalives));
19,816✔
663
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, expr_ptr);
39,632✔
664
}
19,816✔
665

666
template <size_t N>
667
std::shared_ptr<Tensor> TensorImpl<N>::copy() const {
6,531✔
668
  if (needs_evaluation()) {
6,531✔
669
    // Return deep copy to the expression
670
    return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, m_expr_ptr);
2,702✔
671
  } else {
672
    // Actually make a deep copy of the tensor
673
    auto ret_ptr = empty_like();
3,829✔
674
    auto lt_ptr  = std::static_pointer_cast<TensorImpl<N>>(ret_ptr)->libtensor_ptr();
3,829✔
675
    lt::btod_copy<N>(*libtensor_ptr()).perform(*lt_ptr);
3,829✔
676
    return ret_ptr;
3,829✔
677
  }
3,829✔
678
}
679

680
template <size_t N>
681
std::shared_ptr<Tensor> TensorImpl<N>::transpose(std::vector<size_t> permutation) const {
258,895✔
682
  if (permutation.size() != N) {
258,895✔
683
    throw invalid_argument(
×
684
          "Number of indices in provided transposition axes (== " +
685
          std::to_string(permutation.size()) +
686
          ") does not agree with tensor dimension (== " + std::to_string(N) + ").");
687
  }
688

689
  // Reorder the axes
690
  std::vector<AxisInfo> newaxes;
258,895✔
691
  for (size_t i = 0; i < N; ++i) {
957,053✔
692
    for (size_t j = 0; j < i; ++j) {
1,408,001✔
693
      if (permutation[i] == permutation[j]) {
709,843✔
694
        throw invalid_argument("Duplicate index in transposition axes (" +
×
695
                               std::to_string(permutation[i]) + ") at indices " +
×
696
                               std::to_string(i) + " and " + std::to_string(j) + ".");
697
      }
698
    }
699
    if (permutation[i] >= N) {
698,158✔
700
      throw invalid_argument("Invalid axes specifier " + std::to_string(permutation[i]) +
×
701
                             ". Exceeds tensor dimension -1 (==" + std::to_string(N - 1) +
702
                             ").");
703
    }
704

705
    newaxes.push_back(m_axes[permutation[i]]);
698,158✔
706
  }
707

708
  // Chain permutations
709
  std::shared_ptr<ExpressionTree> expr_this = expression_ptr();
258,895✔
710
  std::vector<size_t> result_permutation;
258,895✔
711
  for (size_t i = 0; i < N; ++i) {
957,053✔
712
    result_permutation.push_back(expr_this->permutation[permutation[i]]);
698,158✔
713
  }
714
  auto expr_ptr = std::make_shared<ExpressionTree>(
258,895✔
715
        *expr_this->tree_ptr, result_permutation, expr_this->keepalives);
258,895✔
716
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, newaxes, expr_ptr);
517,790✔
717
}
258,895✔
718

719
namespace {
720
template <size_t R, size_t N, size_t M>
721
std::shared_ptr<Tensor> execute_direct_sum(
5,813✔
722
      std::shared_ptr<const AdcMemory> adcmem_ptr,
723
      const std::vector<std::shared_ptr<const lt::letter>>& label_result,
724
      const std::vector<std::shared_ptr<const lt::letter>>& label_first,
725
      const std::vector<std::shared_ptr<const lt::letter>>& label_second,
726
      std::shared_ptr<ExpressionTree> expr_first,
727
      std::shared_ptr<ExpressionTree> expr_second, std::vector<AxisInfo> axes_result) {
728
  auto lfirst  = expr_first->attach_letters<N>(label_first);
5,813✔
729
  auto lsecond = expr_second->attach_letters<M>(label_second);
5,813✔
730

731
  // Execute the operation (dirsum)
732
  auto res      = lt::dirsum(lfirst, lsecond);
5,813✔
733
  auto expr_ptr = std::make_shared<ExpressionTree>(
5,813✔
734
        res.get_expr(), extract_expr_permutation(res, label_result),
735
        merge(expr_first->keepalives, expr_second->keepalives));
5,813✔
736
  return std::make_shared<TensorImpl<R>>(adcmem_ptr, axes_result, expr_ptr);
11,626✔
737
}
5,813✔
738
}  // namespace
739

740
template <size_t N>
741
std::shared_ptr<Tensor> TensorImpl<N>::direct_sum(std::shared_ptr<Tensor> other) const {
5,813✔
742
  typedef std::vector<std::shared_ptr<const lt::letter>> lalvec_t;
743
  lalvec_t label_first  = make_label(N);
5,813✔
744
  lalvec_t label_second = make_label(other->ndim());
5,813✔
745

746
  lalvec_t label_result;
5,813✔
747
  for (auto& v : label_first) label_result.push_back(v);
15,175✔
748
  for (auto& v : label_second) label_result.push_back(v);
13,718✔
749
  std::vector<AxisInfo> axes_result;
5,813✔
750
  for (auto& ax : m_axes) axes_result.push_back(ax);
15,175✔
751
  for (auto& ax : other->axes()) axes_result.push_back(ax);
13,718✔
752

753
  std::shared_ptr<ExpressionTree> expr_first  = expression_ptr();
5,813✔
754
  std::shared_ptr<ExpressionTree> expr_second = as_expression(other);
5,813✔
755
#define IF_MATCHES_EXECUTE(DIMA, DIMB)                                                   \
756
  if (DIMA == label_first.size() && DIMB == label_second.size()) {                       \
757
    constexpr size_t DIMOUT = DIMA + DIMB;                                               \
758
    static_assert((DIMOUT > 0 && DIMOUT < 100),                                          \
759
                  "Internal error with DIMOUT computation");                             \
760
    if (DIMOUT != label_result.size()) {                                                 \
761
      throw runtime_error(                                                               \
762
            "Internal error: Inconsistency with DIMOUT and label_contracted.size()");    \
763
    }                                                                                    \
764
    return execute_direct_sum<DIMOUT, DIMA, DIMB>(m_adcmem_ptr, label_result,            \
765
                                                  label_first, label_second, expr_first, \
766
                                                  expr_second, axes_result);             \
767
  }
768

769
  IF_MATCHES_EXECUTE(1, 1)  //
5,813✔
770
  IF_MATCHES_EXECUTE(1, 2)  //
3,061✔
771
  IF_MATCHES_EXECUTE(1, 3)  //
3,060✔
772
  IF_MATCHES_EXECUTE(1, 4)  //
3,060✔
773
  IF_MATCHES_EXECUTE(1, 5)  //
3,060✔
774
  IF_MATCHES_EXECUTE(2, 1)  //
3,060✔
775
  IF_MATCHES_EXECUTE(2, 2)  //
2,572✔
776
  IF_MATCHES_EXECUTE(2, 3)  //
493✔
777
  IF_MATCHES_EXECUTE(2, 4)  //
493✔
778
  IF_MATCHES_EXECUTE(3, 1)  //
489✔
779
  IF_MATCHES_EXECUTE(3, 2)  //
×
780
  IF_MATCHES_EXECUTE(3, 3)  //
×
781
  IF_MATCHES_EXECUTE(4, 1)  //
×
782
  IF_MATCHES_EXECUTE(4, 2)  //
×
783
  IF_MATCHES_EXECUTE(5, 1)  //
×
784

785
  throw not_implemented_error(
×
786
        "Did not implement the case of a direct_sum of two tensors of dimension " +
787
        std::to_string(ndim()) + " and " + std::to_string(other->ndim()) + ".");
788
#undef IF_MATCHES_EXECUTE
789
}
5,813✔
790

791
template <>
792
double TensorImpl<1>::trace(std::string) const {
×
793
  throw runtime_error("Trace can only be applied to tensors of even rank.");
×
794
}
795

796
template <>
797
double TensorImpl<3>::trace(std::string) const {
×
798
  throw runtime_error("Trace can only be applied to tensors of even rank.");
×
799
}
800

801
template <size_t N>
802
double TensorImpl<N>::trace(std::string contraction) const {
5✔
803
  if (contraction.size() != N) {
5✔
804
    throw invalid_argument(
×
805
          "Number of passed contraction indices needs to match tensor dimensionality.");
806
  }
807

808
  std::vector<std::pair<size_t, size_t>> trace_pairs;
5✔
809
  std::vector<bool> index_done(N, false);
5✔
810
  for (size_t i = 0; i < N; ++i) {
23✔
811
    if (index_done[i]) continue;
18✔
812
    index_done[i]   = true;
9✔
813
    bool found_pair = false;
9✔
814
    for (size_t j = i + 1; j < N; ++j) {
15✔
815
      if (contraction[i] == contraction[j]) {
15✔
816
        if (m_axes[i] != m_axes[j]) {
9✔
817
          throw invalid_argument("Axes to be traced along do not agree: " +
×
818
                                 m_axes[i].label + " versus " + m_axes[j].label);
×
819
        }
820
        index_done[j] = true;
9✔
821
        trace_pairs.push_back({i, j});
9✔
822
        found_pair = true;
9✔
823
        break;
9✔
824
      }
825
    }
826
    if (!found_pair) {
9✔
827
      throw("Found no matching second index for '" + std::string(1, contraction[i]) +
×
828
            "'.");
829
    }
830
  }
831

832
  if (2 * trace_pairs.size() != N) {
5✔
833
    throw invalid_argument(
×
834
          "Expected to find half as many trace indices as there are tensor dimensions, "
835
          "i.e. " +
836
          std::to_string(N / 2) + " indices and not " +
837
          std::to_string(trace_pairs.size()) + ".");
838
  }
839

840
  typedef std::vector<std::shared_ptr<const lt::letter>> lalvec_t;
841
  lalvec_t label = make_label(N);
5✔
842
  lalvec_t tlal_first;
5✔
843
  lalvec_t tlal_second;
5✔
844
  for (const auto& p : trace_pairs) {
14✔
845
    tlal_first.push_back(label[p.first]);
9✔
846
    tlal_second.push_back(label[p.second]);
9✔
847
  }
848

849
  std::shared_ptr<ExpressionTree> expr_this = expression_ptr();
5✔
850
  auto lfirst                               = expr_this->attach_letters<N>(label);
5✔
851
  constexpr size_t K                        = N / 2;
5✔
852
  return lt::trace(strip_safe<K>(tlal_first), strip_safe<K>(tlal_second), lfirst);
10✔
853
}
5✔
854

855
namespace {
856
template <size_t R, size_t K, size_t N, size_t M>
857
TensorOrScalar execute_tensordot_contract(
233,199✔
858
      std::shared_ptr<const AdcMemory> adcmem_ptr,
859
      const std::vector<std::shared_ptr<const lt::letter>>& label_result,
860
      const std::vector<std::shared_ptr<const lt::letter>>& label_contracted,
861
      const std::vector<std::shared_ptr<const lt::letter>>& label_first,
862
      const std::vector<std::shared_ptr<const lt::letter>>& label_second,
863
      std::shared_ptr<ExpressionTree> expr_first,
864
      std::shared_ptr<ExpressionTree> expr_second, std::vector<AxisInfo> axes_result) {
865
  // Build labelled expressions:
866
  auto lfirst  = expr_first->attach_letters<N>(label_first);
233,199✔
867
  auto lsecond = expr_second->attach_letters<M>(label_second);
233,199✔
868

869
  // Execute the operation (contract)
870
  auto res      = lt::contract(strip_safe<K>(label_contracted), lfirst, lsecond);
233,199✔
871
  auto expr_ptr = std::make_shared<ExpressionTree>(
233,199✔
872
        res.get_expr(), extract_expr_permutation(res, label_result),
873
        merge(expr_first->keepalives, expr_second->keepalives));
233,199✔
874
  auto tensor_ptr = std::make_shared<TensorImpl<R>>(adcmem_ptr, axes_result, expr_ptr);
233,199✔
875
  return TensorOrScalar{tensor_ptr, 0.0};
233,199✔
876
}
233,199✔
877

878
template <size_t R, size_t N, size_t M>
879
TensorOrScalar execute_tensordot_tensorprod(
2,742✔
880
      std::shared_ptr<const AdcMemory> adcmem_ptr,
881
      const std::vector<std::shared_ptr<const lt::letter>>& label_result,
882
      const std::vector<std::shared_ptr<const lt::letter>>& label_first,
883
      const std::vector<std::shared_ptr<const lt::letter>>& label_second,
884
      std::shared_ptr<ExpressionTree> expr_first,
885
      std::shared_ptr<ExpressionTree> expr_second, std::vector<AxisInfo> axes_result) {
886
  auto lfirst  = expr_first->attach_letters<N>(label_first);
2,742✔
887
  auto lsecond = expr_second->attach_letters<M>(label_second);
2,742✔
888

889
  // Execute the operation (tensor product)
890
  auto res      = lfirst * lsecond;
2,742✔
891
  auto expr_ptr = std::make_shared<ExpressionTree>(
2,742✔
892
        res.get_expr(), extract_expr_permutation(res, label_result),
893
        merge(expr_first->keepalives, expr_second->keepalives));
2,742✔
894
  auto tensor_ptr = std::make_shared<TensorImpl<R>>(adcmem_ptr, axes_result, expr_ptr);
2,742✔
895
  return TensorOrScalar{tensor_ptr, 0.0};
2,742✔
896
}
2,742✔
897

898
}  // namespace
899

900
template <size_t N>
901
TensorOrScalar TensorImpl<N>::tensordot(
236,394✔
902
      std::shared_ptr<Tensor> other,
903
      std::pair<std::vector<size_t>, std::vector<size_t>> axes) const {
904
  const std::vector<size_t>& axes_first  = axes.first;
236,394✔
905
  const std::vector<size_t>& axes_second = axes.second;
236,394✔
906

907
  if (axes_first.size() != axes_second.size()) {
236,394✔
908
    throw invalid_argument(
×
909
          "Length of the passed axes does not agree "
910
          " (first == " +
911
          shape_to_string(axes_first) + " and second == " + shape_to_string(axes_second) +
912
          ")");
913
  }
914
  if (axes_first.size() > N) {
236,394✔
915
    throw invalid_argument(
×
916
          "Length of the passed axes overshoots dimensionality of the first "
917
          "tensor.");
918
  }
919
  if (axes_first.size() > other->ndim()) {
236,394✔
920
    throw invalid_argument(
×
921
          "Length of the passed axes overshoots dimensionality of the second "
922
          "tensor.");
923
  }
924

925
  // Build label for first and second tensor and contraction indices
926
  typedef std::vector<std::shared_ptr<const lt::letter>> lalvec_t;
927
  lalvec_t label_first  = make_label(N);
236,394✔
928
  lalvec_t label_second = make_label(other->ndim());
236,394✔
929
  lalvec_t label_contracted;
236,394✔
930
  for (size_t i = 0; i < axes_first.size(); ++i) {
637,055✔
931
    std::shared_ptr<const lt::letter> l_contraction = label_first[axes_first[i]];
400,661✔
932
    label_second[axes_second[i]]                    = l_contraction;
400,661✔
933

934
    if (m_axes[axes_first[i]] != other->axes()[axes_second[i]]) {
400,661✔
935
      throw invalid_argument(
×
936
            "tensordot can only contract equivalent axes together. The " +
937
            std::to_string(i) + "-th axis clashes (" + m_axes[axes_first[i]].label +
×
938
            " versus " + other->axes()[axes_second[i]].label + "). Tensor spaces are " +
×
939
            space() + " and " + other->space());
940
    }
941
    label_contracted.push_back(l_contraction);
400,661✔
942
  }
943

944
  // Build labels of the result
945
  lalvec_t label_result;
236,394✔
946
  std::vector<AxisInfo> axes_result;
236,394✔
947
  for (size_t i = 0; i < N; ++i) {
921,944✔
948
    auto it = std::find(axes_first.begin(), axes_first.end(), i);
685,550✔
949
    if (it == axes_first.end()) {
685,550✔
950
      label_result.push_back(label_first[i]);
284,889✔
951
      axes_result.push_back(m_axes[i]);
284,889✔
952
    }
953
  }
954
  for (size_t j = 0; j < other->ndim(); ++j) {
1,006,059✔
955
    auto it = std::find(axes_second.begin(), axes_second.end(), j);
769,665✔
956
    if (it == axes_second.end()) {
769,665✔
957
      label_result.push_back(label_second[j]);
369,004✔
958
      axes_result.push_back(other->axes()[j]);
369,004✔
959
    }
960
  }
961

962
  if (label_result.size() != N + other->ndim() - 2 * label_contracted.size()) {
236,394✔
963
    throw runtime_error(
×
964
          "Internal error: Result index count does not agree with expected "
965
          "number.");
966
  }
967

968
  // Build labelled expressions:
969
  std::shared_ptr<ExpressionTree> expr_first  = expression_ptr();
236,394✔
970
  std::shared_ptr<ExpressionTree> expr_second = as_expression(other);
236,394✔
971

972
  // Branch into the different cases
973
  if (label_result.size() == 0 && N == label_contracted.size() &&
237,300✔
974
      N == label_first.size() && N == label_second.size()) {
237,300✔
975
    // Full contraction => execute dot_product
976

977
    auto lfirst  = expr_first->attach_letters<N>(label_first);
453✔
978
    auto lsecond = expr_second->attach_letters<N>(label_second);
453✔
979
    return TensorOrScalar{nullptr, lt::dot_product(lfirst, lsecond)};
906✔
980
  } else if (label_contracted.size() == 0) {
236,394✔
981
#define IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(DIMA, DIMB)                            \
982
  if (DIMA == label_first.size() && DIMB == label_second.size()) {                    \
983
    constexpr size_t DIMOUT = DIMB + DIMA;                                            \
984
    static_assert((DIMOUT > 0 && DIMOUT < 100),                                       \
985
                  "Internal error with DIMOUT computation");                          \
986
    if (DIMOUT != label_result.size()) {                                              \
987
      throw runtime_error(                                                            \
988
            "Internal error: Inconsistency with DIMOUT and label_contracted.size()"); \
989
    }                                                                                 \
990
    return execute_tensordot_tensorprod<DIMOUT, DIMA, DIMB>(                          \
991
          m_adcmem_ptr, label_result, label_first, label_second, expr_first,          \
992
          expr_second, axes_result);                                                  \
993
  }
994

995
    //
996
    // Instantiation generated from TensorImpl/instantiate_valid.py
997
    //
998
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(1, 1)  //
2,742✔
999
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(1, 2)  //
2,741✔
1000
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(1, 3)  //
2,741✔
1001
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(1, 4)  //
2,741✔
1002
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(1, 5)  //
2,741✔
1003
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(2, 1)  //
2,741✔
1004
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(2, 2)  //
2,707✔
1005
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(2, 3)  //
×
1006
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(2, 4)  //
×
1007
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(3, 1)  //
×
1008
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(3, 2)  //
×
1009
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(3, 3)  //
×
1010
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(4, 1)  //
×
1011
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(4, 2)  //
×
1012
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(5, 1)  //
×
1013

1014
#undef IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD
1015
  } else {
1016
    // Other cases => normal contraction
1017
#define IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(N_CONTR_IDCS, DIMA, DIMB)                \
1018
  if (DIMA == label_first.size() && DIMB == label_second.size() &&                    \
1019
      N_CONTR_IDCS == label_contracted.size()) {                                      \
1020
    constexpr size_t DIMOUT = DIMB + DIMA - N_CONTR_IDCS - N_CONTR_IDCS;              \
1021
    static_assert((DIMOUT > 0 && DIMOUT < 100),                                       \
1022
                  "Internal error with DIMOUT computation");                          \
1023
    if (DIMOUT != label_result.size()) {                                              \
1024
      throw runtime_error(                                                            \
1025
            "Internal error: Inconsistency with DIMOUT and label_contracted.size()"); \
1026
    }                                                                                 \
1027
    return execute_tensordot_contract<DIMOUT, N_CONTR_IDCS, DIMA, DIMB>(              \
1028
          m_adcmem_ptr, label_result, label_contracted, label_first, label_second,    \
1029
          expr_first, expr_second, axes_result);                                      \
1030
  }
1031

1032
    //
1033
    // Instantiation generated from TensorImpl/instantiate_valid.py
1034
    //
1035
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 1, 2)  //
233,199✔
1036
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 1, 3)  //
233,198✔
1037
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 1, 4)  //
233,198✔
1038
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 1, 5)  //
233,198✔
1039
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 1, 6)  //
233,198✔
1040
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 1)  //
233,198✔
1041
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 2)  //
233,196✔
1042
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 3)  //
180,305✔
1043
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 4)  //
180,305✔
1044
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 5)  //
148,433✔
1045
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 6)  //
148,433✔
1046
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 3, 1)  //
148,433✔
1047
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 3, 2)  //
148,433✔
1048
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 3, 3)  //
148,431✔
1049
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 3, 4)  //
148,431✔
1050
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 3, 5)  //
148,431✔
1051
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 4, 1)  //
148,431✔
1052
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 4, 2)  //
148,431✔
1053
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 4, 3)  //
119,312✔
1054
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 4, 4)  //
119,312✔
1055
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 5, 1)  //
119,304✔
1056
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 5, 2)  //
119,304✔
1057
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 5, 3)  //
119,304✔
1058
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 6, 1)  //
119,304✔
1059
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 6, 2)  //
119,304✔
1060
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 2, 3)  //
119,304✔
1061
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 2, 4)  //
119,304✔
1062
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 2, 5)  //
76,868✔
1063
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 2, 6)  //
76,868✔
1064
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 3, 2)  //
76,868✔
1065
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 3, 3)  //
76,868✔
1066
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 3, 4)  //
76,868✔
1067
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 3, 5)  //
76,868✔
1068
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 3, 6)  //
76,868✔
1069
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 4, 2)  //
76,868✔
1070
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 4, 3)  //
73,755✔
1071
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 4, 4)  //
73,755✔
1072
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 4, 5)  //
46,480✔
1073
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 4, 6)  //
46,480✔
1074
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 5, 2)  //
46,480✔
1075
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 5, 3)  //
46,480✔
1076
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 5, 4)  //
46,480✔
1077
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 5, 5)  //
46,480✔
1078
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 6, 2)  //
46,480✔
1079
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 6, 3)  //
46,480✔
1080
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 6, 4)  //
46,480✔
1081
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 3, 4)  //
46,480✔
1082
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 3, 5)  //
46,480✔
1083
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 3, 6)  //
46,480✔
1084
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 4, 3)  //
46,480✔
1085
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 4, 4)  //
46,480✔
1086
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 4, 5)  //
×
1087
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 4, 6)  //
×
1088
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 5, 3)  //
×
1089
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 5, 4)  //
×
1090
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 5, 5)  //
×
1091
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 5, 6)  //
×
1092
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 6, 3)  //
×
1093
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 6, 4)  //
×
1094
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 6, 5)  //
×
1095
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 6, 6)  //
×
1096
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 4, 5)  //
×
1097
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 4, 6)  //
×
1098
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 5, 4)  //
×
1099
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 5, 5)  //
×
1100
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 5, 6)  //
×
1101
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 6, 4)  //
×
1102
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 6, 5)  //
×
1103
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 6, 6)  //
×
1104
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(5, 5, 6)  //
×
1105
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(5, 6, 5)  //
×
1106
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(5, 6, 6)  //
×
1107

1108
#undef IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT
1109
  }
1110

1111
  throw not_implemented_error(
×
1112
        "Did not implement the case of a tensordot over " +
1113
        std::to_string(label_contracted.size()) +
1114
        " indices for two tensors of dimensions " + std::to_string(label_first.size()) +
1115
        " and " + std::to_string(label_second.size()) +
1116
        ", yielding a tensor of dimension " + std::to_string(label_result.size()) + ".");
1117
}
236,847✔
1118

1119
template <size_t N>
1120
std::vector<scalar_type> TensorImpl<N>::dot(
433,411✔
1121
      std::vector<std::shared_ptr<Tensor>> tensors) const {
1122
  std::vector<scalar_type> ret(tensors.size(), 0.0);
433,411✔
1123

1124
  for (size_t i = 0; i < tensors.size(); ++i) {
1,351,864✔
1125
    auto tensor_ptr = std::static_pointer_cast<TensorImpl<N>>(tensors[i]);
918,453✔
1126
    if (ndim() != tensor_ptr->ndim()) {
918,453✔
1127
      throw dimension_mismatch(
×
1128
            "Dimensionality of this tensor (" + std::to_string(ndim()) +
1129
            ") does not agree with the dimensionality of the " + std::to_string(i) +
1130
            "-th tensor passed, which has dimensionality " +
1131
            std::to_string(tensor_ptr->ndim()) + ".");
×
1132
    }
1133
    if (shape() != tensor_ptr->shape()) {
918,453✔
1134
      throw dimension_mismatch("Shape of this tensor (" + shape_to_string(shape()) +
×
1135
                               ") does not agree with the shape of the " +
1136
                               std::to_string(i) + "-th tensor passed, which has shape " +
1137
                               shape_to_string(tensor_ptr->shape()) + ".");
×
1138
    }
1139

1140
    ret[i] = lt::btod_dotprod<N>(*libtensor_ptr(), *(tensor_ptr->libtensor_ptr()))
1,836,906✔
1141
                   .calculate();
918,453✔
1142
  }
1143
  return ret;
433,411✔
1144
}
×
1145

1146
template <size_t N>
1147
std::shared_ptr<Tensor> TensorImpl<N>::symmetrise(
13,111✔
1148
      const std::vector<std::vector<size_t>>& permutations) const {
1149
  if (permutations.size() == 0) {
13,111✔
1150
    return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, m_libtensor_ptr,
×
1151
                                           m_expr_ptr);  // Noop
×
1152
  }
1153

1154
  // Label this expression
1155
  auto label                                = make_label(N);
13,111✔
1156
  std::shared_ptr<ExpressionTree> expr_this = expression_ptr();
13,111✔
1157
  auto lthis                                = expr_this->attach_letters<N>(label);
13,111✔
1158

1159
  // Execute the operation
1160
  auto symmetrised = [&permutations, &lthis, this, label]() {
26,222✔
1161
    // this assumes that all permutations have the same length
1162
    // but the parse functions throw if this is not the case
1163
    const auto perm_size = permutations.front().size();
13,111✔
1164
    if (perm_size == 2) {
13,111✔
1165
      if (permutations.size() == 1) {
13,102✔
1166
        const auto parsed =
×
1167
              parse_permutation<2, 1>(m_axes, strip_safe<N>(label), permutations);
5,495✔
1168
        return 0.5 * lt::expr::symm(std::get<0>(parsed), std::get<1>(parsed), lthis);
10,990✔
1169
      } else if (permutations.size() == 2) {
7,607✔
1170
        const auto parsed =
×
1171
              parse_permutation<2, 2>(m_axes, strip_safe<N>(label), permutations);
7,607✔
1172
        return 0.5 * lt::expr::symm(std::get<0>(parsed), std::get<1>(parsed), lthis);
15,214✔
1173
      } else {
1174
        throw runtime_error(
×
1175
              "Symmetrisation not implemented for more than two index pairs.");
1176
      }
1177
    } else if (perm_size == 3) {
9✔
1178
      if (permutations.size() == 1) {
9✔
1179
        const auto parsed =
×
1180
              parse_permutation<3, 1>(m_axes, strip_safe<N>(label), permutations);
9✔
1181
        return 1. / 6. *
×
1182
               lt::expr::symm(std::get<0>(parsed), std::get<1>(parsed),
18✔
1183
                              std::get<2>(parsed), lthis);
36✔
1184
      } else {
1185
        throw runtime_error(
×
1186
              "Symmetrisation not implemented for more than one index triple.");
1187
      }
1188
    } else {
1189
      throw runtime_error(
×
1190
            "Symmetrisation only implemented for permutations of length 2 and 3.");
1191
    }
1192
  }();
1193

1194
  auto expr_ptr = std::make_shared<ExpressionTree>(
13,111✔
1195
        symmetrised.get_expr(), extract_expr_permutation(symmetrised, label),
1196
        expr_this->keepalives);
13,111✔
1197
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, expr_ptr);
13,111✔
1198
}
13,111✔
1199

1200
template <size_t N>
1201
std::shared_ptr<Tensor> TensorImpl<N>::antisymmetrise(
64,380✔
1202
      const std::vector<std::vector<size_t>>& permutations) const {
1203
  if (permutations.size() == 0) {
64,380✔
1204
    return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, m_libtensor_ptr,
×
1205
                                           m_expr_ptr);  // Noop
×
1206
  }
1207

1208
  // Label this expression
1209
  auto label                                = make_label(N);
64,380✔
1210
  std::shared_ptr<ExpressionTree> expr_this = expression_ptr();
64,380✔
1211
  auto lthis                                = expr_this->attach_letters<N>(label);
64,380✔
1212

1213
  // Execute the operation
1214
  auto antisymmetrised = [&permutations, &lthis, this, &label]() {
128,760✔
1215
    // this assumes that all permutations have the same length
1216
    // but the parse functions throw if this is not the case
1217
    const auto perm_size = permutations.front().size();
64,380✔
1218
    if (perm_size == 2) {
64,380✔
1219
      if (permutations.size() == 1) {
64,371✔
1220
        const auto parsed =
64,370✔
1221
              parse_permutation<2, 1>(m_axes, strip_safe<N>(label), permutations);
64,370✔
1222
        return 0.5 * lt::expr::asymm(std::get<0>(parsed), std::get<1>(parsed), lthis);
128,740✔
1223
      } else if (permutations.size() == 2) {
1✔
1224
        const auto parsed =
1✔
1225
              parse_permutation<2, 2>(m_axes, strip_safe<N>(label), permutations);
1✔
1226
        return 0.5 * lt::expr::asymm(std::get<0>(parsed), std::get<1>(parsed), lthis);
2✔
1227
      } else {
1228
        throw runtime_error(
×
1229
              "Antisymmetrisation not implemented for more than two index pairs.");
1230
      }
1231
    } else if (perm_size == 3) {
9✔
1232
      if (permutations.size() == 1) {
9✔
1233
        const auto parsed =
9✔
1234
              parse_permutation<3, 1>(m_axes, strip_safe<N>(label), permutations);
9✔
1235
        return 1. / 6. *
×
1236
               lt::expr::asymm(std::get<0>(parsed), std::get<1>(parsed),
18✔
1237
                               std::get<2>(parsed), lthis);
36✔
1238
      } else {
1239
        throw runtime_error(
×
1240
              "Antisymmetrisation not implemented for more than one index triple.");
1241
      }
1242
    } else {
1243
      throw runtime_error(
×
1244
            "Antisymmetrisation only implemented for permutations of length 2 and 3.");
1245
    }
1246
  }();
1247

1248
  auto expr_ptr = std::make_shared<ExpressionTree>(
64,380✔
1249
        antisymmetrised.get_expr(), extract_expr_permutation(antisymmetrised, label),
1250
        expr_this->keepalives);
64,380✔
1251
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, expr_ptr);
64,380✔
1252
}
64,380✔
1253

1254
template <size_t N>
1255
void TensorImpl<N>::set_element(const std::vector<size_t>& tidx, scalar_type value) {
14,928✔
1256
  if (!is_element_allowed(tidx)) {
14,928✔
1257
    throw runtime_error("Setting tensor index (" + shape_to_string(tidx) +
×
1258
                        ") not allowed, since zero by symmetry.");
1259
  }
1260

1261
  lt::index<N> block_idx;
14,928✔
1262
  lt::index<N> in_block_idx;
14,928✔
1263
  std::tie(block_idx, in_block_idx) = assert_convert_tensor_index(*libtensor_ptr(), tidx);
14,928✔
1264
  lt::btod_set_elem<N>{}.perform(*libtensor_ptr(), block_idx, in_block_idx, value);
14,928✔
1265
}
14,928✔
1266

1267
template <size_t N>
1268
scalar_type TensorImpl<N>::get_element(const std::vector<size_t>& tidx) const {
930,100✔
1269
  lt::index<N> block_idx;
930,100✔
1270
  lt::index<N> in_block_idx;
930,100✔
1271
  std::tie(block_idx, in_block_idx) = assert_convert_tensor_index(*libtensor_ptr(), tidx);
930,100✔
1272

1273
  // The value we want to return
1274
  scalar_type ret;
1275

1276
  // Make an orbit over the block index, i.e. find the canonical block
1277
  lt::block_tensor_ctrl<N, scalar_type> ctrl(*libtensor_ptr());
930,100✔
1278
  lt::dimensions<N> bidims(libtensor_ptr()->get_bis().get_block_index_dims());
930,100✔
1279
  lt::orbit<N, scalar_type> obit(ctrl.req_const_symmetry(), block_idx);
930,100✔
1280

1281
  // If the orbit (i.e. the index) is not allowed by symmetry, than the element is
1282
  // zero.
1283
  if (!obit.is_allowed()) return 0;
930,100✔
1284

1285
  // Absolute index of the canonical block
1286
  const lt::abs_index<N> idx_abscanon(obit.get_acindex(), bidims);
930,100✔
1287

1288
  // Check if we have hit a zero block, then return 0 as well.
1289
  if (ctrl.req_is_zero_block(idx_abscanon.get_index())) return 0;
930,100✔
1290

1291
  // The transformation between the selected block and the canonical block
1292
  const lt::tensor_transf<N, scalar_type>& tr = obit.get_transf(block_idx);
255,516✔
1293

1294
  // Transform in-block index.
1295
  lt::index<N> pibidx(in_block_idx);  //< permuted in-block index
255,516✔
1296
  pibidx.permute(tr.get_perm());
255,516✔
1297

1298
  // Get the actual value from the dense tensor representing the block
1299
  {
1300
    auto& block = ctrl.req_const_block(idx_abscanon.get_index());
255,516✔
1301
    lt::dense_tensor_rd_ctrl<N, scalar_type> blkctrl(block);
255,516✔
1302

1303
    const scalar_type* p = blkctrl.req_const_dataptr();
255,516✔
1304
    ret                  = p[lt::abs_index<N>(pibidx, block.get_dims()).get_abs_index()];
255,516✔
1305
    blkctrl.ret_const_dataptr(p);
255,516✔
1306
  }
255,516✔
1307

1308
  // Transform value
1309
  tr.get_scalar_tr().apply(ret);
255,516✔
1310

1311
  ctrl.ret_const_block(idx_abscanon.get_index());
255,516✔
1312
  return ret;
255,516✔
1313
}
930,100✔
1314

1315
template <size_t N>
1316
bool TensorImpl<N>::is_element_allowed(const std::vector<size_t>& tidx) const {
36,965✔
1317
  lt::index<N> block_idx;
36,965✔
1318
  std::tie(block_idx, std::ignore) = assert_convert_tensor_index(*libtensor_ptr(), tidx);
36,965✔
1319

1320
  // Check if the block is allowed in the symmetry of the guess
1321
  lt::block_tensor_ctrl<N, scalar_type> ctrl(*libtensor_ptr());
36,965✔
1322
  lt::orbit<N, scalar_type> orb(ctrl.req_const_symmetry(), block_idx);
36,965✔
1323
  const bool block_allowed = orb.is_allowed();
36,965✔
1324

1325
  // TODO Even if the block is allowed, the index might not be
1326
  // (e.g. diagonal of an anti-symmetric tensor).
1327
  const bool index_allowed = true;
36,965✔
1328

1329
  return block_allowed && index_allowed;
36,965✔
1330
}
36,965✔
1331

1332
template <size_t N>
1333
std::vector<std::pair<std::vector<size_t>, scalar_type>> TensorImpl<N>::select_n_absmax(
9✔
1334
      size_t n, bool unique_by_symmetry) const {
1335
  return execute_select_n<lt::compare4absmax>(*libtensor_ptr(), n, unique_by_symmetry);
18✔
1336
}
1337

1338
template <size_t N>
1339
std::vector<std::pair<std::vector<size_t>, scalar_type>> TensorImpl<N>::select_n_absmin(
9✔
1340
      size_t n, bool unique_by_symmetry) const {
1341
  return execute_select_n<lt::compare4absmin>(*libtensor_ptr(), n, unique_by_symmetry);
18✔
1342
}
1343

1344
template <size_t N>
1345
std::vector<std::pair<std::vector<size_t>, scalar_type>> TensorImpl<N>::select_n_max(
6✔
1346
      size_t n, bool unique_by_symmetry) const {
1347
  return execute_select_n<lt::compare4max>(*libtensor_ptr(), n, unique_by_symmetry);
12✔
1348
}
1349

1350
template <size_t N>
1351
std::vector<std::pair<std::vector<size_t>, scalar_type>> TensorImpl<N>::select_n_min(
1,495✔
1352
      size_t n, bool unique_by_symmetry) const {
1353
  return execute_select_n<lt::compare4min>(*libtensor_ptr(), n, unique_by_symmetry);
2,990✔
1354
}
1355

1356
template <size_t N>
1357
void TensorImpl<N>::export_to(scalar_type* memptr, size_t size) const {
30,137✔
1358
  if (this->size() != size) {
30,137✔
1359
    throw invalid_argument("The memory provided (== " + std::to_string(size) +
×
1360
                           ") does not agree with the number of tensor elements (== " +
1361
                           std::to_string(this->size()) + ")");
1362
  }
1363
  lt::btod_export<N>(*libtensor_ptr()).perform(memptr);
30,137✔
1364
}
30,137✔
1365

1366
template <size_t N>
1367
void TensorImpl<N>::export_block(const std::vector<size_t>& start,
3,162✔
1368
                                 const std::vector<size_t>& end, scalar_type* memptr,
1369
                                 size_t size) const {
1370
  if (start.size() != N || end.size() != N) {
3,162✔
NEW
1371
    throw invalid_argument("start and end must each have exactly " + std::to_string(N) +
×
1372
                           " entries.");
1373
  }
1374

1375
  // Compute requested sub-block extents and the row-major output strides.
1376
  std::array<size_t, N> ext;
1377
  std::array<size_t, N> out_strides;
1378
  size_t total = 1;
3,162✔
1379
  for (size_t i = 0; i < N; ++i) {
15,806✔
1380
    if (end[i] < start[i]) {
12,645✔
NEW
1381
      throw invalid_argument("end must not be smaller than start along any axis.");
×
1382
    }
1383
    if (end[i] > shape()[i]) {
12,645✔
1384
      throw invalid_argument("end exceeds the tensor shape along axis " +
1✔
1385
                             std::to_string(i) + ".");
1386
    }
1387
    ext[i] = end[i] - start[i];
12,644✔
1388
    total *= ext[i];
12,644✔
1389
  }
1390
  if (size != total) {
3,161✔
NEW
1391
    throw invalid_argument("The memory provided (== " + std::to_string(size) +
×
1392
                           ") does not agree with the requested sub-block size (== " +
1393
                           std::to_string(total) + ").");
1394
  }
1395
  if (total == 0) return;
3,161✔
1396

1397
  out_strides[N - 1] = 1;
3,159✔
1398
  for (size_t i = N - 1; i > 0; --i) {
12,636✔
1399
    out_strides[i - 1] = out_strides[i] * ext[i];
9,477✔
1400
  }
1401

1402
  // Default to zero: requested elements falling onto symmetry-forbidden or zero
1403
  // blocks must come out as zero.
1404
  std::fill(memptr, memptr + total, scalar_type(0));
3,159✔
1405

1406
  lt::block_tensor_ctrl<N, scalar_type> ctrl(*libtensor_ptr());
3,159✔
1407
  const lt::block_index_space<N>& bis = libtensor_ptr()->get_bis();
3,159✔
1408
  lt::dimensions<N> bidims(bis.get_block_index_dims());
3,159✔
1409

1410
  // This mirrors btod_export (the routine behind export_to / to_ndarray): for
1411
  // every non-zero canonical block, scatter the block data to all blocks of its
1412
  // symmetry orbit, applying the orbit permutation and scalar transform. The
1413
  // difference here is that we only write elements that fall inside the
1414
  // requested [start, end) window, and into a compact sub-block buffer.
1415
  lt::orbit_list<N, scalar_type> orbitlist(ctrl.req_const_symmetry());
3,159✔
1416
  lt::index<N> canon_idx;
3,159✔
1417
  for (auto oit = orbitlist.begin(); oit != orbitlist.end(); ++oit) {
28,328✔
1418
    orbitlist.get_index(oit, canon_idx);
14,656✔
1419
    if (ctrl.req_is_zero_block(canon_idx)) continue;
14,656✔
1420
    lt::orbit<N, scalar_type> obit(ctrl.req_const_symmetry(), canon_idx);
10,513✔
1421

1422
    auto& cblock = ctrl.req_const_block(canon_idx);
10,513✔
1423
    {
1424
      lt::dense_tensor_rd_ctrl<N, scalar_type> blkctrl(cblock);
10,513✔
1425
      const scalar_type* sptr           = blkctrl.req_const_dataptr();
10,513✔
1426
      const lt::dimensions<N> cblk_dims = cblock.get_dims();
10,513✔
1427

1428
      // Walk every block of the orbit (each is a destination block in the
1429
      // full dense tensor).
1430
      for (auto j = obit.begin(); j != obit.end(); ++j) {
40,645✔
1431
        lt::abs_index<N> aidx(obit.get_abs_index(j), bidims);
30,132✔
1432
        const lt::tensor_transf<N, scalar_type>& tr = obit.get_transf(j);
30,132✔
1433
        lt::index<N> dst_start(bis.get_block_start(aidx.get_index()));
30,132✔
1434
        lt::dimensions<N> dst_dims(bis.get_block_dims(aidx.get_index()));
30,132✔
1435

1436
        // Does this destination block intersect the requested window?
1437
        std::array<size_t, N> lo, hi;
1438
        bool empty = false;
30,132✔
1439
        for (size_t i = 0; i < N; ++i) {
63,688✔
1440
          const size_t blo = dst_start[i];
58,922✔
1441
          const size_t bhi = dst_start[i] + dst_dims[i];
58,922✔
1442
          lo[i]            = std::max(blo, start[i]);
58,922✔
1443
          hi[i]            = std::min(bhi, end[i]);
58,922✔
1444
          if (lo[i] >= hi[i]) {
58,922✔
1445
            empty = true;
25,366✔
1446
            break;
25,366✔
1447
          }
1448
        }
1449
        if (empty) continue;
30,132✔
1450

1451
        // Inverse permutation maps a destination (output) index back to the
1452
        // source (canonical block) index, exactly as btod_export::copy_block.
1453
        lt::permutation<N> inv_perm(tr.get_perm());
4,766✔
1454
        inv_perm.invert();
4,766✔
1455
        const scalar_type coeff = tr.get_scalar_tr().get_coeff();
4,766✔
1456

1457
        std::array<size_t, N> isect_ext;
1458
        size_t isect_total = 1;
4,766✔
1459
        for (size_t i = 0; i < N; ++i) {
23,830✔
1460
          isect_ext[i] = hi[i] - lo[i];
19,064✔
1461
          isect_total *= isect_ext[i];
19,064✔
1462
        }
1463

1464
        for (size_t flat = 0; flat < isect_total; ++flat) {
10,476,721✔
1465
          // Decode flat -> per-axis destination index within the intersection.
1466
          lt::index<N> dst_in_block;  // index within the destination block
10,471,955✔
1467
          size_t out_abs = 0;
10,471,955✔
1468
          for (size_t i = 0; i < N; ++i) {
52,359,775✔
1469
            size_t stride = 1;
41,887,820✔
1470
            for (size_t k = i + 1; k < N; ++k) stride *= isect_ext[k];
104,719,550✔
1471
            const size_t off    = (flat / stride) % isect_ext[i];
41,887,820✔
1472
            const size_t abs_ix = lo[i] + off;  // absolute tensor index on axis i
41,887,820✔
1473
            dst_in_block[i]     = abs_ix - dst_start[i];
41,887,820✔
1474
            out_abs += (abs_ix - start[i]) * out_strides[i];
41,887,820✔
1475
          }
1476

1477
          // Map destination in-block index to the source (canonical) in-block
1478
          // index via the inverse permutation.
1479
          lt::index<N> src_in_block(dst_in_block);
10,471,955✔
1480
          src_in_block.permute(inv_perm);
10,471,955✔
1481
          const size_t src_abs =
1482
                lt::abs_index<N>(src_in_block, cblk_dims).get_abs_index();
10,471,955✔
1483
          memptr[out_abs] = coeff * sptr[src_abs];
10,471,955✔
1484
        }
1485
      }
1486

1487
      blkctrl.ret_const_dataptr(sptr);
10,513✔
1488
    }
10,513✔
1489
    ctrl.ret_const_block(canon_idx);
10,513✔
1490
  }
1491
}
3,159✔
1492

1493
template <size_t N>
1494
void TensorImpl<N>::import_from(const scalar_type* memptr, size_t size,
4,864✔
1495
                                scalar_type tolerance, bool symmetry_check) {
1496
  if (this->size() != size) {
4,864✔
1497
    throw invalid_argument("The memory size provided (== " + std::to_string(size) +
×
1498
                           ") does not agree with the number of tensor elements (== " +
1499
                           std::to_string(this->size()) + ")");
1500
  }
1501

1502
  if (symmetry_check) {
4,864✔
1503
    // Slow algorithm with symmetry check (via libtensor)
1504

1505
    // Zero this memory.
1506
    // TODO Check this really needs to be done for proper functioning
1507
    //      There is some indication that yes, unfortunately.
1508
    lt::btod_set<N>(0.0).perform(*libtensor_ptr());
4,864✔
1509

1510
    scalar_type* noconst_mem = const_cast<scalar_type*>(memptr);
4,864✔
1511
    libtensor::btod_import_raw<N>(noconst_mem, libtensor_ptr()->get_bis().get_dims(),
9,728✔
1512
                                  tolerance)
1513
          .perform(*libtensor_ptr());
4,864✔
1514
  } else {
1515
    // Fast algorithm without symmetry check (via import_from with generator)
1516
    auto fast_importer = [this, memptr](
×
1517
                               const std::vector<std::pair<size_t, size_t>>& range,
1518
                               scalar_type* ptr) {
1519
      if (range.size() != N) {
×
1520
        throw runtime_error("Internal error: Dimension mismatch in fast_importer");
×
1521
      }
1522

1523
      // Strides for accessing the full memptr
1524
      std::array<size_t, N> strides_full;
1525
      strides_full[N - 1] = 1;
×
1526
      for (size_t idim = N - 1; idim > 0; --idim) {
×
1527
        strides_full[idim - 1] = strides_full[idim] * shape()[idim];
×
1528
      }
1529

1530
      // Dimensionalities for access into the small ptr
1531
      std::array<size_t, N> dim_ptr;
1532
      for (size_t idim = 0; idim != N; ++idim) {
×
1533
        dim_ptr[idim] = range[idim].second - range[idim].first;
×
1534
      }
1535

1536
      // Strides for accessing the small ptr
1537
      std::array<size_t, N> strides_ptr;
1538
      strides_ptr[N - 1] = 1;
×
1539
      for (size_t idim = N - 1; idim > 0; --idim) {
×
1540
        strides_ptr[idim - 1] = strides_ptr[idim] * dim_ptr[idim];
×
1541
      }
1542
      const size_t size_ptr = strides_ptr[0] * dim_ptr[0];
×
1543

1544
      for (size_t iabs = 0; iabs < size_ptr; ++iabs) {
×
1545
        size_t full_abs = 0;
×
1546
        for (size_t idim = 0; idim != N; ++idim) {
×
1547
          size_t idx = range[idim].first + (iabs / strides_ptr[idim]) % dim_ptr[idim];
×
1548
          full_abs += strides_full[idim] * idx;
×
1549
        }
1550
        ptr[iabs] = memptr[full_abs];
×
1551
      }
1552
    };
1553
    import_from(fast_importer, tolerance, false);
×
1554
  }
1555
}
4,864✔
1556

1557
template <size_t N>
1558
void TensorImpl<N>::import_from(
6,748✔
1559
      std::function<void(const std::vector<std::pair<size_t, size_t>>&, scalar_type*)>
1560
            generator,
1561
      scalar_type tolerance, bool symmetry_check) {
1562

1563
  if (symmetry_check) {
6,748✔
1564
    // Slow algorithm with symmetry check (via import_from with libtensor)
1565

1566
    // Get complete data via generator ...
1567
    std::vector<std::pair<size_t, size_t>> full_range(N);
×
1568
    for (size_t i = 0; i < N; ++i) {
×
1569
      full_range[i].first  = 0;
×
1570
      full_range[i].second = shape()[i];
×
1571
    }
1572
    std::vector<scalar_type> data(this->size());
×
1573
    generator(full_range, data.data());
×
1574

1575
    // ... and import it
1576
    import_from(data, tolerance, true);
×
1577
  } else {
×
1578
    // Fast algorithm by only considering canonical blocks
1579
    // First zero out the tensor completely.
1580
    lt::block_tensor_ctrl<N, scalar_type> ctrl(*libtensor_ptr());
6,748✔
1581
    ctrl.req_zero_all_blocks();
6,748✔
1582

1583
    // Now loop over all orbits, which thus gives access to all
1584
    // canonical blocks
1585
    lt::orbit_list<N, scalar_type> orbitlist(ctrl.req_const_symmetry());
6,748✔
1586
    lt::index<N> blk_idx;  // Holder for canonical-block indices
6,748✔
1587
    for (auto it = orbitlist.begin(); it != orbitlist.end(); ++it) {
31,030✔
1588
      orbitlist.get_index(it, blk_idx);
24,282✔
1589

1590
      // Get the dense tensor for the current canonical block
1591
      // and import the data from the generator
1592
      lt::dense_tensor_wr_i<N, scalar_type>& blk = ctrl.req_block(blk_idx);
24,282✔
1593

1594
      // Compute range to import
1595
      std::vector<std::pair<size_t, size_t>> range(N);
24,282✔
1596
      const lt::block_index_space<N>& bis = libtensor_ptr()->get_bis();
24,282✔
1597
      lt::index<N> blk_start(bis.get_block_start(blk_idx));
24,282✔
1598
      lt::dimensions<N> blk_dims(bis.get_block_dims(blk_idx));
24,282✔
1599
      for (size_t i = 0; i < N; ++i) {
105,006✔
1600
        range[i].first  = blk_start[i];
80,724✔
1601
        range[i].second = blk_start[i] + blk_dims[i];
80,724✔
1602
      }
1603

1604
      bool all_zero = false;
24,282✔
1605
      {
1606
        lt::dense_tensor_wr_ctrl<N, scalar_type> cblk(blk);
24,282✔
1607
        cblk.req_prefetch();
24,282✔
1608
        scalar_type* ptr = cblk.req_dataptr();
24,282✔
1609

1610
        generator(range, ptr);  // Import data from the generator
24,282✔
1611

1612
        if (tolerance > 0) {
24,282✔
1613
          // Check that some are non-zero
1614
          all_zero = std::all_of(
24,282✔
1615
                ptr, ptr + blk_dims.get_size(),
24,282✔
1616
                [tolerance](scalar_type e) { return std::abs(e) < tolerance; });
1,447,875✔
1617
        }
1618

1619
        cblk.ret_dataptr(ptr);
24,282✔
1620
      }
24,282✔
1621
      ctrl.ret_block(blk_idx);
24,282✔
1622

1623
      if (all_zero) {
24,282✔
1624
        ctrl.req_zero_block(blk_idx);
10,828✔
1625
      }
1626
    }
1627
  }
6,748✔
1628
}
6,748✔
1629

1630
template <size_t N>
1631
std::string TensorImpl<N>::describe_symmetry() const {
28✔
1632
  std::stringstream ss;
28✔
1633

1634
  lt::block_tensor_ctrl<N, scalar_type> ctrl(*libtensor_ptr());
28✔
1635
  ss << ctrl.req_const_symmetry();
28✔
1636

1637
  return ss.str();
56✔
1638
}
28✔
1639

1640
template <size_t N>
1641
std::string TensorImpl<N>::describe_expression(std::string stage) const {
×
1642
  if (needs_evaluation()) {
×
1643
    std::stringstream ss;
×
1644
    if (stage == "unoptimised") {
×
1645
      ss << *m_expr_ptr;
×
1646
    } else if (stage == "optimised") {
×
1647
      lt::expr::print_tree(m_expr_ptr->optimised_tree(), ss, 2);
×
1648
    } else if (stage == "evaluation") {
×
1649
      auto newtensor_ptr =
×
1650
            std::make_shared<lt::btensor<N, scalar_type>>(as_bispace<N>(m_axes));
×
1651
      lt::expr::expr_tree evaltree =
×
1652
            m_expr_ptr->evaluation_tree(*newtensor_ptr, /* add = */ false);
×
1653
      lt::expr::print_tree(evaltree, ss, 2);
×
1654
    } else {
×
1655
      throw invalid_argument("Stage " + stage +
×
1656
                             " not valid for describe_expression. Try 'unoptimised', "
1657
                             "'optimised', 'evaluation' or 'evaluation'");
1658
    }
1659
    return ss.str();
×
1660
  } else {
×
1661
    return "btensor of shape " + shape_to_string(m_shape);
×
1662
  }
1663
}
1664

1665
template <size_t N>
1666
std::shared_ptr<ExpressionTree> TensorImpl<N>::expression_ptr() const {
1,577,102✔
1667
  if (m_expr_ptr != nullptr) {
1,577,102✔
1668
    if (m_libtensor_ptr != nullptr) {
997,549✔
1669
      throw runtime_error(
×
1670
            "Internal error: m_libtensor_ptr is not a nullptr and neither is "
1671
            "m_expr_ptr.");
1672
    }
1673
    return m_expr_ptr;  // Already have an expression for myself, just return it
997,549✔
1674
  } else {
1675
    if (m_libtensor_ptr == nullptr) {
579,553✔
1676
      throw runtime_error(
×
1677
            "Internal error: Both m_libtensor_ptr and m_expr_ptr are nullptrs.");
1678
    }
1679

1680
    // Make a new expression tree with just this tensor in it
1681
    return std::make_shared<ExpressionTree>(
1,738,659✔
1682
          lt::expr::node_ident_any_tensor<N, scalar_type>(*m_libtensor_ptr),
1,159,106✔
1683
          identity_permutation(N), std::vector<std::shared_ptr<void>>{m_libtensor_ptr});
1,738,659✔
1684
  }
1685
}
579,553✔
1686

1687
std::shared_ptr<ExpressionTree> as_expression(const std::shared_ptr<Tensor>& tensor) {
460,492✔
1688
  std::shared_ptr<ExpressionTree> ret;
460,492✔
1689
  if (tensor->ndim() == 1) {
460,492✔
1690
    ret = std::static_pointer_cast<TensorImpl<1>>(tensor)->expression_ptr();
3,776✔
1691
  } else if (tensor->ndim() == 2) {
456,716✔
1692
    ret = std::static_pointer_cast<TensorImpl<2>>(tensor)->expression_ptr();
211,103✔
1693
  } else if (tensor->ndim() == 3) {
245,613✔
1694
    ret = std::static_pointer_cast<TensorImpl<3>>(tensor)->expression_ptr();
×
1695
  } else if (tensor->ndim() == 4) {
245,613✔
1696
    ret = std::static_pointer_cast<TensorImpl<4>>(tensor)->expression_ptr();
245,605✔
1697
  } else if (tensor->ndim() == 5) {
8✔
1698
    ret = std::static_pointer_cast<TensorImpl<5>>(tensor)->expression_ptr();
×
1699
  } else if (tensor->ndim() == 6) {
8✔
1700
    ret = std::static_pointer_cast<TensorImpl<6>>(tensor)->expression_ptr();
8✔
1701
  } else {
1702
    throw not_implemented_error("Only implemented for dimensionality <= 6.");
×
1703
  }
1704

1705
  if (ret->permutation.size() != tensor->ndim()) {
460,492✔
1706
    throw runtime_error("Internal error: Mismatch between permutation.size() == " +
×
1707
                        std::to_string(ret->permutation.size()) +
×
1708
                        " and tensor dimensionality " + std::to_string(tensor->ndim()) +
×
1709
                        ".");
×
1710
  }
1711

1712
  return ret;
460,492✔
1713
}
×
1714

1715
template <size_t N>
1716
lt::btensor<N, scalar_type>& as_btensor(const std::shared_ptr<Tensor>& in) {
1,057,899✔
1717
  if (N != in->ndim()) {
1,057,899✔
1718
    throw dimension_mismatch("Requested dimensionality " + std::to_string(N) +
×
1719
                             ", but passed tensor has dimensionality " +
1720
                             std::to_string(in->ndim()));
1721
  }
1722
  return static_cast<lt::btensor<N, scalar_type>&>(
1,057,899✔
1723
        *std::static_pointer_cast<TensorImpl<N>>(in));
2,115,798✔
1724
}
1725

1726
template <size_t N>
1727
std::shared_ptr<lt::btensor<N, scalar_type>> as_btensor_ptr(
×
1728
      const std::shared_ptr<Tensor>& in) {
1729
  if (N != in->ndim()) {
×
1730
    throw dimension_mismatch("Requested dimensionality " + std::to_string(N) +
×
1731
                             ", but passed tensor has dimensionality " +
1732
                             std::to_string(in->ndim()));
1733
  }
1734
  return std::static_pointer_cast<TensorImpl<N>>(in)->libtensor_ptr();
×
1735
}
1736

1737
namespace {
1738
template <size_t N>
1739
std::shared_ptr<Tensor> make_tensor_inner(std::shared_ptr<Symmetry> symmetry) {
31,579✔
1740
  auto ltsym_ptr = as_lt_symmetry<N>(*symmetry);
31,579✔
1741
  auto newtensor_ptr =
31,579✔
1742
        std::make_shared<lt::btensor<N, scalar_type>>(ltsym_ptr->get_bis());
1743

1744
  lt::block_tensor_ctrl<N, scalar_type> ctrl_to(*newtensor_ptr);
31,579✔
1745
  lt::so_copy<N, scalar_type>(*ltsym_ptr).perform(ctrl_to.req_symmetry());
31,579✔
1746
  return std::make_shared<TensorImpl<N>>(symmetry->adcmem_ptr(), symmetry->axes(),
1747
                                         std::move(newtensor_ptr));
63,158✔
1748
}
31,579✔
1749
}  // namespace
1750

1751
std::shared_ptr<Tensor> make_tensor(std::shared_ptr<Symmetry> symmetry) {
31,579✔
1752
  if (symmetry->ndim() == 1) {
31,579✔
1753
    return make_tensor_inner<1>(symmetry);
704✔
1754
  } else if (symmetry->ndim() == 2) {
30,875✔
1755
    return make_tensor_inner<2>(symmetry);
20,854✔
1756
  } else if (symmetry->ndim() == 3) {
10,021✔
1757
    return make_tensor_inner<3>(symmetry);
×
1758
  } else if (symmetry->ndim() == 4) {
10,021✔
1759
    return make_tensor_inner<4>(symmetry);
10,019✔
1760
  } else if (symmetry->ndim() == 5) {
2✔
1761
    return make_tensor_inner<5>(symmetry);
×
1762
  } else if (symmetry->ndim() == 6) {
2✔
1763
    return make_tensor_inner<6>(symmetry);
2✔
1764
  } else {
1765
    throw not_implemented_error("Only implemented for dimensionality <= 6.");
×
1766
  }
1767
}
1768

1769
std::shared_ptr<Tensor> make_tensor(std::shared_ptr<const AdcMemory> adcmem_ptr,
16✔
1770
                                    std::vector<AxisInfo> axes) {
1771
  if (axes.size() == 1) {
16✔
1772
    return std::make_shared<TensorImpl<1>>(adcmem_ptr, axes);
×
1773
  } else if (axes.size() == 2) {
16✔
1774
    return std::make_shared<TensorImpl<2>>(adcmem_ptr, axes);
16✔
1775
  } else if (axes.size() == 3) {
×
1776
    return std::make_shared<TensorImpl<3>>(adcmem_ptr, axes);
×
1777
  } else if (axes.size() == 4) {
×
1778
    return std::make_shared<TensorImpl<4>>(adcmem_ptr, axes);
×
1779
  } else if (axes.size() == 5) {
×
1780
    return std::make_shared<TensorImpl<5>>(adcmem_ptr, axes);
×
1781
  } else if (axes.size() == 6) {
×
1782
    return std::make_shared<TensorImpl<6>>(adcmem_ptr, axes);
×
1783
  } else {
1784
    throw not_implemented_error("Only implemented for dimensionality <= 6.");
×
1785
  }
1786
}
1787

1788
//
1789
// Explicit instantiation
1790
//
1791

1792
#define INSTANTIATE(DIM)                                                                 \
1793
  template class TensorImpl<DIM>;                                                        \
1794
                                                                                         \
1795
  template lt::btensor<DIM, scalar_type>& as_btensor(const std::shared_ptr<Tensor>& in); \
1796
                                                                                         \
1797
  template std::shared_ptr<lt::btensor<DIM, scalar_type>> as_btensor_ptr(                \
1798
        const std::shared_ptr<Tensor>& in);
1799

1800
INSTANTIATE(1)
1801
INSTANTIATE(2)
1802
INSTANTIATE(3)
1803
INSTANTIATE(4)
1804
INSTANTIATE(5)
1805
INSTANTIATE(6)
1806

1807
#undef INSTANTIATE
1808

1809
}  // namespace libadcc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc