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

adc-connect / adcc / 27744515930

18 Jun 2026 07:43AM UTC coverage: 75.632% (+0.5%) from 75.173%
27744515930

Pull #217

github

web-flow
Merge db07b4ae9 into c6194d4e9
Pull Request #217: ADC(4)

1359 of 2114 branches covered (64.29%)

Branch coverage included in aggregate %.

200 of 218 new or added lines in 9 files covered. (91.74%)

1 existing line in 1 file now uncovered.

8579 of 11026 relevant lines covered (77.81%)

239520.62 hits per line

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

76.83
/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) {
2,298,823✔
75
  std::vector<size_t> permutation;
2,298,823✔
76
  for (size_t i = 0; i < n; ++i) {
10,147,049✔
77
    permutation.push_back(i);
7,848,226✔
78
  }
79
  return permutation;
2,298,823✔
80
}
×
81

82
template <size_t N>
83
lt::expr::label<N> strip_safe(const std::vector<std::shared_ptr<const lt::letter>>& in) {
5,038,820✔
84
  std::vector<const lt::letter*> label_unsafe;
5,038,820✔
85
  for (size_t i = 0; i < in.size(); ++i) {
19,746,752✔
86
    label_unsafe.push_back(in[i].get());
14,707,932✔
87
  }
88
  return lt::expr::label<N>(label_unsafe);
10,077,640✔
89
}
5,038,820✔
90

91
template <size_t N>
92
std::vector<size_t> extract_expr_permutation(
3,555,600✔
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;
3,555,600✔
96
  lt::permutation<N> perm = expr.get_label().permutation_of(strip_safe<N>(label));
3,555,600✔
97
  perm.invert();
3,555,600✔
98
  for (size_t i = 0; i < N; ++i) {
14,910,478✔
99
    permutation.push_back(perm[i]);
11,354,878✔
100
  }
101
  return permutation;
7,111,200✔
102
}
×
103

104
/** Merge two keepalive lists */
105
std::vector<std::shared_ptr<void>> merge(std::vector<std::shared_ptr<void>> lhs,
2,029,848✔
106
                                         const std::vector<std::shared_ptr<void>>& rhs) {
107
  for (auto& ptr : rhs) lhs.push_back(ptr);
7,327,013✔
108
  return lhs;
2,029,848✔
109
}
110

111
std::vector<std::shared_ptr<const lt::letter>> make_label(size_t n) {
4,781,777✔
112
  std::vector<std::shared_ptr<const lt::letter>> ret;
4,781,777✔
113
  for (size_t i = 0; i < n; ++i) {
20,613,819✔
114
    ret.push_back(std::make_shared<lt::letter>());
15,832,042✔
115
  }
116
  return ret;
4,781,777✔
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(
257,373✔
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]))...};
257,373✔
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(
257,373✔
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)
257,373✔
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;
257,373✔
167
  std::vector<size_t> processed_indices;
257,373✔
168
  for (const auto& perm : permutations) {
535,412✔
169
    if (perm.size() != PERM_SIZE)
278,039✔
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++) {
867,105✔
175
      for (size_t j{i + 1}; j < PERM_SIZE; j++) {
933,081✔
176
        if (perm[i] == perm[j])
344,015✔
177
          throw invalid_argument("Duplicate entry in permutation " +
×
178
                                 perm_to_string(perm) + " detected.");
179
        if (axes[perm[i]] != axes[perm[j]]) {
344,015✔
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++) {
867,105✔
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]);
589,066✔
196
      if (find_i != processed_indices.end()) {
589,066✔
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)
589,066✔
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]));
589,066✔
209
      processed_indices.push_back(perm[i]);
589,066✔
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>{});
514,746✔
215
}
257,373✔
216

