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

openmc-dev / openmc / 22286686330

22 Feb 2026 10:26PM UTC coverage: 81.639% (-0.05%) from 81.69%
22286686330

Pull #3808

github

web-flow
Merge c7a8090cd into 83a30f686
Pull Request #3808: Add properties to settings w/ documentation, c++ loading of filename, and python round-trip test

16819 of 23482 branches covered (71.63%)

Branch coverage included in aggregate %.

83 of 100 new or added lines in 8 files covered. (83.0%)

724 existing lines in 14 files now uncovered.

56872 of 66782 relevant lines covered (85.16%)

43142858.74 hits per line

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

97.3
/include/openmc/tensor.h
1
//! \file tensor.h
2
//! \brief Multi-dimensional tensor types for OpenMC.
3
//!
4
//! Tensor<T> is the primary type: a dynamic-rank owning container that stores
5
//! elements contiguously in row-major order.  View<T> is a lightweight
6
//! non-owning reference into a Tensor's storage, returned by the slice()
7
//! method and flat().  StaticTensor2D<T,R,C> is a small stack-allocated 2D
8
//! array used only for simulation::global_tallies.
9
//!
10
//! Slicing follows numpy conventions: each axis takes an index (rank-reducing),
11
//! All (keep entire axis), or Range (keep sub-range).  For example,
12
//! arr.slice(0, all, range(2, 5)) is equivalent to numpy's arr[0, :, 2:5].
13
//!
14
//! View is declared before Tensor because Tensor's methods return View objects.
15

16
#ifndef OPENMC_TENSOR_H
17
#define OPENMC_TENSOR_H
18

19
#include "openmc/vector.h"
20

21
#include <algorithm>
22
#include <array>
23
#include <cmath>
24
#include <cstddef>
25
#include <cstdint>
26
#include <initializer_list>
27
#include <limits>
28
#include <type_traits>
29

