• 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

79.46
/libadcc_src/pyiface/export_Tensor.cc
1
//
2
// Copyright (C) 2018 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 "../Tensor.hh"
21
#include "../exceptions.hh"
22
#include "util.hh"
23
#include <pybind11/numpy.h>
24
#include <pybind11/pybind11.h>
25
#include <sstream>
26

27
namespace libadcc {
28

29
namespace py = pybind11;
30
using namespace pybind11::literals;
31
typedef std::shared_ptr<Tensor> ten_ptr;
32

33
static std::vector<std::vector<size_t>> parse_permutations(
72,382✔
34
      const py::iterable& permutations) {
35
  bool iterator_of_ints = true;
72,382✔
36
  for (auto tpl : permutations) {
181,784✔
37
    if (!py::isinstance<py::int_>(tpl)) {
127,091✔
38
      iterator_of_ints = false;
17,689✔
39
      break;
17,689✔
40
    }
41
  }
72,382✔
42

43
  if (iterator_of_ints) {
72,382✔
44
    std::vector<size_t> perms;
54,693✔
45
    for (auto itm : permutations) {
164,095✔
46
      perms.push_back(itm.cast<size_t>());
109,402✔
47
    }
54,693✔
48
    return std::vector<std::vector<size_t>>{perms};
164,079✔
49
  }
54,693✔
50

51
  std::vector<std::vector<size_t>> vec_perms;
17,689✔
52
  for (auto tpl : permutations) {
42,984✔
53
    std::vector<size_t> perms;
25,295✔
54
    for (auto itm : tpl) {
75,885✔
55
      perms.push_back(itm.cast<size_t>());
50,590✔
56
    }
25,295✔
57
    vec_perms.push_back(perms);
25,295✔
58
  }
42,984✔
59
  return vec_perms;
17,689✔
60
}
72,382✔
61

62
static std::vector<size_t> convert_index_tuple(const ten_ptr& self, py::tuple idcs) {
967,057✔
63
  if (idcs.size() != self->ndim()) {
967,057✔
64
    throw py::value_error(
65
          "Number of elements passed in index tuple (== " + std::to_string(idcs.size()) +
×
66
          ") and dimensionality of tensor (== " + std::to_string(self->ndim()) +
×
67
          ") do not agree. Note, that at the moment any kind of slicing operation "
68
          "(including partial slicing) are not yet implemented.");
×
69
  }
70
  const std::vector<size_t> shape = self->shape();
967,057✔
71
  std::vector<size_t> ret(idcs.size());
967,057✔
72
  for (size_t i = 0; i < idcs.size(); ++i) {
4,777,895✔
73
    ptrdiff_t idx;
74

75
    try {
76
      idx = idcs[i].cast<ptrdiff_t>();
3,810,838✔
77
    } catch (const py::cast_error& c) {
×
78
      throw py::cast_error(
79
            "Right now only integer indices are supported. Any kind of slicing operation "
80
            "(including partial slicing) are not yet implemented.");
×
81
    }
×
82
    if (idx < 0) {
3,810,838✔
83
      auto si = static_cast<size_t>(-idx);
×
84
      if (si > shape[i]) {
×
85
        throw py::index_error("index " + std::to_string(idx) +
×
86
                              " is out of bounds for axis " + std::to_string(i) +
×
87
                              " with size " + std::to_string(shape[i]));
×
88
      }
89
      ret[i] = shape[i] - si;
×
90
    } else {
91
      auto si = static_cast<size_t>(idx);
3,810,838✔
92
      if (si > shape[i]) {
3,810,838✔
93
        throw py::index_error("index " + std::to_string(idx) +
×
94
                              " is out of bounds for axis " + std::to_string(i) +
×
95
                              " with size " + std::to_string(shape[i]));
×
96
      }
97
      ret[i] = si;
3,810,838✔
98
    }
99
  }
100
  return ret;
1,934,114✔
101
}
967,057✔
102

103
//
104
// Extra defs
105
//
106
static py::tuple Tensor_shape(const Tensor& self) { return shape_tuple(self.shape()); }
1,771,166✔
107

108
static py::array_t<scalar_type> Tensor_to_ndarray(const Tensor& self) {
28,457✔
109
  // Get an empty array of the required shape and export the data into it.
110
  py::array_t<scalar_type> res(self.shape());
28,457✔
111
  self.export_to(res.mutable_data(), self.size());
28,457✔
112
  return res;
28,457✔
113
}
×
114

115
static py::array_t<scalar_type> Tensor_export_block(const Tensor& self,
3,163✔
116
                                                    std::vector<size_t> start,
117
                                                    std::vector<size_t> end) {
118
  const size_t ndim = self.ndim();
3,163✔
119
  if (start.size() != ndim || end.size() != ndim) {
3,163✔
120
    throw invalid_argument("start and end must each have one entry per tensor axis.");
1✔
121
  }
122
  std::vector<py::ssize_t> ext(ndim);
3,162✔
123
  size_t total = 1;
3,162✔
124
  for (size_t i = 0; i < ndim; ++i) {
15,810✔
125
    if (end[i] < start[i]) {
12,648✔
NEW
126
      throw invalid_argument("end must not be smaller than start along any axis.");
×
127
    }
128
    ext[i] = static_cast<py::ssize_t>(end[i] - start[i]);
12,648✔
129
    total *= (end[i] - start[i]);
12,648✔
130
  }
131
  py::array_t<scalar_type> res(ext);
3,162✔
132
  self.export_block(start, end, res.mutable_data(), total);
3,162✔
133
  return res;
6,322✔
134
}
3,163✔
135

136
static ten_ptr Tensor_from_ndarray_tol(
4,857✔
137
      ten_ptr self,
138
      py::array_t<scalar_type, py::array::c_style | py::array::forcecast> in_array,
139
      double symmetry_tolerance) {
140
  py::ssize_t nd = in_array.ndim();
4,857✔
141
  if (nd < 1) throw invalid_argument("Cannot import from 0D array.");
4,857✔
142

143
  py::ssize_t pysize = 1;
4,857✔
144
  for (py::ssize_t i = 0; i < nd; ++i) pysize *= in_array.shape(i);
14,807✔
145

146
  const size_t size = static_cast<size_t>(pysize);
4,857✔
147
  self->import_from(in_array.data(), size, symmetry_tolerance);
4,857✔
148
  return self;
4,857✔
149
}
150

151
static ten_ptr Tensor_from_ndarray(ten_ptr self, py::array in_array) {
176✔
152
  return Tensor_from_ndarray_tol(self, in_array, 0.0);
176✔
153
}
154

155
static ten_ptr Tensor_set_random(ten_ptr self) {
914✔
156
  self->set_random();
914✔
157
  return self;
914✔
158
}
159

160
static scalar_type Tensor_dot(const Tensor& self, ten_ptr other) {
402,848✔
161
  return self.dot({other})[0];
1,208,544✔
162
}
402,848✔
163

164
static py::array_t<scalar_type> Tensor_dot_list(const Tensor& self, py::list tensors) {
30,559✔
165
  std::vector<ten_ptr> parsed   = extract_tensors(tensors);
30,559✔
166
  std::vector<scalar_type> dots = self.dot(parsed);
30,559✔
167
  py::array_t<scalar_type> ret(dots.size());
30,559✔
168
  std::copy(dots.begin(), dots.end(), ret.mutable_data());
30,559✔
169

170
  return ret;
61,118✔
171
}
30,559✔
172

173
static ten_ptr Tensor_transpose_1(const Tensor& self) {
5,537✔
174
  std::vector<size_t> vec_axes(self.ndim());
5,537✔
175
  for (size_t i = 0; i < self.ndim(); ++i) {
16,611✔
176
    vec_axes[self.ndim() - i - 1] = i;
11,074✔
177
  }
178
  return self.transpose(vec_axes);
11,074✔
179
}
5,537✔
180

181
static ten_ptr Tensor_transpose_2(const Tensor& self, py::tuple axes) {
253,351✔
182
  std::vector<size_t> vec_axes(py::len(axes));
253,351✔
183
  for (size_t i = 0; i < py::len(axes); ++i) {
940,413✔
184
    vec_axes[i] = axes[i].cast<size_t>();
687,062✔
185
  }
186
  return self.transpose(vec_axes);
506,702✔
187
}
253,351✔
188

189
static ten_ptr Tensor_symmetrise_1(const Tensor& self, py::list permutations) {
7,305✔
190
  return self.symmetrise(parse_permutations(permutations));
7,305✔
191
}
192

193
static ten_ptr Tensor_symmetrise_2(const Tensor& self, py::args permutations) {
5,803✔
194
  if (py::len(permutations) == 0) {
5,803✔
195
    if (self.ndim() != 2) {
3,212✔
196
      throw invalid_argument(
197
            "symmetrise without arguments may only be used for matrices.");
×
198
    }
199
    return self.symmetrise({{0, 1}});
9,636✔
200
  }
201
  return self.symmetrise(parse_permutations(permutations));
2,591✔
202
}
9,636✔
203

204
static ten_ptr Tensor_antisymmetrise_1(const Tensor& self, py::list permutations) {
9,027✔
205
  return self.antisymmetrise(parse_permutations(permutations));
9,027✔
206
}
207

208
static ten_ptr Tensor_antisymmetrise_2(const Tensor& self, py::args permutations) {
53,479✔
209
  if (py::len(permutations) == 0) {
53,479✔
210
    if (self.ndim() != 2) {
20✔
211
      throw invalid_argument(
212
            "antisymmetrise without arguments may only be used for matrices.");
×
213
    }
214
    return self.antisymmetrise({{0, 1}});
60✔
215
  }
216
  return self.antisymmetrise(parse_permutations(permutations));
53,459✔
217
}
60✔
218

219
static py::object tensordot_1(ten_ptr a, ten_ptr b, py::iterable axes) {
229,034✔
220
  if (py::len(axes) != 2) {
229,034✔
221
    throw invalid_argument("axes needs to be an iterable of length 2");
×
222
  }
223
  std::vector<std::vector<size_t>> c_axes;
229,034✔
224
  for (py::handle ax : axes) {
1,145,170✔
225
    std::vector<size_t> res;
458,068✔
226
    for (py::handle elem : ax.cast<py::iterable>()) {
1,244,656✔
227
      res.push_back(elem.cast<size_t>());
786,588✔
228
    }
458,068✔
229
    c_axes.push_back(res);
458,068✔
230
  }
687,102✔
231

232
  TensorOrScalar res = a->tensordot(b, {c_axes[0], c_axes[1]});
229,034✔
233
  if (res.tensor_ptr == nullptr) {
229,034✔
234
    return py::cast(res.scalar);
448✔
235
  } else {
236
    return py::cast(res.tensor_ptr);
228,586✔
237
  }
238
}
229,034✔
239
static py::object tensordot_2(ten_ptr a, ten_ptr b, size_t axes) {
×
240
  std::vector<size_t> a_axes;
×
241
  std::vector<size_t> b_axes;
×
242
  for (size_t i = 0; i < axes; ++i) {
×
243
    a_axes.push_back(a->ndim() - axes + i);
×
244
    b_axes.push_back(i);
×
245
  }
246

247
  TensorOrScalar res = a->tensordot(b, {a_axes, b_axes});
×
248
  if (res.tensor_ptr == nullptr) {
×
249
    return py::cast(res.scalar);
×
250
  } else {
251
    return py::cast(res.tensor_ptr);
×
252
  }
253
}
×
254

255
static py::object tensordot_3(ten_ptr a, ten_ptr b) { return tensordot_2(a, b, 2); }
×
256

257
static ten_ptr Tensor_diagonal(ten_ptr ten, py::args permutations) {
11,282✔
258
  std::vector<size_t> axes;
11,282✔
259
  if (py::len(permutations) == 0) {
11,282✔
260
    axes.push_back(0);
6,435✔
261
    axes.push_back(1);
6,435✔
262
  } else {
263
    for (auto itm : permutations) axes.push_back(itm.cast<size_t>());
14,541✔
264
  }
265
  return ten->diagonal(axes);
22,564✔
266
}
11,282✔
267

268
static ten_ptr direct_sum(ten_ptr a, ten_ptr b) { return a->direct_sum(b); }
5,811✔
269

270
static double Tensor_trace_1(std::string subscripts, const Tensor& tensor) {
1✔
271
  return tensor.trace(subscripts);
1✔
272
}
273
static double Tensor_trace_2(const Tensor& tensor) {
×
274
  if (tensor.ndim() != 2) {
×
275
    throw invalid_argument(
276
          "trace function without arguments may only be used for matrices.");
×
277
  }
278
  return tensor.trace("ii");
×
279
}
280

281
static ten_ptr linear_combination_strict(
40,856✔
282
      py::array_t<scalar_type, py::array::c_style> coefficients, py::list tensors) {
283

284
  if (coefficients.ndim() != 1) {
40,856✔
285
    throw invalid_argument("coefficients array needs to have exactly one dimension.");
×
286
  }
287
  size_t in_size             = static_cast<size_t>(coefficients.shape(0));
40,856✔
288
  const scalar_type* in_data = coefficients.data();
40,856✔
289
  std::vector<scalar_type> scalars(in_size);
40,856✔
290
  std::copy(in_data, in_data + in_size, scalars.data());
40,856✔
291
  std::vector<ten_ptr> parsed = extract_tensors(tensors);
40,856✔
292

293
  auto ret = parsed[0]->zeros_like();
40,856✔
294
  ret->add_linear_combination(scalars, parsed);
40,856✔
295
  return ret;
81,712✔
296
}
40,856✔
297

298
//
299
// Element access
300
//
301

302
static py::list Tensor_select_n_min(const ten_ptr& self, size_t n) {
1,489✔
303
  std::vector<std::pair<std::vector<size_t>, scalar_type>> ret = self->select_n_min(n);
1,489✔
304
  py::list li;
1,489✔
305
  for (auto p : ret) li.append(py::make_tuple(p.first, p.second));
23,526✔
306
  return li;
2,978✔
307
}
1,489✔
308

309
static py::list Tensor_select_n_max(const ten_ptr& self, size_t n) {
×
310
  std::vector<std::pair<std::vector<size_t>, scalar_type>> ret = self->select_n_max(n);
×
311
  py::list li;
×
312
  for (auto p : ret) li.append(py::make_tuple(p.first, p.second));
×
313
  return li;
×
314
}
×
315

316
static py::list Tensor_select_n_absmin(const ten_ptr& self, size_t n) {
×
317
  std::vector<std::pair<std::vector<size_t>, scalar_type>> ret = self->select_n_absmin(n);
×
318
  py::list li;
×
319
  for (auto p : ret) li.append(py::make_tuple(p.first, p.second));
×
320
  return li;
×
321
}
×
322

323
static py::list Tensor_select_n_absmax(const ten_ptr& self, size_t n) {
×
324
  std::vector<std::pair<std::vector<size_t>, scalar_type>> ret = self->select_n_absmax(n);
×
325
  py::list li;
×
326
  for (auto p : ret) li.append(py::make_tuple(p.first, p.second));
×
327
  return li;
×
328
}
×
329

330
static bool Tensor_is_allowed(const ten_ptr& self, py::tuple idcs) {
22,037✔
331
  return self->is_element_allowed(convert_index_tuple(self, idcs));
22,037✔
332
}
333

334
static scalar_type Tensor__getitem__(const ten_ptr& self, py::tuple idcs) {
930,096✔
335
  return self->get_element(convert_index_tuple(self, idcs));
930,096✔
336
}
337

338
static scalar_type Tensor__setitem__(const ten_ptr& self, py::tuple idcs,
14,924✔
339
                                     scalar_type value) {
340
  self->set_element(convert_index_tuple(self, idcs), value);
14,924✔
341
  return value;
14,924✔
342
}
343

344
//
345
// Implementation of python-side special functions
346
//    See https://docs.python.org/3/library/operator.html for details
347
//
348

349
static py::object Tensor___str__(const Tensor& self) {
×
350
  if (self.size() < 50000) {
×
351
    return py::str(Tensor_to_ndarray(self));
×
352
  } else {
353
    std::stringstream ss;
×
354
    ss << "Tensor shape (";
×
355
    for (size_t i = 0; i < self.ndim(); ++i) {
×
356
      ss << self.shape()[i];
×
357
      if (i != self.ndim() - 1) ss << ", ";
×
358
    }
359
    ss << ")";
×
360
    return py::cast(ss.str());
×
361
  }
×
362
}
363

364
static py::object Tensor___repr__(const Tensor& self) {
×
365
  // TODO extremely rudimentary information for now
366
  //      goal would be an unambiguous representation instead
367
  return Tensor___str__(self);
×
368
}
369

370
//
371
// Operations with a scalar
372
//
373
static ten_ptr Tensor_scalar__imul__(ten_ptr self, scalar_type number) {
322✔
374
  return self = self->scale(number);
322✔
375
}
376

377
static ten_ptr Tensor_scalar__mul__(const ten_ptr& self, scalar_type number) {
186,970✔
378
  return self->scale(number);
186,970✔
379
}
380

381
static ten_ptr Tensor_scalar__itruediv__(ten_ptr& self, scalar_type number) {
10✔
382
  return self = self->scale(1. / number);
10✔
383
}
384

385
static ten_ptr Tensor_scalar__truediv__(const ten_ptr& self, scalar_type number) {
24,509✔
386
  return self->scale(1. / number);
24,509✔
387
}
388

389
static ten_ptr Tensor_scalar__add__(const ten_ptr& self, scalar_type number) {
50,636✔
390
  if (number == 0) {
50,636✔
391
    return self;
50,468✔
392
  } else {
393
    // TODO I know there are more efficient ways to do this in libtensor,
394
    //      but they are not yet exported all the way up
395
    auto other = self->empty_like();
168✔
396
    other->fill(number);
168✔
397
    return self->add(other);
168✔
398
  }
168✔
399
}
400

401
static ten_ptr Tensor_scalar__sub__(const ten_ptr& self, scalar_type number) {
16,490✔
402
  if (number == 0) {
16,490✔
403
    return self;
2,007✔
404
  } else {
405
    // TODO I know there are more efficient ways to do this in libtensor,
406
    //      but they are not yet exported all the way up
407
    auto other = self->empty_like();
14,483✔
408
    other->fill(-number);
14,483✔
409
    return self->add(other);
14,483✔
410
  }
14,483✔
411
}
412

413
//
414
// Operations with another tensor
415
//
416

417
static ten_ptr Tensor__iadd__(ten_ptr self, const ten_ptr& other) {
9,072✔
418
  return self = self->add(other);
9,072✔
419
}
420

421
static ten_ptr Tensor__add__(const ten_ptr& self, const ten_ptr& other) {
89,257✔
422
  return self->add(other);
89,257✔
423
}
424

425
static ten_ptr Tensor__isub__(ten_ptr self, const ten_ptr& other) {
1,494✔
426
  return self = self->add(other->scale(-1.0));
1,494✔
427
}
428

429
static ten_ptr Tensor__sub__(const ten_ptr& self, const ten_ptr& other) {
82,267✔
430
  return self->add(other->scale(-1.0));
82,267✔
431
}
432

433
static ten_ptr Tensor__mul__(const ten_ptr& self, const ten_ptr& other) {
1,714✔
434
  return self->multiply(other);
1,714✔
435
}
436

437
static ten_ptr Tensor__truediv__(const ten_ptr& self, const ten_ptr& other) {
19,813✔
438
  return self->divide(other);
19,813✔
439
}
440

441
static ten_ptr Tensor__matmul__(const ten_ptr& self, const ten_ptr& other) {
7,350✔
442
  return self->tensordot(other, {{1}, {0}}).tensor_ptr;
36,750✔
443
}
444

445
void export_Tensor(py::module& m) {
3✔
446
  py::class_<Tensor, std::shared_ptr<Tensor>>(
3✔
447
        m, "Tensor",
448
        "Class representing the Tensor objects used for computations in adcc")
449
        .def(py::init(&make_tensor_zero),
3✔
450
             "Construct a Tensor object using a Symmetry object describing its symmetry "
451
             "properties.\n"
452
             "The returned object is not guaranteed to contain initialised memory. "
453
             "Python binding to :cpp:class:`libadcc::Tensor`")
454
        .def_property_readonly("ndim", &Tensor::ndim)
3✔
455
        .def_property_readonly("shape", &Tensor_shape)
3✔
456
        .def_property_readonly("size", &Tensor::size)
3✔
457
        .def_property_readonly("space", &Tensor::space)
3✔
458
        .def_property_readonly("subspaces", &Tensor::subspaces)
3✔
459
        .def_property("flags", &Tensor::flags, &Tensor::set_flags)
3✔
460
        //
461
        .def_property_readonly("needs_evaluation", &Tensor::needs_evaluation,
3✔
462
                               "Does the tensor need evaluation or is it fully evaluated "
463
                               "and resilient in memory.")
464
        .def("evaluate", &evaluate,
3✔
465
             "Ensure the tensor to be fully evaluated and resilient in memory. Usually "
466
             "happens automatically when needed. Might be useful for fine-tuning, "
467
             "however.")
468
        .def_property_readonly("mutable", &Tensor::is_mutable)
3✔
469
        .def("set_immutable", &Tensor::set_immutable,
3✔
470
             "Set the tensor as immutable, allowing some optimisations to be performed.")
471
        //
472
        .def("empty_like", &Tensor::zeros_like)  // TODO used to be empty_like
3✔
473
        .def("zeros_like", &Tensor::zeros_like)
3✔
474
        .def("ones_like", &Tensor::ones_like)
3✔
475
        .def("nosym_like", &Tensor::nosym_like)
3✔
476
        .def("set_random", &Tensor_set_random,
3✔
477
             "Set all tensor elements to random data, adhering to the internal "
478
             "symmetry.")
479
        .def("set_mask", &Tensor::set_mask,
3✔
480
             "Set all elements corresponding to an index mask, which is given by a "
481
             "string eg. 'iijkli' sets elements T_{iijkli}")
482
        .def("diagonal", &Tensor_diagonal)
3✔
483
        .def("copy", &Tensor::copy, "Returns a deep copy of the tensor.")
3✔
484
        .def("dot", &Tensor_dot)
3✔
485
        .def("dot", &Tensor_dot_list)
3✔
486
        .def_property_readonly("T", &Tensor_transpose_1)
3✔
487
        .def("transpose", &Tensor_transpose_1)
3✔
488
        .def("transpose", &Tensor_transpose_2)
3✔
489
        .def("symmetrise", &Tensor_symmetrise_1)
3✔
490
        .def("symmetrise", &Tensor_symmetrise_2)
3✔
491
        .def("antisymmetrise", &Tensor_antisymmetrise_1)
3✔
492
        .def("antisymmetrise", &Tensor_antisymmetrise_2)
3✔
493
        .def("to_ndarray", &Tensor_to_ndarray,
3✔
494
             "Export the tensor data to a standard np::ndarray by making a copy.")
495
        .def("export_block", &Tensor_export_block, "start"_a, "end"_a,
3✔
496
             "Export a dense sub-block of the tensor (half-open per-axis range "
497
             "[start, end)) to a np::ndarray, respecting the full tensor "
498
             "symmetry, without materialising the full dense tensor.")
499
        .def("set_from_ndarray", &Tensor_from_ndarray,
3✔
500
             "Set all tensor elements from a standard np::ndarray by making a copy. "
501
             "Provide an optional tolerance argument to increase the tolerance for the "
502
             "check for symmetry consistency.")
503
        .def("set_from_ndarray", &Tensor_from_ndarray_tol,
3✔
504
             "Set all tensor elements from a standard np::ndarray by making a copy. "
505
             "Provide an optional tolerance argument to increase the tolerance for the "
506
             "check for symmetry consistency.")
507
        .def("describe_symmetry", &Tensor::describe_symmetry,
3✔
508
             "Return a string providing a hopefully descriptive representation of the "
509
             "symmetry information stored inside the tensor.")
510
        .def("describe_expression", &Tensor::describe_expression,
3✔
511
             "Return a string providing a hopefully descriptive representation of the "
512
             "tensor expression stored inside the object.")
513
        .def("describe_expression",
3✔
514
             [](ten_ptr t) { return t->describe_expression("unoptimised"); })
×
515
        //
516
        .def("__getitem__", &Tensor__getitem__,
3✔
517
             "Get a tensor element or a slice of tensor elements.")
518
        .def("__setitem__", &Tensor__setitem__,
3✔
519
             "Set a tensor element or a slice of tensor elements. The operation will "
520
             "adhere symmetry, i.e. alter all elements equivalent by symmetry at once.")
521
        .def("is_allowed", &Tensor_is_allowed,
3✔
522
             " Is a particular index allowed by symmetry")
523
        .def("select_n_absmax", &Tensor_select_n_absmax,
3✔
524
             "Select the n absolute maximal elements.")
525
        .def("select_n_absmin", &Tensor_select_n_absmin,
3✔
526
             "Select the n absolute minimal elements.")
527
        .def("select_n_max", &Tensor_select_n_max, "Select the n maximal elements.")
3✔
528
        .def("select_n_min", &Tensor_select_n_min, "Select the n minimal elements.")
3✔
529
        //
530
        .def("__len__", [](ten_ptr self) { return self->shape()[0]; })
3✔
531
        .def("__repr__", &Tensor___repr__)
3✔
532
        .def("__str__", &Tensor___str__)
3✔
533
        //
534
        .def("__pos__", [](ten_ptr self) { return self; })               // + tensor
33,803✔
535
        .def("__neg__", [](ten_ptr self) { return self->scale(-1.0); })  // - tensor
10,990✔
536
        .def("__add__", &Tensor_scalar__add__)            // tensor + scalar
3✔
537
        .def("__sub__", &Tensor_scalar__sub__)            // tensor - scalar
3✔
538
        .def("__radd__", &Tensor_scalar__add__)           // scalar + tensor
3✔
539
        .def("__rsub__", &Tensor_scalar__sub__)           // scalar - tensor
3✔
540
        .def("__imul__", &Tensor_scalar__imul__)          // tensor *= scalar
3✔
541
        .def("__mul__", &Tensor_scalar__mul__)            // tensor * scalar
3✔
542
        .def("__rmul__", &Tensor_scalar__mul__)           // scalar * tensor
3✔
543
        .def("__itruediv__", &Tensor_scalar__itruediv__)  // tensor /= scalar
3✔
544
        .def("__truediv__", &Tensor_scalar__truediv__)    // tensor / scalar
3✔
545
                                                          //
546
        .def("__mul__", &Tensor__mul__,
3✔
547
             "Multiply two tensors elementwise.")  // tensor * tensor
548
        .def("__truediv__", &Tensor__truediv__,
3✔
549
             "Divide two tensors elementwise.")  // tensor / tensor
550
        .def("__iadd__", &Tensor__iadd__)        // tensor += tensor
3✔
551
        .def("__add__", &Tensor__add__)          // tensor + tensor
3✔
552
        .def("__isub__", &Tensor__isub__)        // tensor -= tensor
3✔
553
        .def("__sub__", &Tensor__sub__)          // tensor - tensor
3✔
554
        //
555
        .def("__matmul__", &Tensor__matmul__)  // tensor @ tensor
3✔
556
        //
557
        ;
558

559
  m.def("evaluate", &evaluate);
3✔
560
  m.def("tensordot", &tensordot_1, "a"_a, "b"_a, "axes"_a);
3✔
561
  m.def("tensordot", &tensordot_2, "a"_a, "b"_a, "axes"_a);
3✔
562
  m.def("tensordot", &tensordot_3, "a"_a, "b"_a);
3✔
563
  m.def("direct_sum", &direct_sum, "a"_a, "b"_a);
3✔
564
  m.def("trace", &Tensor_trace_1, "subscripts"_a, "tensor"_a);
3✔
565
  m.def("trace", &Tensor_trace_2, "tensor"_a);
3✔
566
  m.def("linear_combination_strict", &linear_combination_strict, "coefficients"_a,
3✔
567
        "tensors"_a);
3✔
568
}
3✔
569

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