217
template <size_t N, typename T>
218
std::pair<lt::index<N>, lt::index<N>> assert_convert_tensor_index(
1,974,512✔
219
      lt::btensor<N, T>& tensor, const std::vector<size_t>& idx) {
220
  if (idx.size() != N) {
1,974,512✔
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();
1,974,512✔
226
  for (size_t i = 0; i < N; ++i) {
9,710,048✔
227
    if (idx[i] >= dims[i]) {
7,735,536✔
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;
1,974,512✔
235
  for (size_t idim = 0; idim < N; ++idim) {
9,710,048✔
236
    // Find splits
237
    const size_t dim_type     = tensor.get_bis().get_type(idim);
7,735,536✔
238
    const lt::split_points sp = tensor.get_bis().get_splits(dim_type);
7,735,536✔
239
    block_idx[idim]           = 0;
7,735,536✔
240
    for (size_t isp = 0; isp < sp.get_num_points(); ++isp) {
11,586,932✔
241
      if (sp[isp] > idx[idim]) break;
7,735,566✔
242
      block_idx[idim] = isp + 1;
3,851,396✔
243
    }
244
  }
245

246
  lt::index<N> inblock_idx;
1,974,512✔
247
  lt::index<N> bstart    = tensor.get_bis().get_block_start(block_idx);
1,974,512✔
248
  lt::dimensions<N> bdim = tensor.get_bis().get_block_dims(block_idx);
1,974,512✔
249
  for (size_t idim = 0; idim < N; ++idim) {
9,710,048✔
250
    inblock_idx[idim] = idx[idim] - bstart[idim];
7,735,536✔
251
    if (inblock_idx[idim] >= bdim[idim]) {
7,735,536✔
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);
3,949,024✔
258
}
259

260
template <typename Comparator, size_t N>
261
std::vector<std::pair<std::vector<size_t>, scalar_type>> execute_select_n(
3,432✔
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;
3,432✔
265
  if (!unique_by_symmetry) {
3,432✔
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);
3,432✔
270
  }
271

272
  std::vector<std::pair<std::vector<size_t>, scalar_type>> ret;
3,432✔
273
  for (auto it = il.begin(); it != il.end(); ++it) {
54,992✔
274
    std::vector<size_t> fidx(N);
51,560✔
275
    const lt::index<N> bstart = tensor.get_bis().get_block_start(it->get_block_index());
51,560✔
276
    for (size_t i = 0; i < N; ++i) {
154,680✔
277
      fidx[i] = bstart[i] + it->get_in_block_index()[i];
103,120✔
278
    }
279
    ret.emplace_back(fidx, it->get_value());
51,560✔
280
  }
281
  return ret;
6,864✔
282
}
3,432✔
283
}  // namespace
284

285
template <size_t N>
286
void TensorImpl<N>::check_state() const {
19,099,992✔
287
  if (m_expr_ptr == nullptr && m_libtensor_ptr == nullptr) {
19,099,992✔
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) {
19,099,992✔
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()) {
19,099,992✔
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) {
19,099,992✔
303
    std::vector<size_t> btshape(N);
13,964,217✔
304
    const lt::dimensions<N>& dims = m_libtensor_ptr->get_bis().get_dims();
13,964,217✔
305
    for (size_t i = 0; i < N; ++i) {
62,516,533✔
306
      btshape[i] = dims.get_dim(i);
48,552,316✔
307
    }
308
    if (shape() != btshape) {
13,964,217✔
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 =
13,964,217✔
315
          get_block_starts(*m_libtensor_ptr);
13,964,217✔
316
    for (size_t i = 0; i < N; ++i) {
62,516,533✔
317
      if (axes()[i].block_starts != tensorblocks[i]) {
48,552,316✔
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
  }
13,964,217✔
326

327
  if (m_expr_ptr) {
19,099,992✔
328
    if (m_expr_ptr->permutation.size() != N) {
5,135,775✔
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
}
19,099,992✔
335

336
template <size_t N>
337
void TensorImpl<N>::reset_state(
644,727✔
338
      std::shared_ptr<lt::btensor<N, scalar_type>> libtensor_ptr) const {
339
  if (m_expr_ptr != nullptr && m_libtensor_ptr != nullptr) {
644,727✔
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) {
644,727✔
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;
644,727✔
348
  m_expr_ptr.reset();
644,727✔
349
  check_state();
644,727✔
350
}
644,727✔
351

352
template <size_t N>
353
void TensorImpl<N>::reset_state(std::shared_ptr<ExpressionTree> expr_ptr) const {
4,702,748✔
354
  if (m_expr_ptr != nullptr && m_libtensor_ptr != nullptr) {
4,702,748✔
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) {
4,702,748✔
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;
4,702,748✔
363
  m_libtensor_ptr.reset();
4,702,748✔
364
  check_state();
4,702,748✔
365
}
4,702,748✔
366

367
template <size_t N>
368
TensorImpl<N>::TensorImpl(std::shared_ptr<const AdcMemory> adcmem_ptr,
4,915,532✔
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) {
4,915,532✔
373
  if (axes.size() != N) {
4,915,532✔
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) {
4,915,532✔
380
    throw invalid_argument("libtensor_ptr and expr_ptr cannot both be set pointers.");
×
381
  }
382
  if (expr_ptr == nullptr && libtensor_ptr == nullptr) {
4,915,532✔
383
    // Allocate an empty tensor.
384
    libtensor_ptr = std::make_shared<lt::btensor<N, scalar_type>>(as_bispace<N>(axes));
84✔
385
  }
386

387
  if (expr_ptr != nullptr) reset_state(expr_ptr);
4,915,532✔
388
  if (libtensor_ptr != nullptr) reset_state(libtensor_ptr);
4,915,532✔
389
}
4,915,532✔
390

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

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

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

405
template <size_t N>
406
std::shared_ptr<Tensor> TensorImpl<N>::empty_like() const {
139,858✔
407
  check_state();
139,858✔
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 =
139,858✔
413
        std::make_shared<lt::btensor<N, scalar_type>>(libtensor_ptr()->get_bis());
279,716✔
414

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

421
  // Enwrap inside TensorImpl and return
422
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, std::move(newtensor_ptr));
279,716✔
423
}
139,858✔
424

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

430
template <size_t N>
431
void TensorImpl<N>::set_mask(std::string mask, scalar_type value) {
210,066✔
432
  if (N != mask.size()) {
210,066✔
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;
210,066✔
441
  std::map<char, size_t> char_to_idx;
210,066✔
442
  lt::sequence<N, size_t> seq(0);
210,066✔
443
  for (size_t i = 0; i < mask.size(); ++i) {
836,370✔
444
    const char c  = mask[i];
626,304✔
445
    const auto it = char_to_idx.find(c);
626,304✔
446
    if (it == char_to_idx.end()) {
626,304✔
447
      char_to_idx[c] = next_idx;
622,336✔
448
      seq[i]         = next_idx;
622,336✔
449
      next_idx += 1;
622,336✔
450
    } else {
451
      seq[i] = it->second;
3,968✔
452
    }
453
  }
454

455
  if (char_to_idx.size() == N) {
210,066✔
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());
206,164✔
459
  } else {
460
    lt::btod_set_diag<N>(seq, value).perform(*libtensor_ptr());
3,902✔
461
  }
462
}
210,066✔
463

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

470
namespace {
471

472
template <size_t R, size_t D, size_t N>
473
std::shared_ptr<Tensor> execute_diagonal(
28,404✔
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);
28,404✔
480
  auto res      = lt::expr::diag(*label_diag[0], strip_safe<D>(label_diag), lthis);
28,404✔
481
  auto expr_ptr = std::make_shared<ExpressionTree>(
28,404✔
482
        res.get_expr(), extract_expr_permutation(res, label_result), expr->keepalives);
28,404✔
483
  return std::make_shared<TensorImpl<R>>(adcmem_ptr, axes, expr_ptr);
56,808✔
484
}
28,404✔
485

486
}  // namespace
487

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

496
  std::vector<std::shared_ptr<const lt::letter>> diag;
28,404✔
497
  std::unique_ptr<AxisInfo> diagaxis_ptr;
28,404✔
498
  std::vector<size_t> used_indices;
28,404✔
499
  for (size_t& i : axes) {
85,212✔
500
    auto it = std::find(used_indices.begin(), used_indices.end(), i);
56,808✔
501
    if (it != used_indices.end()) {
56,808✔
502
      throw invalid_argument("Axes may not have repeated indices.");
×
503
    }
504
    if (i >= N) {
56,808✔
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) {
56,808✔
510
      diagaxis_ptr.reset(new AxisInfo(m_axes[i]));
28,404✔
511
    } else {
512
      if (*diagaxis_ptr != m_axes[i]) {
28,404✔
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]);
56,808✔
519
    used_indices.push_back(i);
56,808✔
520
  }
521

522
  // Collect letters, which are to be left unchanged.
523
  std::vector<AxisInfo> axes_result;
28,404✔
524
  std::vector<std::shared_ptr<const lt::letter>> label_result;
28,404✔
525
  for (size_t i = 0; i < N; ++i) {
107,074✔
526
    auto it = std::find(used_indices.begin(), used_indices.end(), i);
78,670✔
527
    if (it == used_indices.end()) {
78,670✔
528
      label_result.push_back(label[i]);
21,862✔
529
      axes_result.push_back(m_axes[i]);
21,862✔
530
    }
531
  }
532
  label_result.push_back(diag[0]);
28,404✔
533
  axes_result.push_back(*diagaxis_ptr);
28,404✔
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)  //
14,936✔
545
  IF_MATCHES_EXECUTE(3, 2)  //
6,178✔
546
  IF_MATCHES_EXECUTE(3, 3)  //
×
547
  IF_MATCHES_EXECUTE(4, 2)  //
6,554✔
548
  IF_MATCHES_EXECUTE(4, 3)  //
×
549
  IF_MATCHES_EXECUTE(4, 4)  //
×
550
  IF_MATCHES_EXECUTE(5, 2)  //
368✔
551
  IF_MATCHES_EXECUTE(5, 3)  //
×
552
  IF_MATCHES_EXECUTE(5, 4)  //
×
553
  IF_MATCHES_EXECUTE(5, 5)  //
×
554
  IF_MATCHES_EXECUTE(6, 2)  //
368✔
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
}
28,404✔
566

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

574
  // Execute the operation
575
  auto scaled = c * lthis;
1,239,975✔
576

577
  auto expr_ptr = std::make_shared<ExpressionTree>(
1,239,975✔
578
        scaled.get_expr(), extract_expr_permutation(scaled, label),
579
        expr_this->keepalives);
1,239,975✔
580
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, expr_ptr);
2,479,950✔
581
}
1,239,975✔
582

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

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

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

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

603
template <size_t N>
604
void TensorImpl<N>::add_linear_combination(
96,516✔
605
      std::vector<scalar_type> scalars,
606
      std::vector<std::shared_ptr<Tensor>> tensors) const {
607
  if (scalars.size() != tensors.size()) {
96,516✔
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;
96,516✔
613

614
  std::unique_ptr<lt::btod_add<N>> operator_ptr;
96,516✔
615
  for (size_t i = 0; i < scalars.size(); ++i) {
2,540,452✔
616
    DIMENSIONALITY_CHECK(tensors[i]);
2,443,936✔
617
    if (!operator_ptr) {
2,443,936✔
618
      operator_ptr.reset(new lt::btod_add<N>(as_btensor<N>(tensors[i]), scalars[i]));
96,516✔
619
    } else {
620
      operator_ptr->add_op(as_btensor<N>(tensors[i]), scalars[i]);
2,347,420✔
621
    }
622
  }
623
  operator_ptr->perform(*libtensor_ptr(), 1.0);
96,516✔
624
}
96,516✔
625

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

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

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

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

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

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

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

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

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

680
template <size_t N>
681
std::shared_ptr<Tensor> TensorImpl<N>::transpose(std::vector<size_t> permutation) const {
1,140,044✔
682
  if (permutation.size() != N) {
1,140,044✔
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;
1,140,044✔
691
  for (size_t i = 0; i < N; ++i) {
4,668,060✔
692
    for (size_t j = 0; j < i; ++j) {
7,877,314✔
693
      if (permutation[i] == permutation[j]) {
4,349,298✔
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) {
3,528,016✔
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]]);
3,528,016✔
706
  }
707

708
  // Chain permutations
709
  std::shared_ptr<ExpressionTree> expr_this = expression_ptr();
1,140,044✔
710
  std::vector<size_t> result_permutation;
1,140,044✔
711
  for (size_t i = 0; i < N; ++i) {
4,668,060✔
712
    result_permutation.push_back(expr_this->permutation[permutation[i]]);
3,528,016✔
713
  }
714
  auto expr_ptr = std::make_shared<ExpressionTree>(
1,140,044✔
715
        *expr_this->tree_ptr, result_permutation, expr_this->keepalives);
1,140,044✔
716
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, newaxes, expr_ptr);
2,280,088✔
717
}
1,140,044✔
718

719
namespace {
720
template <size_t R, size_t N, size_t M>
721
std::shared_ptr<Tensor> execute_direct_sum(
14,764✔
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);
14,764✔
729
  auto lsecond = expr_second->attach_letters<M>(label_second);
14,764✔
730

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

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

746
  lalvec_t label_result;
14,764✔
747
  for (auto& v : label_first) label_result.push_back(v);
40,726✔
748
  for (auto& v : label_second) label_result.push_back(v);
35,884✔
749
  std::vector<AxisInfo> axes_result;
14,764✔
750
  for (auto& ax : m_axes) axes_result.push_back(ax);
40,726✔
751
  for (auto& ax : other->axes()) axes_result.push_back(ax);
35,884✔
752

753
  std::shared_ptr<ExpressionTree> expr_first  = expression_ptr();
14,764✔
754
  std::shared_ptr<ExpressionTree> expr_second = as_expression(other);
14,764✔
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)  //
14,764✔
770
  IF_MATCHES_EXECUTE(1, 2)  //
8,966✔
771
  IF_MATCHES_EXECUTE(1, 3)  //
8,964✔
772
  IF_MATCHES_EXECUTE(1, 4)  //
8,780✔
773
  IF_MATCHES_EXECUTE(1, 5)  //
8,780✔
774
  IF_MATCHES_EXECUTE(2, 1)  //
8,780✔
775
  IF_MATCHES_EXECUTE(2, 2)  //
7,468✔
776
  IF_MATCHES_EXECUTE(2, 3)  //
2,058✔
777
  IF_MATCHES_EXECUTE(2, 4)  //
2,058✔
778
  IF_MATCHES_EXECUTE(3, 1)  //
1,866✔
779
  IF_MATCHES_EXECUTE(3, 2)  //
368✔
780
  IF_MATCHES_EXECUTE(3, 3)  //
368✔
781
  IF_MATCHES_EXECUTE(4, 1)  //
368✔
782
  IF_MATCHES_EXECUTE(4, 2)  //
184✔
783
  IF_MATCHES_EXECUTE(5, 1)  //
184✔
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
}
14,764✔
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 {
10✔
803
  if (contraction.size() != N) {
10✔
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;
10✔
809
  std::vector<bool> index_done(N, false);
10✔
810
  for (size_t i = 0; i < N; ++i) {
46✔
811
    if (index_done[i]) continue;
36✔
812
    index_done[i]   = true;
18✔
813
    bool found_pair = false;
18✔
814
    for (size_t j = i + 1; j < N; ++j) {
30✔
815
      if (contraction[i] == contraction[j]) {
30✔
816
        if (m_axes[i] != m_axes[j]) {
18✔
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;
18✔
821
        trace_pairs.push_back({i, j});
18✔
822
        found_pair = true;
18✔
823
        break;
18✔
824
      }
825
    }
826
    if (!found_pair) {
18✔
827
      throw("Found no matching second index for '" + std::string(1, contraction[i]) +
×
828
            "'.");
829
    }
830
  }
831

832
  if (2 * trace_pairs.size() != N) {
10✔
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);
10✔
842
  lalvec_t tlal_first;
10✔
843
  lalvec_t tlal_second;
10✔
844
  for (const auto& p : trace_pairs) {
28✔
845
    tlal_first.push_back(label[p.first]);
18✔
846
    tlal_second.push_back(label[p.second]);
18✔
847
  }
848

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

855
namespace {
856
template <size_t R, size_t K, size_t N, size_t M>
857
TensorOrScalar execute_tensordot_contract(
1,197,423✔
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);
1,197,423✔
867
  auto lsecond = expr_second->attach_letters<M>(label_second);
1,197,423✔
868

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

878
template <size_t R, size_t N, size_t M>
879
TensorOrScalar execute_tensordot_tensorprod(
12,168✔
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);
12,168✔
887
  auto lsecond = expr_second->attach_letters<M>(label_second);
12,168✔
888

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

898
}  // namespace
899

900
template <size_t N>
901
TensorOrScalar TensorImpl<N>::tensordot(
1,210,497✔
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;
1,210,497✔
905
  const std::vector<size_t>& axes_second = axes.second;
1,210,497✔
906

907
  if (axes_first.size() != axes_second.size()) {
1,210,497✔
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) {
1,210,497✔
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()) {
1,210,497✔
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);
1,210,497✔
928
  lalvec_t label_second = make_label(other->ndim());
1,210,497✔
929
  lalvec_t label_contracted;
1,210,497✔
930
  for (size_t i = 0; i < axes_first.size(); ++i) {
3,434,859✔
931
    std::shared_ptr<const lt::letter> l_contraction = label_first[axes_first[i]];
2,224,362✔
932
    label_second[axes_second[i]]                    = l_contraction;
2,224,362✔
933

934
    if (m_axes[axes_first[i]] != other->axes()[axes_second[i]]) {
2,224,362✔
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);
2,224,362✔
942
  }
943

944
  // Build labels of the result
945
  lalvec_t label_result;
1,210,497✔
946
  std::vector<AxisInfo> axes_result;
1,210,497✔
947
  for (size_t i = 0; i < N; ++i) {
5,328,025✔
948
    auto it = std::find(axes_first.begin(), axes_first.end(), i);
4,117,528✔
949
    if (it == axes_first.end()) {
4,117,528✔
950
      label_result.push_back(label_first[i]);
1,893,166✔
951
      axes_result.push_back(m_axes[i]);
1,893,166✔
952
    }
953
  }
954
  for (size_t j = 0; j < other->ndim(); ++j) {
5,309,711✔
955
    auto it = std::find(axes_second.begin(), axes_second.end(), j);
4,099,214✔
956
    if (it == axes_second.end()) {
4,099,214✔
957
      label_result.push_back(label_second[j]);
1,874,852✔
958
      axes_result.push_back(other->axes()[j]);
1,874,852✔
959
    }
960
  }
961

962
  if (label_result.size() != N + other->ndim() - 2 * label_contracted.size()) {
1,210,497✔
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();
1,210,497✔
970
  std::shared_ptr<ExpressionTree> expr_second = as_expression(other);
1,210,497✔
971

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

977
    auto lfirst  = expr_first->attach_letters<N>(label_first);
906✔
978
    auto lsecond = expr_second->attach_letters<N>(label_second);
906✔
979
    return TensorOrScalar{nullptr, lt::dot_product(lfirst, lsecond)};
1,812✔
980
  } else if (label_contracted.size() == 0) {
1,210,497✔
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)  //
12,168✔
999
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(1, 2)  //
12,166✔
1000
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(1, 3)  //
12,166✔
1001
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(1, 4)  //
12,166✔
1002
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(1, 5)  //
12,166✔
1003
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(2, 1)  //
12,166✔
1004
    IF_DIMENSIONS_MATCH_EXECUTE_TENSORPROD(2, 2)  //
12,166✔
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)  //
1,197,423✔
1036
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 1, 3)  //
1,197,421✔
1037
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 1, 4)  //
1,197,421✔
1038
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 1, 5)  //
1,197,421✔
1039
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 1, 6)  //
1,197,421✔
1040
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 1)  //
1,197,421✔
1041
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 2)  //
1,197,417✔
1042
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 3)  //
1,081,233✔
1043
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 4)  //
1,081,233✔
1044
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 5)  //
924,912✔
1045
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 2, 6)  //
924,912✔
1046
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 3, 1)  //
924,912✔
1047
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 3, 2)  //
924,912✔
1048
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 3, 3)  //
924,908✔
1049
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 3, 4)  //
924,908✔
1050
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 3, 5)  //
924,908✔
1051
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 4, 1)  //
924,908✔
1052
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 4, 2)  //
924,908✔
1053
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 4, 3)  //
723,502✔
1054
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 4, 4)  //
723,502✔
1055
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 5, 1)  //
702,254✔
1056
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 5, 2)  //
702,254✔
1057
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 5, 3)  //
702,254✔
1058
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 6, 1)  //
702,254✔
1059
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(1, 6, 2)  //
702,254✔
1060
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 2, 3)  //
702,254✔
1061
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 2, 4)  //
702,254✔
1062
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 2, 5)  //
608,409✔
1063
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 2, 6)  //
608,409✔
1064
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 3, 2)  //
608,409✔
1065
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 3, 3)  //
608,409✔
1066
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 3, 4)  //
608,409✔
1067
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 3, 5)  //
608,409✔
1068
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 3, 6)  //
608,409✔
1069
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 4, 2)  //
608,409✔
1070
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 4, 3)  //
541,359✔
1071
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 4, 4)  //
541,359✔
1072
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 4, 5)  //
322,065✔
1073
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 4, 6)  //
322,065✔
1074
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 5, 2)  //
321,697✔
1075
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 5, 3)  //
321,697✔
1076
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 5, 4)  //
321,697✔
1077
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 5, 5)  //
321,697✔
1078
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 6, 2)  //
321,697✔
1079
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 6, 3)  //
318,281✔
1080
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(2, 6, 4)  //
318,281✔
1081
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 3, 4)  //
317,913✔
1082
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 3, 5)  //
317,913✔
1083
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 3, 6)  //
317,913✔
1084
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 4, 3)  //
317,913✔
1085
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 4, 4)  //
317,913✔
1086
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 4, 5)  //
41,264✔
1087
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 4, 6)  //
41,264✔
1088
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 5, 3)  //
12,648✔
1089
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 5, 4)  //
12,648✔
1090
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 5, 5)  //
12,648✔
1091
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 5, 6)  //
12,648✔
1092
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 6, 3)  //
12,648✔
1093
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 6, 4)  //
12,648✔
1094
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 6, 5)  //
3,416✔
1095
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(3, 6, 6)  //
3,416✔
1096
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 4, 5)  //
3,416✔
1097
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 4, 6)  //
3,416✔
1098
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 5, 4)  //
3,416✔
1099
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 5, 5)  //
3,416✔
1100
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 5, 6)  //
3,416✔
1101
    IF_DIMENSIONS_MATCH_EXECUTE_CONTRACT(4, 6, 4)  //