30
namespace openmc {
31
namespace tensor {
32

33
//==============================================================================
34
// Forward declarations
35
//==============================================================================
36

37
template<typename T>
38
class Tensor;
39

40
template<typename T, size_t R, size_t C>
41
class StaticTensor2D;
42

43
//==============================================================================
44
// Storage type mapping
45
//
46
// std::vector<bool> is a bit-packed specialization that returns proxy objects
47
// instead of real references, which breaks generic code.  storage_type_map
48
// redirects bool to unsigned char so that Tensor<bool> stores one byte per
49
// element with normal reference semantics.
50
//==============================================================================
51

52
template<typename T>
53
struct storage_type_map {
54
  using type = T;
55
};
56
template<>
57
struct storage_type_map<bool> {
58
  using type = unsigned char;
59
};
60
template<typename T>
61
using storage_type = typename storage_type_map<T>::type;
62

63
//==============================================================================
64
// Slice argument types
65
//
66
// Used with the variadic slice() method on Tensor, View, and StaticTensor2D.
67
// Each argument corresponds to one axis: a plain integer fixes that axis at
68
// a single index (rank-reducing), All keeps the entire axis, and Range keeps
69
// a sub-range.
70
//==============================================================================
71

72
//! Keep an entire axis (equivalent to numpy's ':' or xtensor's xt::all())
73
struct All {};
74
constexpr All all {};
75

76
//! Sub-range along an axis [start, end)
77
struct Range {
78
  size_t start;
79
  size_t end; // SIZE_MAX means "to end of axis"
80
};
81

82
//! Create a Range [start, end)
83
inline Range range(size_t start, size_t end)
135,198,427✔
84
{
85
  return {start, end};
135,198,427✔
86
}
87

88
//! Create a Range [0, end)
89
inline Range range(size_t end)
90
{
91
  return {0, end};
92
}
93

94
namespace detail {
95

96
//! Internal: normalized representation of a per-axis slice argument
97
struct SliceArg {
98
  enum Kind { INDEX, ALL, RANGE } kind;
99
  size_t start;
100
  size_t end;
101
};
102

103
inline SliceArg to_slice_arg(All)
121,989✔
104
{
105
  return {SliceArg::ALL, 0, 0};
121,989✔
106
}
107
inline SliceArg to_slice_arg(Range r)
135,198,427✔
108
{
109
  return {SliceArg::RANGE, r.start, r.end};
135,198,427✔
110
}
111

112
template<typename I>
113
inline
114
  typename std::enable_if<std::is_integral<I>::value || std::is_enum<I>::value,
115
    SliceArg>::type
116
  to_slice_arg(I i)
228,617,346✔
117
{
118
  return {SliceArg::INDEX, static_cast<size_t>(i), 0};
228,617,346✔
119
}
120

121
//! Result of a slice computation: pointer offset + new shape/strides
122
struct SliceResult {
123
  size_t ptr_offset;
124
  vector<size_t> shape;
125
  vector<size_t> strides;
126
};
127

128
//! Compute the result of applying slice arguments to shape/strides
129
template<typename First, typename... Rest>
130
SliceResult compute_slice(const vector<size_t>& shape,
228,608,022✔
131
  const vector<size_t>& strides, First first, Rest... rest)
132
{
133
  const size_t n = 1 + sizeof...(Rest);
228,608,022✔
134
  SliceArg args[1 + sizeof...(Rest)] = {
135
    to_slice_arg(first), to_slice_arg(rest)...};
228,608,022✔
136

137
  size_t offset = 0;
228,608,022✔
138
  vector<size_t> new_shape;
228,608,022✔
139
  vector<size_t> new_strides;
228,608,022✔
140

141
  for (size_t a = 0; a < n; ++a) {
592,545,784✔
142
    switch (args[a].kind) {
363,937,762!
143
    case SliceArg::INDEX:
228,617,346✔
144
      offset += args[a].start * strides[a];
228,617,346✔
145
      break;
228,617,346✔
146
    case SliceArg::ALL:
121,989✔
147
      new_shape.push_back(shape[a]);
121,989✔
148
      new_strides.push_back(strides[a]);
121,989✔
149
      break;
121,989✔
150
    case SliceArg::RANGE: {
135,198,427✔
151
      offset += args[a].start * strides[a];
135,198,427✔
152
      size_t end = (args[a].end == SIZE_MAX) ? shape[a] : args[a].end;
135,198,427!
153
      new_shape.push_back(end - args[a].start);
135,198,427✔
154
      new_strides.push_back(strides[a]);
135,198,427✔
155
      break;
135,198,427✔
156
    }
157
    }
158
  }
159

160
  // Trailing axes not covered by arguments are implicitly All.
161
  // This matches numpy: a[i] on a 2D array returns a 1D row.
162
  for (size_t a = n; a < shape.size(); ++a) {
321,934,960✔
163
    new_shape.push_back(shape[a]);
93,326,938✔
164
    new_strides.push_back(strides[a]);
93,326,938✔
165
  }
166

167
  return {offset, std::move(new_shape), std::move(new_strides)};
457,216,044✔
168
}
228,608,022✔
169

170
} // namespace detail
171

172
//==============================================================================
173
// View<T>: a non-owning N-dimensional view into a tensor's storage.
174
//
175
// Holds a base pointer, shape, and strides (in elements).  Supports arbitrary
176
// rank and multi-axis slicing via the variadic slice() method.
177
//==============================================================================
178

179
template<typename T>
180
class View {
181
public:
182
  //--------------------------------------------------------------------------
183
  // Constructors
184

185
  View(T* data, vector<size_t> shape, vector<size_t> strides)
228,608,022✔
186
    : data_(data), shape_(std::move(shape)), strides_(std::move(strides))
228,608,022✔
187
  {}
228,608,022✔
188

189
  // Explicitly default copy/move constructors (declaring copy assignment
190
  // below would otherwise suppress the implicit move constructor).
191
  View(const View&) = default;
192
  View(View&&) = default;
193

194
  //--------------------------------------------------------------------------
195
  // Indexing
196

197
  //! Multi-index element access (1D, 2D, 3D, ...)
198
  template<typename... Indices>
199
  T& operator()(Indices... indices)
982,930,432✔
200
  {
201
    const size_t idx[] = {static_cast<size_t>(indices)...};
982,930,432✔
202
    size_t off = 0;
982,930,432✔
203
    for (size_t d = 0; d < sizeof...(Indices); ++d)
1,965,860,864✔
204
      off += idx[d] * strides_[d];
982,930,432✔
205
    return data_[off];
982,930,432✔
206
  }
207

208
  template<typename... Indices>
209
  const T& operator()(Indices... indices) const
210
  {
211
    const size_t idx[] = {static_cast<size_t>(indices)...};
212
    size_t off = 0;
213
    for (size_t d = 0; d < sizeof...(Indices); ++d)
214
      off += idx[d] * strides_[d];
215
    return data_[off];
216
  }
217

218
  //! Flat logical index (row-major order)
219
  T& operator[](size_t i) { return data_[flat_to_offset(i)]; }
220
  const T& operator[](size_t i) const { return data_[flat_to_offset(i)]; }
1,199,725,970✔
221

222
  //--------------------------------------------------------------------------
223
  // Accessors
224

225
  size_t size() const
425,319,903✔
226
  {
227
    size_t s = 1;
425,319,903✔
228
    for (auto d : shape_)
850,718,470✔
229
      s *= d;
425,398,567✔
230
    return s;
425,319,903✔
231
  }
232
  size_t ndim() const { return shape_.size(); }
233
  size_t shape(size_t axis) const { return shape_[axis]; }
234
  const vector<size_t>& shape_vec() const { return shape_; }
18,189,244✔
235
  T* data() { return data_; }
236
  const T* data() const { return data_; }
237

238
  //--------------------------------------------------------------------------
239
  // View accessors
240

241
  //! Multi-axis slice.  Each argument corresponds to one axis and is either:
242
  //!   - an integer (fixes that axis, rank-reducing)
243
  //!   - All (keeps entire axis)
244
  //!   - Range (keeps sub-range along that axis)
245
  //! Example: v.slice(0, all, range(2, 5)) == numpy v[0, :, 2:5]
246
  template<typename First, typename... Rest>
247
  View<T> slice(First first, Rest... rest)
248
  {
249
    auto r = detail::compute_slice(shape_, strides_, first, rest...);
250
    return {data_ + r.ptr_offset, std::move(r.shape), std::move(r.strides)};
251
  }
252

253
  template<typename First, typename... Rest>
254
  View<const T> slice(First first, Rest... rest) const
255
  {
256
    auto r = detail::compute_slice(shape_, strides_, first, rest...);
257
    return {data_ + r.ptr_offset, std::move(r.shape), std::move(r.strides)};
258
  }
259

260
  //--------------------------------------------------------------------------
261
  // Assignment operators
262

263
  //! Copy assignment: element-wise deep copy (writes through data pointer).
264
  //! Without this, the compiler's implicit copy assignment just copies the
265
  //! View metadata (pointer, shape, strides) instead of the viewed data.
266
  View& operator=(const View& other)
4,131✔
267
  {
268
    size_t n = size();
4,131✔
269
    for (size_t i = 0; i < n; ++i)
77,568✔
270
      data_[flat_to_offset(i)] = other[i];
73,437✔
271
    return *this;
4,131✔
272
  }
273

274
  //! Fill all elements with a scalar
275
  View& operator=(T val)
223,652✔
276
  {
277
    size_t n = size();
223,652✔
278
    for (size_t i = 0; i < n; ++i)
11,988,297✔
279
      data_[flat_to_offset(i)] = val;
11,764,645✔
280
    return *this;
223,652✔
281
  }
282

283
  //! Assignment from initializer_list (for 1D views)
284
  View& operator=(std::initializer_list<T> vals)
233,798✔
285
  {
286
    auto it = vals.begin();
233,798✔
287
    for (size_t i = 0; i < size() && it != vals.end(); ++i, ++it)
1,904,472!
288
      data_[flat_to_offset(i)] = *it;
1,670,674✔
289
    return *this;
233,798✔
290
  }
291

292
  //! Assignment from another View
293
  template<typename U>
294
  View& operator=(const View<U>& other)
295
  {
296
    size_t n = size();
297
    for (size_t i = 0; i < n; ++i)
298
      data_[flat_to_offset(i)] = other[i];
299
    return *this;
300
  }
301

302
  //! Assignment from Tensor (forward-declared, defined after Tensor)
303
  template<typename U>
304
  View& operator=(const Tensor<U>& other);
305

306
  //! Compound addition from Tensor (forward-declared, defined after Tensor)
307
  template<typename U>
308
  View& operator+=(const Tensor<U>& o);
309

310
  //! Compound multiply by scalar
311
  View& operator*=(T val)
42,482✔
312
  {
313
    size_t n = size();
42,482✔
314
    for (size_t i = 0; i < n; ++i)
1,316,942✔
315
      data_[flat_to_offset(i)] *= val;
1,274,460✔
316
    return *this;
42,482✔
317
  }
318

319
  //! Compound divide by scalar
320
  View& operator/=(T val)
5,316✔
321
  {
322
    size_t n = size();
5,316✔
323
    for (size_t i = 0; i < n; ++i)
83,028✔
324
      data_[flat_to_offset(i)] /= val;
77,712✔
325
    return *this;
5,316✔
326
  }
327

328
  //--------------------------------------------------------------------------
329
  // Reductions
330

331
  //! Sum of all elements
332
  T sum() const
5,316✔
333
  {
334
    // remove_const needed so accumulator is mutable when T is const-qualified
335
    std::remove_const_t<T> s = 0;
5,316✔
336
    size_t n = size();
5,316✔
337
    for (size_t i = 0; i < n; ++i)
83,028✔
338
      s += data_[flat_to_offset(i)];
77,712✔
339
    return s;
5,316✔
340
  }
341

342
  //--------------------------------------------------------------------------
343
  // Iterators
344
  //
345
  // Lightweight row-major iterator parameterized on pointer type (Ptr).
346
  // Stores a flat logical position and converts to a physical offset on each
347
  // dereference via divmod over shape/strides.  For contiguous 1D views (the
348
  // common case) the divmod chain reduces to a single multiply-by-1, which
349
  // the compiler optimizes away.
350
  //
351
  // view_iterator<T*>       = mutable iterator  (from non-const View)
352
  // view_iterator<const T*> = read-only iterator (from const View)
353

354
  template<typename Ptr>
355
  class view_iterator {
356
    Ptr base_;
357
    size_t count_;
358
    const size_t* shape_;
359
    const size_t* strides_;
360
    size_t ndim_;
361

362
  public:
363
    using iterator_category = std::random_access_iterator_tag;
364
    using value_type = std::remove_const_t<T>;
365
    using difference_type = std::ptrdiff_t;
366
    using pointer = Ptr;
367
    using reference = decltype(*std::declval<Ptr>());
368

369
    view_iterator(Ptr base, size_t count, const View* v)
259,963,086✔
370
      : base_(base), count_(count), shape_(v->shape_.data()),
259,963,086✔
371
        strides_(v->strides_.data()), ndim_(v->shape_.size())
259,963,086✔
372
    {}
259,963,086✔
373

374
    reference operator*() const { return base_[offset()]; }
1,794,943,779✔
375
    reference operator[](difference_type n) const
376
    {
377
      return base_[offset_of(count_ + n)];
378
    }
379
    view_iterator& operator++()
1,760,095,219✔
380
    {
381
      ++count_;
1,760,095,219✔
382
      return *this;
1,760,095,219✔
383
    }
384
    view_iterator operator++(int)
385
    {
386
      auto tmp = *this;
387
      ++count_;
388
      return tmp;
389
    }
390
    view_iterator& operator--()
391
    {
392
      --count_;
393
      return *this;
394
    }
395
    view_iterator operator+(difference_type n) const
396
    {
397
      auto tmp = *this;
398
      tmp.count_ += n;
399
      return tmp;
400
    }
401
    view_iterator operator-(difference_type n) const
402
    {
403
      auto tmp = *this;
404
      tmp.count_ -= n;
405
      return tmp;
406
    }
407
    difference_type operator-(const view_iterator& o) const
254,619,079✔
408
    {
409
      return static_cast<difference_type>(count_) -
254,619,079✔
410
             static_cast<difference_type>(o.count_);
254,619,079✔
411
    }
412
    view_iterator& operator+=(difference_type n)
45,860,170✔
413
    {
414
      count_ += n;
45,860,170✔
415
      return *this;
45,860,170✔
416
    }
417
    view_iterator& operator-=(difference_type n)
418
    {
419
      count_ -= n;
420
      return *this;
421
    }
422
    bool operator==(const view_iterator& o) const { return count_ == o.count_; }
423
    bool operator!=(const view_iterator& o) const { return count_ != o.count_; }
678,092✔
424
    bool operator<(const view_iterator& o) const { return count_ < o.count_; }
425
    bool operator>(const view_iterator& o) const { return count_ > o.count_; }
426
    bool operator<=(const view_iterator& o) const { return count_ <= o.count_; }
427
    bool operator>=(const view_iterator& o) const { return count_ >= o.count_; }
428
    friend view_iterator operator+(difference_type n, const view_iterator& it)
429
    {
430
      return it + n;
431
    }
432

433
  private:
434
    size_t offset() const { return offset_of(count_); }
1,794,943,779✔
435
    size_t offset_of(size_t flat) const
1,794,943,779✔
436
    {
437
      size_t off = 0;
1,794,943,779✔
438
      for (int d = static_cast<int>(ndim_) - 1; d >= 0; --d) {
2,147,483,647✔
439
        off += (flat % shape_[d]) * strides_[d];
1,794,943,779✔
440
        flat /= shape_[d];
1,794,943,779✔
441
      }
442
      return off;
1,794,943,779✔
443
    }
444
  };
445

446
  using iterator = view_iterator<T*>;
447
  using const_iterator = view_iterator<const T*>;
448

449
  iterator begin() { return {data_, 0, this}; }
120,809,725✔
450
  iterator end() { return {data_, size(), this}; }
120,809,293✔
451
  const_iterator begin() const { return cbegin(); }
452
  const_iterator end() const { return cend(); }
453
  const_iterator cbegin() const { return {data_, 0, this}; }
9,172,034✔
454
  const_iterator cend() const { return {data_, size(), this}; }
9,172,034✔
455

456
private:
457
  //! Convert a logical flat index (row-major) to a physical element offset
458
  size_t flat_to_offset(size_t flat) const
2,147,483,647✔
459
  {
460
    size_t off = 0;
2,147,483,647✔
461
    for (int d = static_cast<int>(shape_.size()) - 1; d >= 0; --d) {
2,147,483,647✔
462
      off += (flat % shape_[d]) * strides_[d];
2,147,483,647✔
463
      flat /= shape_[d];
2,147,483,647✔
464
    }
465
    return off;
2,147,483,647✔
466
  }
467

468
  T* data_;
469
  vector<size_t> shape_;
470
  vector<size_t> strides_;
471
};
472

473
//==============================================================================
474
// Tensor<T>: dynamic-rank N-dimensional tensor.
475
//
476
// Stores elements in a contiguous row-major vector<storage_type<T>>
477
// with a dynamic shape.
478
//==============================================================================
479

480
template<typename T>
481
class Tensor {
482
public:
483
  using value_type = T;
484
  using stored_type = storage_type<T>;
485
  using iterator = typename vector<stored_type>::iterator;
486
  using const_iterator = typename vector<stored_type>::const_iterator;
487

488
  //--------------------------------------------------------------------------
489
  // Constructors
490

491
  Tensor() = default;
23,725,488✔
492

493
  //! Construct with shape (uninitialized for arithmetic types via vector
494
  //! resize)
495
  explicit Tensor(vector<size_t> shape)
219,057,638✔
496
    : shape_(std::move(shape)), data_(compute_size())
219,057,638✔
497
  {}
219,057,638✔
498

499
  //! Construct with shape and fill value
500
  Tensor(vector<size_t> shape, T fill)
163,790✔
501
    : shape_(std::move(shape)), data_(compute_size(), fill)
163,790✔
502
  {}
163,790✔
503

504
  //! Construct from initializer_list shape
505
  explicit Tensor(std::initializer_list<size_t> shape)
90,675✔
506
    : shape_(shape), data_(compute_size())
90,675✔
507
  {}
90,675✔
508

509
  //! Construct from initializer_list shape with fill
510
  Tensor(std::initializer_list<size_t> shape, T fill)
9,216✔
511
    : shape_(shape), data_(compute_size(), fill)
9,216✔
512
  {}
9,216✔
513

514
  //! 1D copy from raw pointer + count
515
  Tensor(const T* ptr, size_t count) : shape_({count}), data_(ptr, ptr + count)
15,748,631✔
516
  {}
15,748,631✔
517

518
  //! Copy from View (preserves view's shape)
519
  template<typename U>
520
  explicit Tensor(const View<U>& v) : shape_(v.shape_vec())
67,668✔
521
  {
522
    size_t n = v.size();
67,668✔
523
    data_.resize(n);
67,668✔
524
    for (size_t i = 0; i < n; ++i)
23,361,939✔
525
      data_[i] = v[i];
23,294,271✔
526
  }
67,668✔
527

528
  //--------------------------------------------------------------------------
529
  // Assignment
530

531
  //! Assignment from View
532
  template<typename U>
533
  Tensor& operator=(const View<U>& v)
18,121,576✔
534
  {
535
    shape_ = v.shape_vec();
18,121,576✔
536
    size_t n = v.size();
18,121,576✔
537
    data_.resize(n);
18,121,576✔
538
    for (size_t i = 0; i < n; ++i)
1,194,479,838✔
539
      data_[i] = v[i];
1,176,358,262✔
540
    return *this;
18,121,576✔
541
  }
542

543
  //! Assignment from initializer_list of values (1D)
544
  Tensor& operator=(std::initializer_list<T> vals)
1,660✔
545
  {
546
    shape_ = {vals.size()};
1,660✔
547
    data_.assign(vals.begin(), vals.end());
1,660✔
548
    return *this;
1,660✔
549
  }
550

551
  //--------------------------------------------------------------------------
552
  // Accessors
553

554
  stored_type* data() { return data_.data(); }
155,942,317✔
555
  const stored_type* data() const { return data_.data(); }
2,147,483,647✔
556
  size_t size() const { return data_.size(); }
404,996,983✔
557
  const vector<size_t>& shape() const { return shape_; }
102,230,181✔
558
  size_t shape(size_t dim) const
2,147,483,647✔
559
  {
560
    return dim < shape_.size() ? shape_[dim] : 0;
2,147,483,647✔
561
  }
562
  size_t ndim() const { return shape_.size(); }
563
  bool empty() const { return data_.empty(); }
3,192✔
564

565
  //--------------------------------------------------------------------------
566
  // Indexing (row-major)
567

568
  template<typename... Indices>
569
  stored_type& operator()(Indices... indices)
2,147,483,647✔
570
  {
571
    const size_t idx[] = {static_cast<size_t>(indices)...};
2,147,483,647✔
572
    size_t off = 0;
2,147,483,647✔
573
    for (size_t d = 0; d < sizeof...(Indices); ++d)
2,147,483,647✔
574
      off = off * shape_[d] + idx[d];
2,147,483,647✔
575
    return data_[off];
2,147,483,647✔
576
  }
577

578
  template<typename... Indices>
579
  const stored_type& operator()(Indices... indices) const
2,147,483,647✔
580
  {
581
    const size_t idx[] = {static_cast<size_t>(indices)...};
2,147,483,647✔
582
    size_t off = 0;
2,147,483,647✔
583
    for (size_t d = 0; d < sizeof...(Indices); ++d)
2,147,483,647✔
584
      off = off * shape_[d] + idx[d];
2,147,483,647✔
585
    return data_[off];
2,147,483,647✔
586
  }
587

588
  stored_type& operator[](size_t i) { return data_[i]; }
2,147,483,647✔
589
  const stored_type& operator[](size_t i) const { return data_[i]; }
2,147,483,647✔
590

591
  //! First and last element
592
  stored_type& front() { return data_.front(); }
2,574✔
593
  const stored_type& front() const { return data_.front(); }
594
  stored_type& back() { return data_.back(); }
2,574✔
595
  const stored_type& back() const { return data_.back(); }
596

597
  //--------------------------------------------------------------------------
598
  // Iterators
599

600
  iterator begin() { return data_.begin(); }
14,070✔
601
  iterator end() { return data_.end(); }
14,070✔
602
  const_iterator begin() const { return data_.begin(); }
21,656✔
603
  const_iterator end() const { return data_.end(); }
21,656✔
604
  const_iterator cbegin() const { return data_.cbegin(); }
68,743,621✔
605
  const_iterator cend() const { return data_.cend(); }
68,743,621✔
606

607
  //--------------------------------------------------------------------------
608
  // Mutation
609

610
  void resize(const vector<size_t>& shape)
5,368,286✔
611
  {
612
    shape_ = shape;
5,368,286✔
613
    data_.resize(compute_size());
5,368,286✔
614
  }
5,368,286✔
615

616
  void resize(std::initializer_list<size_t> shape)
48,108✔
617
  {
618
    shape_.assign(shape.begin(), shape.end());
48,108✔
619
    data_.resize(compute_size());
48,108✔
620
  }
48,108✔
621

622
  void reshape(const vector<size_t>& new_shape) { shape_ = new_shape; }
×
623

624
  void fill(T val) { std::fill(data_.begin(), data_.end(), val); }
55,612✔
625

626
  //--------------------------------------------------------------------------
627
  // View accessors
628

629
  //! Fix one axis at a given index, returning an (N-1)-dimensional view
630
  //! Multi-axis slice.  Each argument corresponds to one axis and is either:
631
  //!   - an integer (fixes that axis, rank-reducing)
632
  //!   - All (keeps entire axis)
633
  //!   - Range (keeps sub-range along that axis)
634
  //! Example: t.slice(0, all, range(2, 5)) == numpy t[0, :, 2:5]
635
  template<typename First, typename... Rest>
636
  View<stored_type> slice(First first, Rest... rest)
140,734,864✔
637
  {
638
    auto strides = compute_strides();
140,734,864✔
639
    auto r = detail::compute_slice(shape_, strides, first, rest...);
140,734,864✔
640
    return {
641
      data_.data() + r.ptr_offset, std::move(r.shape), std::move(r.strides)};
281,469,728✔
642
  }
140,734,864✔
643

644
  template<typename First, typename... Rest>
645
  View<const stored_type> slice(First first, Rest... rest) const
87,832,638✔
646
  {
647
    auto strides = compute_strides();
87,832,638✔
648
    auto r = detail::compute_slice(shape_, strides, first, rest...);
87,832,638✔
649
    return {
650
      data_.data() + r.ptr_offset, std::move(r.shape), std::move(r.strides)};
175,665,276✔
651
  }
87,832,638✔
652

653
  //! Flat 1D view of all elements
654
  View<stored_type> flat()
655
  {
656
    return {data_.data(), {data_.size()}, {size_t(1)}};
657
  }
658
  View<const stored_type> flat() const
659
  {
660
    return {data_.data(), {data_.size()}, {size_t(1)}};
661
  }
662

663
  //--------------------------------------------------------------------------
664
  // Reductions and transforms
665

666
  //! Sum of all elements
667
  T sum() const
33,307✔
668
  {
669
    T s = T(0);
33,307✔
670
    for (size_t i = 0; i < data_.size(); ++i)
880,687✔
671
      s += data_[i];
847,380✔
672
    return s;
33,307✔
673
  }
674

675
  //! Sum along an axis, reducing rank by 1 (defined out-of-line below)
676
  Tensor<T> sum(size_t axis) const;
677

678
  //! Product of all elements
679
  T prod() const
1,878✔
680
  {
681
    T p = T(1);
1,878✔
682
    for (size_t i = 0; i < data_.size(); ++i)
7,074✔
683
      p *= data_[i];
5,196✔
684
    return p;
1,878✔
685
  }
686

687
  //! True if any element is nonzero
688
  bool any() const
14,695,675✔
689
  {
690
    for (size_t i = 0; i < data_.size(); ++i)
29,826,278✔
691
      if (data_[i])
15,130,603!
692
        return true;
×
693
    return false;
14,695,675✔
694
  }
695

696
  //! True if all elements are nonzero
697
  bool all() const
13,139✔
698
  {
699
    for (size_t i = 0; i < data_.size(); ++i)
55,120✔
700
      if (!data_[i])
42,722✔
701
        return false;
741✔
702
    return true;
12,398✔
703
  }
704

705
  //! Flat index of the minimum element
706
  size_t argmin() const
102,123,099✔
707
  {
708
    return static_cast<size_t>(std::distance(data_.data(),
102,123,099✔
709
      std::min_element(data_.data(), data_.data() + data_.size())));
204,246,198✔
710
  }
711

712
  //! Reverse element order along an axis (e.g. flip(0) reverses rows)
713
  Tensor flip(size_t axis) const
3,870✔
714
  {
715
    size_t outer_size = 1;
3,870✔
716
    for (size_t d = 0; d < axis; ++d)
3,870!
717
      outer_size *= shape_[d];
×
718
    size_t axis_size = shape_[axis];
3,870✔
719
    size_t inner_size = 1;
3,870✔
720
    for (size_t d = axis + 1; d < shape_.size(); ++d)
7,740✔
721
      inner_size *= shape_[d];
3,870✔
722

723
    Tensor r(shape_);
3,870✔
724
    for (size_t o = 0; o < outer_size; ++o)
7,740✔
725
      for (size_t a = 0; a < axis_size; ++a)
746,370✔
726
        for (size_t i = 0; i < inner_size; ++i)
146,767,500✔
727
          r.data_[(o * axis_size + (axis_size - 1 - a)) * inner_size + i] =
146,025,000✔
728
            data_[(o * axis_size + a) * inner_size + i];
146,025,000✔
729
    return r;
3,870✔
730
  }
731

732
  //--------------------------------------------------------------------------
733
  // Operators
734

735
  Tensor& operator+=(T val)
736
  {
737
    for (auto& x : data_)
738
      x += val;
739
    return *this;
740
  }
741
  Tensor& operator-=(T val)
6,366✔
742
  {
743
    for (auto& x : data_)
1,453,770✔
744
      x -= val;
1,447,404✔
745
    return *this;
6,366✔
746
  }
747
  Tensor& operator*=(T val)
17,354✔
748
  {
749
    for (auto& x : data_)
107,049✔
750
      x *= val;
89,695✔
751
    return *this;
17,354✔
752
  }
753
  Tensor& operator/=(T val)
24,239✔
754
  {
755
    for (auto& x : data_)
698,796✔
756
      x /= val;
674,557✔
757
    return *this;
24,239✔
758
  }
759
  Tensor& operator+=(const Tensor& o)
19,125✔
760
  {
761
    for (size_t i = 0; i < data_.size(); ++i)
10,608,517✔
762
      data_[i] += o.data_[i];
10,589,392✔
763
    return *this;
19,125✔
764
  }
765
  Tensor& operator-=(const Tensor& o)
766
  {
767
    for (size_t i = 0; i < data_.size(); ++i)
768
      data_[i] -= o.data_[i];
769
    return *this;
770
  }
771
  Tensor& operator*=(const Tensor& o)
772
  {
773
    for (size_t i = 0; i < data_.size(); ++i)
774
      data_[i] *= o.data_[i];
775
    return *this;
776
  }
777
  Tensor& operator/=(const Tensor& o)
778
  {
779
    for (size_t i = 0; i < data_.size(); ++i)
780
      data_[i] /= o.data_[i];
781
    return *this;
782
  }
783

784
  Tensor operator+(const Tensor& o) const
1,104✔
785
  {
786
    Tensor r(shape_);
1,104✔
787
    for (size_t i = 0; i < data_.size(); ++i)
966,346✔
788
      r.data_[i] = data_[i] + o.data_[i];
965,242✔
789
    return r;
1,104✔
790
  }
791
  Tensor operator-(const Tensor& o) const
4,527✔
792
  {
793
    Tensor r(shape_);
4,527✔
794
    for (size_t i = 0; i < data_.size(); ++i)
19,026✔
795
      r.data_[i] = data_[i] - o.data_[i];
14,499✔
796
    return r;
4,527✔
797
  }
798
  Tensor operator/(const Tensor& o) const
2,346✔
799
  {
800
    Tensor r(shape_);
2,346✔
801
    for (size_t i = 0; i < data_.size(); ++i)
207,588✔
802
      r.data_[i] = data_[i] / o.data_[i];
205,242✔
803
    return r;
2,346✔
804
  }
805
  Tensor operator*(const Tensor& o) const
806
  {
807
    Tensor r(shape_);
808
    for (size_t i = 0; i < data_.size(); ++i)
809
      r.data_[i] = data_[i] * o.data_[i];
810
    return r;
811
  }
812

813
  Tensor operator+(T val) const
2,694✔
814
  {
815
    Tensor r(shape_);
2,694✔
816
    for (size_t i = 0; i < data_.size(); ++i)
12,096✔
817
      r.data_[i] = data_[i] + val;
9,402✔
818
    return r;
2,694✔
819
  }
820
  Tensor operator-(T val) const
102,123,099✔
821
  {
822
    Tensor r(shape_);
102,123,099✔
823
    for (size_t i = 0; i < data_.size(); ++i)
208,127,719✔
824
      r.data_[i] = data_[i] - val;
106,004,620✔
825
    return r;
102,123,099✔
826
  }
827
  Tensor operator*(T val) const
19,125✔
828
  {
829
    Tensor r(shape_);
19,125✔
830
    for (size_t i = 0; i < data_.size(); ++i)
10,608,517✔
831
      r.data_[i] = data_[i] * val;
10,589,392✔
832
    return r;
19,125✔
833
  }
834

835
  Tensor<bool> operator<=(T val) const
2,439✔
836
  {
837
    Tensor<bool> r(shape_);
2,439✔
838
    for (size_t i = 0; i < data_.size(); ++i)
13,512✔
839
      r.data()[i] = data_[i] <= val;
11,073✔
840
    return r;
2,439✔
841
  }
842
  Tensor<bool> operator<(T val) const
36✔
843
  {
844
    Tensor<bool> r(shape_);
36✔
845
    for (size_t i = 0; i < data_.size(); ++i)
108✔
846
      r.data()[i] = data_[i] < val;
72✔
847
    return r;
36✔
848
  }
849
  Tensor<bool> operator>=(T val) const
12,398✔
850
  {
851
    Tensor<bool> r(shape_);
12,398✔
852
    for (size_t i = 0; i < data_.size(); ++i)
54,379✔
853
      r.data()[i] = data_[i] >= val;
41,981✔
854
    return r;
12,398✔
855
  }
856
  Tensor<bool> operator>(T val) const
14,699,635✔
857
  {
858
    Tensor<bool> r(shape_);
14,699,635✔
859
    for (size_t i = 0; i < data_.size(); ++i)
39,163,809✔
860
      r.data()[i] = data_[i] > val;
24,464,174✔
861
    return r;
14,699,635✔
862
  }
863
  Tensor<bool> operator<(const Tensor& o) const
1,662✔
864
  {
865
    Tensor<bool> r(shape_);
1,662✔
866
    for (size_t i = 0; i < data_.size(); ++i)
6,246✔
867
      r.data()[i] = data_[i] < o.data_[i];
4,584✔
868
    return r;
1,662✔
869
  }
870

871
private:
872
  size_t compute_size() const
224,726,678✔
873
  {
874
    size_t s = 1;
224,737,713✔
875
    for (auto d : shape_)
455,142,782✔
876
      s *= d;
230,405,069✔
877
    return s;
224,737,713✔
878
  }
879

880
  //! Compute row-major strides from shape
881
  vector<size_t> compute_strides() const
227,496,352✔
882
  {
883
    vector<size_t> strides(shape_.size());
228,567,502✔
884
    if (!shape_.empty()) {
228,567,502!
885
      strides.back() = 1;
228,567,502✔
886
      for (int d = static_cast<int>(shape_.size()) - 2; d >= 0; --d)
457,183,660✔
887
        strides[d] = strides[d + 1] * shape_[d + 1];
228,616,158✔
888
    }
889
    return strides;
228,567,502✔
890
  }
891

892
  //--------------------------------------------------------------------------
893
  // Data members
894

895
  vector<size_t> shape_;
896
  vector<storage_type<T>> data_;
897
};
898

899
//==============================================================================
900
// Non-member operators (scalar op tensor)
901
//==============================================================================
902

903
template<typename T>
904
Tensor<T> operator*(T val, const Tensor<T>& arr)
19,125✔
905
{
906
  return arr * val;
19,125✔
907
}
908

909
template<typename T>
910
Tensor<T> operator+(T val, const Tensor<T>& arr)
911
{
912
  return arr + val;
913
}
914

915
// Mixed-type arithmetic: Tensor<T1> op Tensor<T2> -> Tensor<double>
916
// A SFINAE guard is used here, as without !is_same Tensor<T> * Tensor<T>
917
// would be ambiguous between the member operator* and this non-member function.
918
template<typename T1, typename T2,
919
  typename = std::enable_if_t<!std::is_same<T1, T2>::value>>
920
Tensor<double> operator*(const Tensor<T1>& a, const Tensor<T2>& b)
54✔
921
{
922
  Tensor<double> r(a.shape());
54✔
923
  for (size_t i = 0; i < a.size(); ++i)
180✔
924
    r.data()[i] =
126✔
925
      static_cast<double>(a.data()[i]) * static_cast<double>(b.data()[i]);
126✔
926
  return r;
54✔
927
}
928

929
// Same SFINAE guard as operator* above.
930
template<typename T1, typename T2,
931
  typename = std::enable_if_t<!std::is_same<T1, T2>::value>>
932
Tensor<double> operator/(const Tensor<T1>& a, const Tensor<T2>& b)
1,824✔
933
{
934
  Tensor<double> r(a.shape());
1,824✔
935
  for (size_t i = 0; i < a.size(); ++i)
6,894✔
936
    r.data()[i] =
5,070✔
937
      static_cast<double>(a.data()[i]) / static_cast<double>(b.data()[i]);
5,070✔
938
  return r;
1,824✔
939
}
940

941
//==============================================================================
942
// Out-of-line method definitions (require complete types)
943
//==============================================================================
944

945
template<typename T>
946
template<typename U>
947
View<T>& View<T>::operator=(const Tensor<U>& other)
33,990✔
948
{
949
  size_t n = size();
33,990✔
950
  for (size_t i = 0; i < n; ++i)
16,462,679✔
951
    data_[flat_to_offset(i)] = static_cast<T>(other.data()[i]);
16,428,689✔
952
  return *this;
33,990✔
953
}
954

955
template<typename T>
956
template<typename U>
957
View<T>& View<T>::operator+=(const Tensor<U>& o)
1,267,977✔
958
{
959
  size_t n = size();
1,267,977✔
960
  for (size_t i = 0; i < n; ++i)
2,147,483,647✔
961
    data_[flat_to_offset(i)] += o.data()[i];
2,147,483,647✔
962
  return *this;
1,267,977✔
963
}
964

965
template<typename T>
966
Tensor<T> Tensor<T>::sum(size_t axis) const
2,226✔
967
{
968
  // Build output shape (all dims except the summed axis)
969
  vector<size_t> out_shape;
2,226✔
970
  for (size_t d = 0; d < shape_.size(); ++d)
7,887✔
971
    if (d != axis)
5,661✔
972
      out_shape.push_back(shape_[d]);
3,435✔
973

974
  // Split dimensions into three zones: outer | axis | inner
975
  size_t outer_size = 1;
2,226✔
976
  for (size_t d = 0; d < axis; ++d)
5,553✔
977
    outer_size *= shape_[d];
3,327✔
978
  size_t axis_size = shape_[axis];
2,226✔
979
  size_t inner_size = 1;
2,226✔
980
  for (size_t d = axis + 1; d < shape_.size(); ++d)
2,334✔
981
    inner_size *= shape_[d];
108✔
982

983
  Tensor<T> result(out_shape, T(0));
2,226✔
984
  for (size_t o = 0; o < outer_size; ++o)
3,867✔
985
    for (size_t a = 0; a < axis_size; ++a)
6,924✔
986
      for (size_t i = 0; i < inner_size; ++i)
10,830✔
987
        result.data()[o * inner_size + i] +=
5,547✔
988
          data_[(o * axis_size + a) * inner_size + i];
5,547✔
989

990
  return result;
4,452✔
991
}
2,226✔
992

993
//==============================================================================
994
// StaticTensor2D<T, R, C>: compile-time fixed 2D tensor.
995
//==============================================================================
996

997
template<typename T, size_t R, size_t C>
998
class StaticTensor2D {
999
public:
1000
  using value_type = T;
1001

1002
  //--------------------------------------------------------------------------
1003
  // Indexing
1004

1005
  //! Templated to accept enum class indices (e.g. GlobalTally, TallyResult)
1006
  //! which don't implicitly convert to integer types.
1007
  template<typename I0, typename I1>
1008
  T& operator()(I0 i, I1 j)
2,352,220✔
1009
  {
1010
    return data_[static_cast<size_t>(i) * C + static_cast<size_t>(j)];
2,352,220✔
1011
  }
1012
  template<typename I0, typename I1>
1013
  const T& operator()(I0 i, I1 j) const
123,151✔
1014
  {
1015
    return data_[static_cast<size_t>(i) * C + static_cast<size_t>(j)];
134,787✔
1016
  }
1017

1018
  //--------------------------------------------------------------------------
1019
  // Accessors
1020

1021
  T* data() { return data_; }
2,426✔
1022
  const T* data() const { return data_; }
5,668✔
1023
  constexpr size_t size() const { return R * C; }
2,366✔
1024
  std::array<size_t, 2> shape() const { return {R, C}; }
5,668✔
1025

1026
  //--------------------------------------------------------------------------
1027
  // Mutation
1028

1029
  void fill(T val) { std::fill(data_, data_ + R * C, val); }
37,214✔
1030

1031
  //--------------------------------------------------------------------------
1032
  // Iterators
1033

1034
  T* begin() { return data_; }
3✔
1035
  T* end() { return data_ + R * C; }
1036
  const T* begin() const { return data_; }
1037
  const T* end() const { return data_ + R * C; }
1038

1039
  //--------------------------------------------------------------------------
1040
  // View accessors
1041

1042
  //! Multi-axis slice (same interface as Tensor/View).
1043
  template<typename First, typename... Rest>
1044
  View<T> slice(First first, Rest... rest)
40,520✔
1045
  {
1046
    vector<size_t> sh = {R, C};
40,520✔
1047
    vector<size_t> st = {C, 1};
40,520✔
1048
    auto r = detail::compute_slice(sh, st, first, rest...);
40,520✔
1049
    return {data_ + r.ptr_offset, std::move(r.shape), std::move(r.strides)};
81,040✔
1050
  }
40,520✔
1051
  template<typename First, typename... Rest>
1052
  View<const T> slice(First first, Rest... rest) const
1053
  {
1054
    vector<size_t> sh = {R, C};
1055
    vector<size_t> st = {C, 1};
1056
    auto r = detail::compute_slice(sh, st, first, rest...);
1057
    return {data_ + r.ptr_offset, std::move(r.shape), std::move(r.strides)};
1058
  }
1059

1060
  //! Flat view (1D, contiguous)
1061
  View<T> flat() { return {data_, {R * C}, {size_t(1)}}; }
1062
  View<const T> flat() const { return {data_, {R * C}, {size_t(1)}}; }
1063

1064
private:
1065
  //--------------------------------------------------------------------------
1066
  // Data members
1067

1068
  T data_[R * C] = {};
1069
};
1070

1071
//==============================================================================
1072
// Non-member functions
1073
//==============================================================================
1074

1075
template<typename T>
1076
Tensor<T> zeros(std::initializer_list<size_t> shape)
111,311✔
1077
{
1078
  vector<size_t> s(shape);
111,311✔
1079
  return Tensor<T>(std::move(s), T(0));
222,622✔
1080
}
111,311✔
1081

1082
template<typename T>
1083
Tensor<T> zeros(const vector<size_t>& shape)
45,468✔
1084
{
1085
  return Tensor<T>(shape, T(0));
45,468✔
1086
}
1087

1088
template<typename T>
1089
Tensor<T> ones(std::initializer_list<size_t> shape)
4,227✔
1090
{
1091
  vector<size_t> s(shape);
4,227✔
1092
  return Tensor<T>(std::move(s), T(1));
8,454✔
1093
}
4,227✔
1094

1095
template<typename T>
1096
Tensor<T> ones(const vector<size_t>& shape)
1097
{
1098
  return Tensor<T>(shape, T(1));
1099
}
1100

1101
template<typename T>
1102
Tensor<T> zeros_like(const Tensor<T>& o)
9✔
1103
{
1104
  return Tensor<T>(o.shape(), T(0));
9✔
1105
}
1106

1107
template<typename T, typename V>
1108
Tensor<T> full_like(const Tensor<T>& o, V val)
1109
{
1110
  return Tensor<T>(o.shape(), static_cast<T>(val));
1111
}
1112

1113
//! Return a 1D tensor of n evenly spaced values from start to stop (inclusive)
1114
template<typename T>
1115
Tensor<T> linspace(T start, T stop, size_t n)
27,985✔
1116
{
1117
  Tensor<T> result({n});
27,985✔
1118
  if (n < 2) {
27,985!
UNCOV
1119
    result[0] = start;
×
UNCOV
1120
    return result;
×
1121
  }
1122
  for (size_t i = 0; i < n; ++i) {
186,755,242✔
1123
    result[i] =
186,727,257✔
1124
      start + static_cast<T>(i) * (stop - start) / static_cast<T>(n - 1);
186,727,257✔
1125
  }
1126
  return result;
27,985✔
1127
}
1128

1129
//! Concatenate two 1D tensors end-to-end
1130
template<typename T>
1131
Tensor<T> concatenate(const Tensor<T>& a, const Tensor<T>& b)
9✔
1132
{
1133
  size_t total = a.size() + b.size();
9✔
1134
  Tensor<T> result({total});
9✔
1135
  std::copy(a.data(), a.data() + a.size(), result.data());
9✔
1136
  std::copy(b.data(), b.data() + b.size(), result.data() + a.size());
9✔
1137
  return result;
9✔
UNCOV
1138
}
×
1139

1140
//! Element-wise natural logarithm
1141
template<typename T>
1142
Tensor<T> log(const Tensor<T>& a)
8,118✔
1143
{
1144
  Tensor<T> r(a.shape());
8,118✔
1145
  for (size_t i = 0; i < a.size(); ++i)
10,273,494✔
1146
    r.data()[i] = std::log(a.data()[i]);
10,265,376✔
1147
  return r;
8,118✔
1148
}
1149

1150
//! Element-wise absolute value
1151
template<typename T>
1152
Tensor<T> abs(const Tensor<T>& a)
102,123,099✔
1153
{
1154
  Tensor<T> r(a.shape());
102,123,099✔
1155
  for (size_t i = 0; i < a.size(); ++i)
208,127,719✔
1156
    r.data()[i] = std::abs(a.data()[i]);
106,004,620✔
1157
  return r;
102,123,099✔
1158
}
1159

1160
//! Element-wise conditional: select from true_val where cond is true,
1161
//! otherwise use false_val
1162
template<typename T, typename V>
1163
Tensor<T> where(
3,171✔
1164
  const Tensor<bool>& cond, const Tensor<T>& true_val, V false_val)
1165
{
1166
  Tensor<T> r(cond.shape());
7,356✔
1167
  for (size_t i = 0; i < cond.size(); ++i)
9,350,239✔
1168
    r.data()[i] =
9,342,883✔
1169
      cond.data()[i] ? true_val.data()[i] : static_cast<T>(false_val);
9,342,883✔
1170
  return r;
7,356✔
1171
}
1172

1173
//! Replace NaN/Inf values with finite substitutes
1174
template<typename T>
1175
Tensor<T> nan_to_num(const Tensor<T>& a, T nan_val = T(0),
2,346✔
1176
  T posinf_val = std::numeric_limits<T>::max(),
1177
  T neginf_val = std::numeric_limits<T>::lowest())
1178
{
1179
  Tensor<T> r(a.shape());
2,346✔
1180
  for (size_t i = 0; i < a.size(); ++i) {
207,588✔
1181
    T val = a.data()[i];
205,242✔
1182
    if (std::isnan(val))
205,242✔
1183
      r.data()[i] = nan_val;
171,528✔
1184
    else if (std::isinf(val))
33,714!
UNCOV
1185
      r.data()[i] = val > 0 ? posinf_val : neginf_val;
×
1186
    else
1187
      r.data()[i] = val;
33,714✔
1188
  }
1189
  return r;
2,346✔
1190
}
1191

1192
//==============================================================================
1193
// Type traits
1194
//==============================================================================
1195

1196
//! Type trait that is true for Tensor and StaticTensor2D.
1197
//! Used by hdf5_interface.h to select the correct write_dataset overload.
1198
template<typename T>
1199
struct is_tensor : std::false_type {};
1200

1201
template<typename T>
1202
struct is_tensor<Tensor<T>> : std::true_type {};
1203

1204
template<typename T, size_t R, size_t C>
1205
struct is_tensor<StaticTensor2D<T, R, C>> : std::true_type {};
1206

1207
} // namespace tensor
1208
} // namespace openmc
1209

1210
#endif // OPENMC_TENSOR_H
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