3,416✔
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
}
1,211,403✔
1118

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

1124
  for (size_t i = 0; i < tensors.size(); ++i) {
3,219,196✔
1125
    auto tensor_ptr = std::static_pointer_cast<TensorImpl<N>>(tensors[i]);
2,178,932✔
1126
    if (ndim() != tensor_ptr->ndim()) {
2,178,932✔
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()) {
2,178,932✔
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()))
4,357,864✔
1141
                   .calculate();
2,178,932✔
1142
  }
1143
  return ret;
1,040,264✔
1144
}
×
1145

1146
template <size_t N>
1147
std::shared_ptr<Tensor> TensorImpl<N>::symmetrise(
34,978✔
1148
      const std::vector<std::vector<size_t>>& permutations) const {
1149
  if (permutations.size() == 0) {
34,978✔
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);
34,978✔
1156
  std::shared_ptr<ExpressionTree> expr_this = expression_ptr();
34,978✔
1157
  auto lthis                                = expr_this->attach_letters<N>(label);
34,978✔
1158

1159
  // Execute the operation
1160
  auto symmetrised = [&permutations, &lthis, this, label]() {
69,956✔
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();
34,978✔
1164
    if (perm_size == 2) {
34,978✔
1165
      if (permutations.size() == 1) {
31,386✔
1166
        const auto parsed =
×
1167
              parse_permutation<2, 1>(m_axes, strip_safe<N>(label), permutations);
13,562✔
1168
        return 0.5 * lt::expr::symm(std::get<0>(parsed), std::get<1>(parsed), lthis);
27,124✔
1169
      } else if (permutations.size() == 2) {
17,824✔
1170
        const auto parsed =
×
1171
              parse_permutation<2, 2>(m_axes, strip_safe<N>(label), permutations);
17,824✔
1172
        return 0.5 * lt::expr::symm(std::get<0>(parsed), std::get<1>(parsed), lthis);
35,648✔
1173
      } else {
1174
        throw runtime_error(
×
1175
              "Symmetrisation not implemented for more than two index pairs.");
1176
      }
1177
    } else if (perm_size == 3) {
3,592✔
1178
      if (permutations.size() == 1) {
3,592✔
1179
        const auto parsed =
×
1180
              parse_permutation<3, 1>(m_axes, strip_safe<N>(label), permutations);
754✔
1181
        return 1. / 6. *
×
1182
               lt::expr::symm(std::get<0>(parsed), std::get<1>(parsed),
1,508✔
1183
                              std::get<2>(parsed), lthis);
3,016✔
1184
      } else if (permutations.size() == 2) {
2,838✔
NEW
1185
        const auto parsed =
×
1186
              parse_permutation<3, 2>(m_axes, strip_safe<N>(label), permutations);
2,838✔
NEW
1187
        return 1. / 6. *
×
1188
               lt::expr::symm(std::get<0>(parsed), std::get<1>(parsed),
5,676✔
1189
                              std::get<2>(parsed), lthis);
11,352✔
1190
      } else {
1191
        throw runtime_error(
×
1192
              "Symmetrisation not implemented for more than two index triple.");
1193
      }
1194
    } else {
1195
      throw runtime_error(
×
1196
            "Symmetrisation only implemented for permutations of length 2 and 3.");
1197
    }
1198
  }();
1199

1200
  auto expr_ptr = std::make_shared<ExpressionTree>(
34,978✔
1201
        symmetrised.get_expr(), extract_expr_permutation(symmetrised, label),
1202
        expr_this->keepalives);
34,978✔
1203
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, expr_ptr);
34,978✔
1204
}
34,978✔
1205

1206
template <size_t N>
1207
std::shared_ptr<Tensor> TensorImpl<N>::antisymmetrise(
222,395✔
1208
      const std::vector<std::vector<size_t>>& permutations) const {
1209
  if (permutations.size() == 0) {
222,395✔
1210
    return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, m_libtensor_ptr,
×
1211
                                           m_expr_ptr);  // Noop
×
1212
  }
1213

1214
  // Label this expression
1215
  auto label                                = make_label(N);
222,395✔
1216
  std::shared_ptr<ExpressionTree> expr_this = expression_ptr();
222,395✔
1217
  auto lthis                                = expr_this->attach_letters<N>(label);
222,395✔
1218

1219
  // Execute the operation
1220
  auto antisymmetrised = [&permutations, &lthis, this, &label]() {
444,790✔
1221
    // this assumes that all permutations have the same length
1222
    // but the parse functions throw if this is not the case
1223
    const auto perm_size = permutations.front().size();
222,395✔
1224
    if (perm_size == 2) {
222,395✔
1225
      if (permutations.size() == 1) {
195,839✔
1226
        const auto parsed =
195,837✔
1227
              parse_permutation<2, 1>(m_axes, strip_safe<N>(label), permutations);
195,837✔
1228
        return 0.5 * lt::expr::asymm(std::get<0>(parsed), std::get<1>(parsed), lthis);
391,674✔
1229
      } else if (permutations.size() == 2) {
2✔
1230
        const auto parsed =
2✔
1231
              parse_permutation<2, 2>(m_axes, strip_safe<N>(label), permutations);
2✔
1232
        return 0.5 * lt::expr::asymm(std::get<0>(parsed), std::get<1>(parsed), lthis);
4✔
1233
      } else {
1234
        throw runtime_error(
×
1235
              "Antisymmetrisation not implemented for more than two index pairs.");
1236
      }
1237
    } else if (perm_size == 3) {
26,556✔
1238
      if (permutations.size() == 1) {
26,556✔
1239
        const auto parsed =
26,554✔
1240
              parse_permutation<3, 1>(m_axes, strip_safe<N>(label), permutations);
26,554✔
1241
        return 1. / 6. *
×
1242
               lt::expr::asymm(std::get<0>(parsed), std::get<1>(parsed),
53,108✔
1243
                               std::get<2>(parsed), lthis);
106,216✔
1244
      } else if (permutations.size() == 2) {
2✔
1245
        const auto parsed =
2✔
1246
              parse_permutation<3, 2>(m_axes, strip_safe<N>(label), permutations);
2✔
NEW
1247
        return 1. / 6. *
×
1248
               lt::expr::asymm(std::get<0>(parsed), std::get<1>(parsed),
4✔
1249
                               std::get<2>(parsed), lthis);
8✔
1250
      } else {
1251
        throw runtime_error(
×
1252
              "Antisymmetrisation not implemented for more than two index triple.");
1253
      }
1254
    } else {
1255
      throw runtime_error(
×
1256
            "Antisymmetrisation only implemented for permutations of length 2 and 3.");
1257
    }
1258
  }();
1259

1260
  auto expr_ptr = std::make_shared<ExpressionTree>(
222,395✔
1261
        antisymmetrised.get_expr(), extract_expr_permutation(antisymmetrised, label),
1262
        expr_this->keepalives);
222,395✔
1263
  return std::make_shared<TensorImpl<N>>(m_adcmem_ptr, m_axes, expr_ptr);
222,395✔
1264
}
222,395✔
1265

1266
template <size_t N>
1267
void TensorImpl<N>::set_element(const std::vector<size_t>& tidx, scalar_type value) {
31,508✔
1268
  if (!is_element_allowed(tidx)) {
31,508✔
1269
    throw runtime_error("Setting tensor index (" + shape_to_string(tidx) +
×
1270
                        ") not allowed, since zero by symmetry.");
1271
  }
1272

1273
  lt::index<N> block_idx;
31,508✔
1274
  lt::index<N> in_block_idx;
31,508✔
1275
  std::tie(block_idx, in_block_idx) = assert_convert_tensor_index(*libtensor_ptr(), tidx);
31,508✔
1276
  lt::btod_set_elem<N>{}.perform(*libtensor_ptr(), block_idx, in_block_idx, value);
31,508✔
1277
}
31,508✔
1278

1279
template <size_t N>
1280
scalar_type TensorImpl<N>::get_element(const std::vector<size_t>& tidx) const {
1,860,200✔
1281
  lt::index<N> block_idx;
1,860,200✔
1282
  lt::index<N> in_block_idx;
1,860,200✔
1283
  std::tie(block_idx, in_block_idx) = assert_convert_tensor_index(*libtensor_ptr(), tidx);
1,860,200✔
1284

1285
  // The value we want to return
1286
  scalar_type ret;
1287

1288
  // Make an orbit over the block index, i.e. find the canonical block
1289
  lt::block_tensor_ctrl<N, scalar_type> ctrl(*libtensor_ptr());
1,860,200✔
1290
  lt::dimensions<N> bidims(libtensor_ptr()->get_bis().get_block_index_dims());
1,860,200✔
1291
  lt::orbit<N, scalar_type> obit(ctrl.req_const_symmetry(), block_idx);
1,860,200✔
1292

1293
  // If the orbit (i.e. the index) is not allowed by symmetry, than the element is
1294
  // zero.
1295
  if (!obit.is_allowed()) return 0;
1,860,200✔
1296

1297
  // Absolute index of the canonical block
1298
  const lt::abs_index<N> idx_abscanon(obit.get_acindex(), bidims);
1,860,200✔
1299

1300
  // Check if we have hit a zero block, then return 0 as well.
1301
  if (ctrl.req_is_zero_block(idx_abscanon.get_index())) return 0;
1,860,200✔
1302

1303
  // The transformation between the selected block and the canonical block
1304
  const lt::tensor_transf<N, scalar_type>& tr = obit.get_transf(block_idx);
511,032✔
1305

1306
  // Transform in-block index.
1307
  lt::index<N> pibidx(in_block_idx);  //< permuted in-block index
511,032✔
1308
  pibidx.permute(tr.get_perm());
511,032✔
1309

1310
  // Get the actual value from the dense tensor representing the block
1311
  {
1312
    auto& block = ctrl.req_const_block(idx_abscanon.get_index());
511,032✔
1313
    lt::dense_tensor_rd_ctrl<N, scalar_type> blkctrl(block);
511,032✔
1314

1315
    const scalar_type* p = blkctrl.req_const_dataptr();
511,032✔
1316
    ret                  = p[lt::abs_index<N>(pibidx, block.get_dims()).get_abs_index()];
511,032✔
1317
    blkctrl.ret_const_dataptr(p);
511,032✔
1318
  }
511,032✔
1319

1320
  // Transform value
1321
  tr.get_scalar_tr().apply(ret);
511,032✔
1322

1323
  ctrl.ret_const_block(idx_abscanon.get_index());
511,032✔
1324
  return ret;
511,032✔
1325
}
1,860,200✔
1326

1327
template <size_t N>
1328
bool TensorImpl<N>::is_element_allowed(const std::vector<size_t>& tidx) const {
82,804✔
1329
  lt::index<N> block_idx;
82,804✔
1330
  std::tie(block_idx, std::ignore) = assert_convert_tensor_index(*libtensor_ptr(), tidx);
82,804✔
1331

1332
  // Check if the block is allowed in the symmetry of the guess
1333
  lt::block_tensor_ctrl<N, scalar_type> ctrl(*libtensor_ptr());
82,804✔
1334
  lt::orbit<N, scalar_type> orb(ctrl.req_const_symmetry(), block_idx);
82,804✔
1335
  const bool block_allowed = orb.is_allowed();
82,804✔
1336

1337
  // TODO Even if the block is allowed, the index might not be
1338
  // (e.g. diagonal of an anti-symmetric tensor).
1339
  const bool index_allowed = true;
82,804✔
1340

1341
  return block_allowed && index_allowed;
82,804✔
1342
}
82,804✔
1343

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

1350
template <size_t N>
1351
std::vector<std::pair<std::vector<size_t>, scalar_type>> TensorImpl<N>::select_n_absmin(
18✔
1352
      size_t n, bool unique_by_symmetry) const {
1353
  return execute_select_n<lt::compare4absmin>(*libtensor_ptr(), n, unique_by_symmetry);
36✔
1354
}
1355

1356
template <size_t N>
1357
std::vector<std::pair<std::vector<size_t>, scalar_type>> TensorImpl<N>::select_n_max(
12✔
1358
      size_t n, bool unique_by_symmetry) const {
1359
  return execute_select_n<lt::compare4max>(*libtensor_ptr(), n, unique_by_symmetry);
24✔
1360
}
1361

1362
template <size_t N>
1363
std::vector<std::pair<std::vector<size_t>, scalar_type>> TensorImpl<N>::select_n_min(
3,384✔
1364
      size_t n, bool unique_by_symmetry) const {
1365
  return execute_select_n<lt::compare4min>(*libtensor_ptr(), n, unique_by_symmetry);
6,768✔
1366
}
1367

1368
template <size_t N>
1369
void TensorImpl<N>::export_to(scalar_type* memptr, size_t size) const {
64,542✔
1370
  if (this->size() != size) {
64,542✔
1371
    throw invalid_argument("The memory provided (== " + std::to_string(size) +
×
1372
                           ") does not agree with the number of tensor elements (== " +
1373
                           std::to_string(this->size()) + ")");
1374
  }
1375
  lt::btod_export<N>(*libtensor_ptr()).perform(memptr);
64,542✔
1376
}
64,542✔
1377

1378
template <size_t N>
1379
void TensorImpl<N>::import_from(const scalar_type* memptr, size_t size,
9,762✔
1380
                                scalar_type tolerance, bool symmetry_check) {
1381
  if (this->size() != size) {
9,762✔
1382
    throw invalid_argument("The memory size provided (== " + std::to_string(size) +
×
1383
                           ") does not agree with the number of tensor elements (== " +
1384
                           std::to_string(this->size()) + ")");
1385
  }
1386

1387
  if (symmetry_check) {
9,762✔
1388
    // Slow algorithm with symmetry check (via libtensor)
1389

1390
    // Zero this memory.
1391
    // TODO Check this really needs to be done for proper functioning
1392
    //      There is some indication that yes, unfortunately.
1393
    lt::btod_set<N>(0.0).perform(*libtensor_ptr());
9,762✔
1394

1395
    scalar_type* noconst_mem = const_cast<scalar_type*>(memptr);
9,762✔
1396
    libtensor::btod_import_raw<N>(noconst_mem, libtensor_ptr()->get_bis().get_dims(),
19,524✔
1397
                                  tolerance)
1398
          .perform(*libtensor_ptr());
9,762✔
1399
  } else {
1400
    // Fast algorithm without symmetry check (via import_from with generator)
1401
    auto fast_importer = [this, memptr](
×
1402
                               const std::vector<std::pair<size_t, size_t>>& range,
1403
                               scalar_type* ptr) {
1404
      if (range.size() != N) {
×
1405
        throw runtime_error("Internal error: Dimension mismatch in fast_importer");
×
1406
      }
1407

1408
      // Strides for accessing the full memptr
1409
      std::array<size_t, N> strides_full;
1410
      strides_full[N - 1] = 1;
×
1411
      for (size_t idim = N - 1; idim > 0; --idim) {
×
1412
        strides_full[idim - 1] = strides_full[idim] * shape()[idim];
×
1413
      }
1414

1415
      // Dimensionalities for access into the small ptr
1416
      std::array<size_t, N> dim_ptr;
1417
      for (size_t idim = 0; idim != N; ++idim) {
×
1418
        dim_ptr[idim] = range[idim].second - range[idim].first;
×
1419
      }
1420

1421
      // Strides for accessing the small ptr
1422
      std::array<size_t, N> strides_ptr;
1423
      strides_ptr[N - 1] = 1;
×
1424
      for (size_t idim = N - 1; idim > 0; --idim) {
×
1425
        strides_ptr[idim - 1] = strides_ptr[idim] * dim_ptr[idim];
×
1426
      }
1427
      const size_t size_ptr = strides_ptr[0] * dim_ptr[0];
×
1428

1429
      for (size_t iabs = 0; iabs < size_ptr; ++iabs) {
×
1430
        size_t full_abs = 0;
×
1431
        for (size_t idim = 0; idim != N; ++idim) {
×
1432
          size_t idx = range[idim].first + (iabs / strides_ptr[idim]) % dim_ptr[idim];
×
1433
          full_abs += strides_full[idim] * idx;
×
1434
        }
1435
        ptr[iabs] = memptr[full_abs];
×
1436
      }
1437
    };
1438
    import_from(fast_importer, tolerance, false);
×
1439
  }
1440
}
9,762✔
1441

1442
template <size_t N>
1443
void TensorImpl<N>::import_from(
11,848✔
1444
      std::function<void(const std::vector<std::pair<size_t, size_t>>&, scalar_type*)>
1445
            generator,
1446
      scalar_type tolerance, bool symmetry_check) {
1447

1448
  if (symmetry_check) {
11,848✔
1449
    // Slow algorithm with symmetry check (via import_from with libtensor)
1450

1451
    // Get complete data via generator ...
1452
    std::vector<std::pair<size_t, size_t>> full_range(N);
×
1453
    for (size_t i = 0; i < N; ++i) {
×
1454
      full_range[i].first  = 0;
×
1455
      full_range[i].second = shape()[i];
×
1456
    }
1457
    std::vector<scalar_type> data(this->size());
×
1458
    generator(full_range, data.data());
×
1459

1460
    // ... and import it
1461
    import_from(data, tolerance, true);
×
1462
  } else {
×
1463
    // Fast algorithm by only considering canonical blocks
1464
    // First zero out the tensor completely.
1465
    lt::block_tensor_ctrl<N, scalar_type> ctrl(*libtensor_ptr());
11,848✔
1466
    ctrl.req_zero_all_blocks();
11,848✔
1467

1468
    // Now loop over all orbits, which thus gives access to all
1469
    // canonical blocks
1470
    lt::orbit_list<N, scalar_type> orbitlist(ctrl.req_const_symmetry());
11,848✔
1471
    lt::index<N> blk_idx;  // Holder for canonical-block indices
11,848✔
1472
    for (auto it = orbitlist.begin(); it != orbitlist.end(); ++it) {
56,916✔
1473
      orbitlist.get_index(it, blk_idx);
45,068✔
1474

1475
      // Get the dense tensor for the current canonical block
1476
      // and import the data from the generator
1477
      lt::dense_tensor_wr_i<N, scalar_type>& blk = ctrl.req_block(blk_idx);
45,068✔
1478

1479
      // Compute range to import
1480
      std::vector<std::pair<size_t, size_t>> range(N);
45,068✔
1481
      const lt::block_index_space<N>& bis = libtensor_ptr()->get_bis();
45,068✔
1482
      lt::index<N> blk_start(bis.get_block_start(blk_idx));
45,068✔
1483
      lt::dimensions<N> blk_dims(bis.get_block_dims(blk_idx));
45,068✔
1484
      for (size_t i = 0; i < N; ++i) {
196,152✔
1485
        range[i].first  = blk_start[i];
151,084✔
1486
        range[i].second = blk_start[i] + blk_dims[i];
151,084✔
1487
      }
1488

1489
      bool all_zero = false;
45,068✔
1490
      {
1491
        lt::dense_tensor_wr_ctrl<N, scalar_type> cblk(blk);
45,068✔
1492
        cblk.req_prefetch();
45,068✔
1493
        scalar_type* ptr = cblk.req_dataptr();
45,068✔
1494

1495
        generator(range, ptr);  // Import data from the generator
45,068✔
1496

1497
        if (tolerance > 0) {
45,068✔
1498
          // Check that some are non-zero
1499
          all_zero = std::all_of(
45,068✔
1500
                ptr, ptr + blk_dims.get_size(),
45,068✔
1501
                [tolerance](scalar_type e) { return std::abs(e) < tolerance; });
2,880,094✔
1502
        }
1503

1504
        cblk.ret_dataptr(ptr);
45,068✔
1505
      }
45,068✔
1506
      ctrl.ret_block(blk_idx);
45,068✔
1507

1508
      if (all_zero) {
45,068✔
1509
        ctrl.req_zero_block(blk_idx);
21,400✔
1510
      }
1511
    }
1512
  }
11,848✔
1513
}
11,848✔
1514

1515
template <size_t N>
1516
std::string TensorImpl<N>::describe_symmetry() const {
56✔
1517
  std::stringstream ss;
56✔
1518

1519
  lt::block_tensor_ctrl<N, scalar_type> ctrl(*libtensor_ptr());
56✔
1520
  ss << ctrl.req_const_symmetry();
56✔
1521

1522
  return ss.str();
112✔
1523
}
56✔
1524

1525
template <size_t N>
1526
std::string TensorImpl<N>::describe_expression(std::string stage) const {
×
1527
  if (needs_evaluation()) {
×
1528
    std::stringstream ss;
×
1529
    if (stage == "unoptimised") {
×
1530
      ss << *m_expr_ptr;
×
1531
    } else if (stage == "optimised") {
×
1532
      lt::expr::print_tree(m_expr_ptr->optimised_tree(), ss, 2);
×
1533
    } else if (stage == "evaluation") {
×
1534
      auto newtensor_ptr =
×
1535
            std::make_shared<lt::btensor<N, scalar_type>>(as_bispace<N>(m_axes));
×
1536
      lt::expr::expr_tree evaltree =
×
1537
            m_expr_ptr->evaluation_tree(*newtensor_ptr, /* add = */ false);
×
1538
      lt::expr::print_tree(evaltree, ss, 2);
×
1539
    } else {
×
1540
      throw invalid_argument("Stage " + stage +
×
1541
                             " not valid for describe_expression. Try 'unoptimised', "
1542
                             "'optimised', 'evaluation' or 'evaluation'");
1543
    }
1544
    return ss.str();
×
1545
  } else {
×
1546
    return "btensor of shape " + shape_to_string(m_shape);
×
1547
  }
1548
}
1549

1550
template <size_t N>
1551
std::shared_ptr<ExpressionTree> TensorImpl<N>::expression_ptr() const {
6,727,314✔
1552
  if (m_expr_ptr != nullptr) {
6,727,314✔
1553
    if (m_libtensor_ptr != nullptr) {
4,428,491✔
1554
      throw runtime_error(
×
1555
            "Internal error: m_libtensor_ptr is not a nullptr and neither is "
1556
            "m_expr_ptr.");
1557
    }
1558
    return m_expr_ptr;  // Already have an expression for myself, just return it
4,428,491✔
1559
  } else {
1560
    if (m_libtensor_ptr == nullptr) {
2,298,823✔
1561
      throw runtime_error(
×
1562
            "Internal error: Both m_libtensor_ptr and m_expr_ptr are nullptrs.");
1563
    }
1564

1565
    // Make a new expression tree with just this tensor in it
1566
    return std::make_shared<ExpressionTree>(
6,896,469✔
1567
          lt::expr::node_ident_any_tensor<N, scalar_type>(*m_libtensor_ptr),
4,597,646✔
1568
          identity_permutation(N), std::vector<std::shared_ptr<void>>{m_libtensor_ptr});
6,896,469✔
1569
  }
1570
}
2,298,823✔
1571

1572
std::shared_ptr<ExpressionTree> as_expression(const std::shared_ptr<Tensor>& tensor) {
2,030,754✔
1573
  std::shared_ptr<ExpressionTree> ret;
2,030,754✔
1574
  if (tensor->ndim() == 1) {
2,030,754✔
1575
    ret = std::static_pointer_cast<TensorImpl<1>>(tensor)->expression_ptr();
9,002✔
1576
  } else if (tensor->ndim() == 2) {
2,021,752✔
1577
    ret = std::static_pointer_cast<TensorImpl<2>>(tensor)->expression_ptr();
765,198✔
1578
  } else if (tensor->ndim() == 3) {
1,256,554✔
1579
    ret = std::static_pointer_cast<TensorImpl<3>>(tensor)->expression_ptr();
184✔
1580
  } else if (tensor->ndim() == 4) {
1,256,370✔
1581
    ret = std::static_pointer_cast<TensorImpl<4>>(tensor)->expression_ptr();
1,197,414✔
1582
  } else if (tensor->ndim() == 5) {
58,956✔
1583
    ret = std::static_pointer_cast<TensorImpl<5>>(tensor)->expression_ptr();
×
1584
  } else if (tensor->ndim() == 6) {
58,956✔
1585
    ret = std::static_pointer_cast<TensorImpl<6>>(tensor)->expression_ptr();
58,956✔
1586
  } else {
1587
    throw not_implemented_error("Only implemented for dimensionality <= 6.");
×
1588
  }
1589

1590
  if (ret->permutation.size() != tensor->ndim()) {
2,030,754✔
1591
    throw runtime_error("Internal error: Mismatch between permutation.size() == " +
×
1592
                        std::to_string(ret->permutation.size()) +
×
1593
                        " and tensor dimensionality " + std::to_string(tensor->ndim()) +
×
1594
                        ".");
×
1595
  }
1596

1597
  return ret;
2,030,754✔
1598
}
×
1599

1600
template <size_t N>
1601
lt::btensor<N, scalar_type>& as_btensor(const std::shared_ptr<Tensor>& in) {
2,456,094✔
1602
  if (N != in->ndim()) {
2,456,094✔
1603
    throw dimension_mismatch("Requested dimensionality " + std::to_string(N) +
×
1604
                             ", but passed tensor has dimensionality " +
1605
                             std::to_string(in->ndim()));
1606
  }
1607
  return static_cast<lt::btensor<N, scalar_type>&>(
2,456,094✔
1608
        *std::static_pointer_cast<TensorImpl<N>>(in));
4,912,188✔
1609
}
1610

1611
template <size_t N>
1612
std::shared_ptr<lt::btensor<N, scalar_type>> as_btensor_ptr(
×
1613
      const std::shared_ptr<Tensor>& in) {
1614
  if (N != in->ndim()) {
×
1615
    throw dimension_mismatch("Requested dimensionality " + std::to_string(N) +
×
1616
                             ", but passed tensor has dimensionality " +
1617
                             std::to_string(in->ndim()));
1618
  }
1619
  return std::static_pointer_cast<TensorImpl<N>>(in)->libtensor_ptr();
×
1620
}
1621

1622
namespace {
1623
template <size_t N>
1624
std::shared_ptr<Tensor> make_tensor_inner(std::shared_ptr<Symmetry> symmetry) {
72,432✔
1625
  auto ltsym_ptr = as_lt_symmetry<N>(*symmetry);
72,432✔
1626
  auto newtensor_ptr =
72,432✔
1627
        std::make_shared<lt::btensor<N, scalar_type>>(ltsym_ptr->get_bis());
1628

1629
  lt::block_tensor_ctrl<N, scalar_type> ctrl_to(*newtensor_ptr);
72,432✔
1630
  lt::so_copy<N, scalar_type>(*ltsym_ptr).perform(ctrl_to.req_symmetry());
72,432✔
1631
  return std::make_shared<TensorImpl<N>>(symmetry->adcmem_ptr(), symmetry->axes(),
1632
                                         std::move(newtensor_ptr));
144,864✔
1633
}
72,432✔
1634
}  // namespace
1635

1636
std::shared_ptr<Tensor> make_tensor(std::shared_ptr<Symmetry> symmetry) {
72,432✔
1637
  if (symmetry->ndim() == 1) {
72,432✔
1638
    return make_tensor_inner<1>(symmetry);
1,188✔
1639
  } else if (symmetry->ndim() == 2) {
71,244✔
1640
    return make_tensor_inner<2>(symmetry);
47,692✔
1641
  } else if (symmetry->ndim() == 3) {
23,552✔
1642
    return make_tensor_inner<3>(symmetry);
×
1643
  } else if (symmetry->ndim() == 4) {
23,552✔
1644
    return make_tensor_inner<4>(symmetry);
20,976✔
1645
  } else if (symmetry->ndim() == 5) {
2,576✔
1646
    return make_tensor_inner<5>(symmetry);
×
1647
  } else if (symmetry->ndim() == 6) {
2,576✔
1648
    return make_tensor_inner<6>(symmetry);
2,576✔
1649
  } else {
1650
    throw not_implemented_error("Only implemented for dimensionality <= 6.");
×
1651
  }
1652
}
1653

1654
std::shared_ptr<Tensor> make_tensor(std::shared_ptr<const AdcMemory> adcmem_ptr,
32✔
1655
                                    std::vector<AxisInfo> axes) {
1656
  if (axes.size() == 1) {
32✔
1657
    return std::make_shared<TensorImpl<1>>(adcmem_ptr, axes);
×
1658
  } else if (axes.size() == 2) {
32✔
1659
    return std::make_shared<TensorImpl<2>>(adcmem_ptr, axes);
32✔
1660
  } else if (axes.size() == 3) {
×
1661
    return std::make_shared<TensorImpl<3>>(adcmem_ptr, axes);
×
1662
  } else if (axes.size() == 4) {
×
1663
    return std::make_shared<TensorImpl<4>>(adcmem_ptr, axes);
×
1664
  } else if (axes.size() == 5) {
×
1665
    return std::make_shared<TensorImpl<5>>(adcmem_ptr, axes);
×
1666
  } else if (axes.size() == 6) {
×
1667
    return std::make_shared<TensorImpl<6>>(adcmem_ptr, axes);
×
1668
  } else {
1669
    throw not_implemented_error("Only implemented for dimensionality <= 6.");
×
1670
  }
1671
}
1672

1673
//
1674
// Explicit instantiation
1675
//
1676

1677
#define INSTANTIATE(DIM)                                                                 \
1678
  template class TensorImpl<DIM>;                                                        \
1679
                                                                                         \
1680
  template lt::btensor<DIM, scalar_type>& as_btensor(const std::shared_ptr<Tensor>& in); \
1681
                                                                                         \
1682
  template std::shared_ptr<lt::btensor<DIM, scalar_type>> as_btensor_ptr(                \
1683
        const std::shared_ptr<Tensor>& in);
1684

1685
INSTANTIATE(1)
1686
INSTANTIATE(2)
1687
INSTANTIATE(3)
1688
INSTANTIATE(4)
1689
INSTANTIATE(5)
1690
INSTANTIATE(6)
1691

1692
#undef INSTANTIATE
1693

1694
}  // 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