//! \file tensor.h //! \brief Multi-dimensional tensor types for OpenMC. //! //! Tensor is the primary type: a dynamic-rank owning container that stores //! elements contiguously in row-major order. View is a lightweight //! non-owning reference into a Tensor's storage, returned by the slice() //! method and flat(). StaticTensor2D is a small stack-allocated 2D //! array used only for simulation::global_tallies. //! //! Slicing follows numpy conventions: each axis takes an index (rank-reducing), //! All (keep entire axis), or Range (keep sub-range). For example, //! arr.slice(0, all, range(2, 5)) is equivalent to numpy's arr[0, :, 2:5]. //! //! View is declared before Tensor because Tensor's methods return View objects. #ifndef OPENMC_TENSOR_H #define OPENMC_TENSOR_H #include "openmc/vector.h" #include #include #include #include #include #include #include #include namespace openmc { namespace tensor { //============================================================================== // Forward declarations //============================================================================== template class Tensor; template class StaticTensor2D; //============================================================================== // Storage type mapping // // std::vector is a bit-packed specialization that returns proxy objects // instead of real references, which breaks generic code. storage_type_map // redirects bool to unsigned char so that Tensor stores one byte per // element with normal reference semantics. //============================================================================== template struct storage_type_map { using type = T; }; template<> struct storage_type_map { using type = unsigned char; }; template using storage_type = typename storage_type_map::type; //============================================================================== // Slice argument types // // Used with the variadic slice() method on Tensor, View, and StaticTensor2D. // Each argument corresponds to one axis: a plain integer fixes that axis at // a single index (rank-reducing), All keeps the entire axis, and Range keeps // a sub-range. //============================================================================== //! Keep an entire axis (equivalent to numpy's ':' or xtensor's xt::all()) struct All {}; constexpr All all {}; //! Sub-range along an axis [start, end) struct Range { size_t start; size_t end; // SIZE_MAX means "to end of axis" }; //! Create a Range [start, end) inline Range range(size_t start, size_t end) { return {start, end}; } //! Create a Range [0, end) inline Range range(size_t end) { return {0, end}; } namespace detail { //! Internal: normalized representation of a per-axis slice argument struct SliceArg { enum Kind { INDEX, ALL, RANGE } kind; size_t start; size_t end; }; inline SliceArg to_slice_arg(All) { return {SliceArg::ALL, 0, 0}; } inline SliceArg to_slice_arg(Range r) { return {SliceArg::RANGE, r.start, r.end}; } template inline typename std::enable_if::value || std::is_enum::value, SliceArg>::type to_slice_arg(I i) { return {SliceArg::INDEX, static_cast(i), 0}; } //! Result of a slice computation: pointer offset + new shape/strides struct SliceResult { size_t ptr_offset; vector shape; vector strides; }; //! Compute the result of applying slice arguments to shape/strides template SliceResult compute_slice(const vector& shape, const vector& strides, First first, Rest... rest) { const size_t n = 1 + sizeof...(Rest); SliceArg args[1 + sizeof...(Rest)] = { to_slice_arg(first), to_slice_arg(rest)...}; size_t offset = 0; vector new_shape; vector new_strides; for (size_t a = 0; a < n; ++a) { switch (args[a].kind) { case SliceArg::INDEX: offset += args[a].start * strides[a]; break; case SliceArg::ALL: new_shape.push_back(shape[a]); new_strides.push_back(strides[a]); break; case SliceArg::RANGE: { offset += args[a].start * strides[a]; size_t end = (args[a].end == SIZE_MAX) ? shape[a] : args[a].end; new_shape.push_back(end - args[a].start); new_strides.push_back(strides[a]); break; } } } // Trailing axes not covered by arguments are implicitly All. // This matches numpy: a[i] on a 2D array returns a 1D row. for (size_t a = n; a < shape.size(); ++a) { new_shape.push_back(shape[a]); new_strides.push_back(strides[a]); } return {offset, std::move(new_shape), std::move(new_strides)}; } } // namespace detail //============================================================================== // View: a non-owning N-dimensional view into a tensor's storage. // // Holds a base pointer, shape, and strides (in elements). Supports arbitrary // rank and multi-axis slicing via the variadic slice() method. //============================================================================== template class View { public: //-------------------------------------------------------------------------- // Constructors View(T* data, vector shape, vector strides) : data_(data), shape_(std::move(shape)), strides_(std::move(strides)) {} // Explicitly default copy/move constructors (declaring copy assignment // below would otherwise suppress the implicit move constructor). View(const View&) = default; View(View&&) = default; //-------------------------------------------------------------------------- // Indexing //! Multi-index element access (1D, 2D, 3D, ...) template T& operator()(Indices... indices) { const size_t idx[] = {static_cast(indices)...}; size_t off = 0; for (size_t d = 0; d < sizeof...(Indices); ++d) off += idx[d] * strides_[d]; return data_[off]; } template const T& operator()(Indices... indices) const { const size_t idx[] = {static_cast(indices)...}; size_t off = 0; for (size_t d = 0; d < sizeof...(Indices); ++d) off += idx[d] * strides_[d]; return data_[off]; } //! Flat logical index (row-major order) T& operator[](size_t i) { return data_[flat_to_offset(i)]; } const T& operator[](size_t i) const { return data_[flat_to_offset(i)]; } //-------------------------------------------------------------------------- // Accessors size_t size() const { size_t s = 1; for (auto d : shape_) s *= d; return s; } size_t ndim() const { return shape_.size(); } size_t shape(size_t axis) const { return shape_[axis]; } const vector& shape_vec() const { return shape_; } T* data() { return data_; } const T* data() const { return data_; } //-------------------------------------------------------------------------- // View accessors //! Multi-axis slice. Each argument corresponds to one axis and is either: //! - an integer (fixes that axis, rank-reducing) //! - All (keeps entire axis) //! - Range (keeps sub-range along that axis) //! Example: v.slice(0, all, range(2, 5)) == numpy v[0, :, 2:5] template View slice(First first, Rest... rest) { auto r = detail::compute_slice(shape_, strides_, first, rest...); return {data_ + r.ptr_offset, std::move(r.shape), std::move(r.strides)}; } template View slice(First first, Rest... rest) const { auto r = detail::compute_slice(shape_, strides_, first, rest...); return {data_ + r.ptr_offset, std::move(r.shape), std::move(r.strides)}; } //-------------------------------------------------------------------------- // Assignment operators //! Copy assignment: element-wise deep copy (writes through data pointer). //! Without this, the compiler's implicit copy assignment just copies the //! View metadata (pointer, shape, strides) instead of the viewed data. View& operator=(const View& other) { size_t n = size(); for (size_t i = 0; i < n; ++i) data_[flat_to_offset(i)] = other[i]; return *this; } //! Fill all elements with a scalar View& operator=(T val) { size_t n = size(); for (size_t i = 0; i < n; ++i) data_[flat_to_offset(i)] = val; return *this; } //! Assignment from initializer_list (for 1D views) View& operator=(std::initializer_list vals) { auto it = vals.begin(); for (size_t i = 0; i < size() && it != vals.end(); ++i, ++it) data_[flat_to_offset(i)] = *it; return *this; } //! Assignment from another View template View& operator=(const View& other) { size_t n = size(); for (size_t i = 0; i < n; ++i) data_[flat_to_offset(i)] = other[i]; return *this; } //! Assignment from Tensor (forward-declared, defined after Tensor) template View& operator=(const Tensor& other); //! Compound addition from Tensor (forward-declared, defined after Tensor) template View& operator+=(const Tensor& o); //! Compound multiply by scalar View& operator*=(T val) { size_t n = size(); for (size_t i = 0; i < n; ++i) data_[flat_to_offset(i)] *= val; return *this; } //! Compound divide by scalar View& operator/=(T val) { size_t n = size(); for (size_t i = 0; i < n; ++i) data_[flat_to_offset(i)] /= val; return *this; } //-------------------------------------------------------------------------- // Reductions //! Sum of all elements T sum() const { // remove_const needed so accumulator is mutable when T is const-qualified std::remove_const_t s = 0; size_t n = size(); for (size_t i = 0; i < n; ++i) s += data_[flat_to_offset(i)]; return s; } //-------------------------------------------------------------------------- // Iterators // // Lightweight row-major iterator parameterized on pointer type (Ptr). // Stores a flat logical position and converts to a physical offset on each // dereference via divmod over shape/strides. For contiguous 1D views (the // common case) the divmod chain reduces to a single multiply-by-1, which // the compiler optimizes away. // // view_iterator = mutable iterator (from non-const View) // view_iterator = read-only iterator (from const View) template class view_iterator { Ptr base_; size_t count_; const size_t* shape_; const size_t* strides_; size_t ndim_; public: using iterator_category = std::random_access_iterator_tag; using value_type = std::remove_const_t; using difference_type = std::ptrdiff_t; using pointer = Ptr; using reference = decltype(*std::declval()); view_iterator(Ptr base, size_t count, const View* v) : base_(base), count_(count), shape_(v->shape_.data()), strides_(v->strides_.data()), ndim_(v->shape_.size()) {} reference operator*() const { return base_[offset()]; } reference operator[](difference_type n) const { return base_[offset_of(count_ + n)]; } view_iterator& operator++() { ++count_; return *this; } view_iterator operator++(int) { auto tmp = *this; ++count_; return tmp; } view_iterator& operator--() { --count_; return *this; } view_iterator operator+(difference_type n) const { auto tmp = *this; tmp.count_ += n; return tmp; } view_iterator operator-(difference_type n) const { auto tmp = *this; tmp.count_ -= n; return tmp; } difference_type operator-(const view_iterator& o) const { return static_cast(count_) - static_cast(o.count_); } view_iterator& operator+=(difference_type n) { count_ += n; return *this; } view_iterator& operator-=(difference_type n) { count_ -= n; return *this; } bool operator==(const view_iterator& o) const { return count_ == o.count_; } bool operator!=(const view_iterator& o) const { return count_ != o.count_; } bool operator<(const view_iterator& o) const { return count_ < o.count_; } bool operator>(const view_iterator& o) const { return count_ > o.count_; } bool operator<=(const view_iterator& o) const { return count_ <= o.count_; } bool operator>=(const view_iterator& o) const { return count_ >= o.count_; } friend view_iterator operator+(difference_type n, const view_iterator& it) { return it + n; } private: size_t offset() const { return offset_of(count_); } size_t offset_of(size_t flat) const { size_t off = 0; for (int d = static_cast(ndim_) - 1; d >= 0; --d) { off += (flat % shape_[d]) * strides_[d]; flat /= shape_[d]; } return off; } }; using iterator = view_iterator; using const_iterator = view_iterator; iterator begin() { return {data_, 0, this}; } iterator end() { return {data_, size(), this}; } const_iterator begin() const { return cbegin(); } const_iterator end() const { return cend(); } const_iterator cbegin() const { return {data_, 0, this}; } const_iterator cend() const { return {data_, size(), this}; } private: //! Convert a logical flat index (row-major) to a physical element offset size_t flat_to_offset(size_t flat) const { size_t off = 0; for (int d = static_cast(shape_.size()) - 1; d >= 0; --d) { off += (flat % shape_[d]) * strides_[d]; flat /= shape_[d]; } return off; } T* data_; vector shape_; vector strides_; }; //============================================================================== // Tensor: dynamic-rank N-dimensional tensor. // // Stores elements in a contiguous row-major vector> // with a dynamic shape. //============================================================================== template class Tensor { public: using value_type = T; using stored_type = storage_type; using iterator = typename vector::iterator; using const_iterator = typename vector::const_iterator; //-------------------------------------------------------------------------- // Constructors Tensor() = default; //! Construct with shape (uninitialized for arithmetic types via vector //! resize) explicit Tensor(vector shape) : shape_(std::move(shape)), data_(compute_size()) {} //! Construct with shape and fill value Tensor(vector shape, T fill) : shape_(std::move(shape)), data_(compute_size(), fill) {} //! Construct from initializer_list shape explicit Tensor(std::initializer_list shape) : shape_(shape), data_(compute_size()) {} //! Construct from initializer_list shape with fill Tensor(std::initializer_list shape, T fill) : shape_(shape), data_(compute_size(), fill) {} //! 1D copy from raw pointer + count Tensor(const T* ptr, size_t count) : shape_({count}), data_(ptr, ptr + count) {} //! Copy from View (preserves view's shape) template explicit Tensor(const View& v) : shape_(v.shape_vec()) { size_t n = v.size(); data_.resize(n); for (size_t i = 0; i < n; ++i) data_[i] = v[i]; } //-------------------------------------------------------------------------- // Assignment //! Assignment from View template Tensor& operator=(const View& v) { shape_ = v.shape_vec(); size_t n = v.size(); data_.resize(n); for (size_t i = 0; i < n; ++i) data_[i] = v[i]; return *this; } //! Assignment from initializer_list of values (1D) Tensor& operator=(std::initializer_list vals) { shape_ = {vals.size()}; data_.assign(vals.begin(), vals.end()); return *this; } //-------------------------------------------------------------------------- // Accessors stored_type* data() { return data_.data(); } const stored_type* data() const { return data_.data(); } size_t size() const { return data_.size(); } const vector& shape() const { return shape_; } size_t shape(size_t dim) const { return dim < shape_.size() ? shape_[dim] : 0; } size_t ndim() const { return shape_.size(); } bool empty() const { return data_.empty(); } //-------------------------------------------------------------------------- // Indexing (row-major) template stored_type& operator()(Indices... indices) { const size_t idx[] = {static_cast(indices)...}; size_t off = 0; for (size_t d = 0; d < sizeof...(Indices); ++d) off = off * shape_[d] + idx[d]; return data_[off]; } template const stored_type& operator()(Indices... indices) const { const size_t idx[] = {static_cast(indices)...}; size_t off = 0; for (size_t d = 0; d < sizeof...(Indices); ++d) off = off * shape_[d] + idx[d]; return data_[off]; } stored_type& operator[](size_t i) { return data_[i]; } const stored_type& operator[](size_t i) const { return data_[i]; } //! First and last element stored_type& front() { return data_.front(); } const stored_type& front() const { return data_.front(); } stored_type& back() { return data_.back(); } const stored_type& back() const { return data_.back(); } //-------------------------------------------------------------------------- // Iterators iterator begin() { return data_.begin(); } iterator end() { return data_.end(); } const_iterator begin() const { return data_.begin(); } const_iterator end() const { return data_.end(); } const_iterator cbegin() const { return data_.cbegin(); } const_iterator cend() const { return data_.cend(); } //-------------------------------------------------------------------------- // Mutation void resize(const vector& shape) { shape_ = shape; data_.resize(compute_size()); } void resize(std::initializer_list shape) { shape_.assign(shape.begin(), shape.end()); data_.resize(compute_size()); } void reshape(const vector& new_shape) { shape_ = new_shape; } void fill(T val) { std::fill(data_.begin(), data_.end(), val); } //-------------------------------------------------------------------------- // View accessors //! Fix one axis at a given index, returning an (N-1)-dimensional view //! Multi-axis slice. Each argument corresponds to one axis and is either: //! - an integer (fixes that axis, rank-reducing) //! - All (keeps entire axis) //! - Range (keeps sub-range along that axis) //! Example: t.slice(0, all, range(2, 5)) == numpy t[0, :, 2:5] template View slice(First first, Rest... rest) { auto strides = compute_strides(); auto r = detail::compute_slice(shape_, strides, first, rest...); return { data_.data() + r.ptr_offset, std::move(r.shape), std::move(r.strides)}; } template View slice(First first, Rest... rest) const { auto strides = compute_strides(); auto r = detail::compute_slice(shape_, strides, first, rest...); return { data_.data() + r.ptr_offset, std::move(r.shape), std::move(r.strides)}; } //! Flat 1D view of all elements View flat() { return {data_.data(), {data_.size()}, {size_t(1)}}; } View flat() const { return {data_.data(), {data_.size()}, {size_t(1)}}; } //-------------------------------------------------------------------------- // Reductions and transforms //! Sum of all elements T sum() const { T s = T(0); for (size_t i = 0; i < data_.size(); ++i) s += data_[i]; return s; } //! Sum along an axis, reducing rank by 1 (defined out-of-line below) Tensor sum(size_t axis) const; //! Product of all elements T prod() const { T p = T(1); for (size_t i = 0; i < data_.size(); ++i) p *= data_[i]; return p; } //! True if any element is nonzero bool any() const { for (size_t i = 0; i < data_.size(); ++i) if (data_[i]) return true; return false; } //! True if all elements are nonzero bool all() const { for (size_t i = 0; i < data_.size(); ++i) if (!data_[i]) return false; return true; } //! Flat index of the minimum element size_t argmin() const { return static_cast(std::distance(data_.data(), std::min_element(data_.data(), data_.data() + data_.size()))); } //! Reverse element order along an axis (e.g. flip(0) reverses rows) Tensor flip(size_t axis) const { size_t outer_size = 1; for (size_t d = 0; d < axis; ++d) outer_size *= shape_[d]; size_t axis_size = shape_[axis]; size_t inner_size = 1; for (size_t d = axis + 1; d < shape_.size(); ++d) inner_size *= shape_[d]; Tensor r(shape_); for (size_t o = 0; o < outer_size; ++o) for (size_t a = 0; a < axis_size; ++a) for (size_t i = 0; i < inner_size; ++i) r.data_[(o * axis_size + (axis_size - 1 - a)) * inner_size + i] = data_[(o * axis_size + a) * inner_size + i]; return r; } //-------------------------------------------------------------------------- // Operators Tensor& operator+=(T val) { for (auto& x : data_) x += val; return *this; } Tensor& operator-=(T val) { for (auto& x : data_) x -= val; return *this; } Tensor& operator*=(T val) { for (auto& x : data_) x *= val; return *this; } Tensor& operator/=(T val) { for (auto& x : data_) x /= val; return *this; } Tensor& operator+=(const Tensor& o) { for (size_t i = 0; i < data_.size(); ++i) data_[i] += o.data_[i]; return *this; } Tensor& operator-=(const Tensor& o) { for (size_t i = 0; i < data_.size(); ++i) data_[i] -= o.data_[i]; return *this; } Tensor& operator*=(const Tensor& o) { for (size_t i = 0; i < data_.size(); ++i) data_[i] *= o.data_[i]; return *this; } Tensor& operator/=(const Tensor& o) { for (size_t i = 0; i < data_.size(); ++i) data_[i] /= o.data_[i]; return *this; } Tensor operator+(const Tensor& o) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data_[i] = data_[i] + o.data_[i]; return r; } Tensor operator-(const Tensor& o) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data_[i] = data_[i] - o.data_[i]; return r; } Tensor operator/(const Tensor& o) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data_[i] = data_[i] / o.data_[i]; return r; } Tensor operator*(const Tensor& o) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data_[i] = data_[i] * o.data_[i]; return r; } Tensor operator+(T val) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data_[i] = data_[i] + val; return r; } Tensor operator-(T val) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data_[i] = data_[i] - val; return r; } Tensor operator*(T val) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data_[i] = data_[i] * val; return r; } Tensor operator<=(T val) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data()[i] = data_[i] <= val; return r; } Tensor operator<(T val) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data()[i] = data_[i] < val; return r; } Tensor operator>=(T val) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data()[i] = data_[i] >= val; return r; } Tensor operator>(T val) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data()[i] = data_[i] > val; return r; } Tensor operator<(const Tensor& o) const { Tensor r(shape_); for (size_t i = 0; i < data_.size(); ++i) r.data()[i] = data_[i] < o.data_[i]; return r; } private: size_t compute_size() const { size_t s = 1; for (auto d : shape_) s *= d; return s; } //! Compute row-major strides from shape vector compute_strides() const { vector strides(shape_.size()); if (!shape_.empty()) { strides.back() = 1; for (int d = static_cast(shape_.size()) - 2; d >= 0; --d) strides[d] = strides[d + 1] * shape_[d + 1]; } return strides; } //-------------------------------------------------------------------------- // Data members vector shape_; vector> data_; }; //============================================================================== // Non-member operators (scalar op tensor) //============================================================================== template Tensor operator*(T val, const Tensor& arr) { return arr * val; } template Tensor operator+(T val, const Tensor& arr) { return arr + val; } // Mixed-type arithmetic: Tensor op Tensor -> Tensor // A SFINAE guard is used here, as without !is_same Tensor * Tensor // would be ambiguous between the member operator* and this non-member function. template::value>> Tensor operator*(const Tensor& a, const Tensor& b) { Tensor r(a.shape()); for (size_t i = 0; i < a.size(); ++i) r.data()[i] = static_cast(a.data()[i]) * static_cast(b.data()[i]); return r; } // Same SFINAE guard as operator* above. template::value>> Tensor operator/(const Tensor& a, const Tensor& b) { Tensor r(a.shape()); for (size_t i = 0; i < a.size(); ++i) r.data()[i] = static_cast(a.data()[i]) / static_cast(b.data()[i]); return r; } //============================================================================== // Out-of-line method definitions (require complete types) //============================================================================== template template View& View::operator=(const Tensor& other) { size_t n = size(); for (size_t i = 0; i < n; ++i) data_[flat_to_offset(i)] = static_cast(other.data()[i]); return *this; } template template View& View::operator+=(const Tensor& o) { size_t n = size(); for (size_t i = 0; i < n; ++i) data_[flat_to_offset(i)] += o.data()[i]; return *this; } template Tensor Tensor::sum(size_t axis) const { // Build output shape (all dims except the summed axis) vector out_shape; for (size_t d = 0; d < shape_.size(); ++d) if (d != axis) out_shape.push_back(shape_[d]); // Split dimensions into three zones: outer | axis | inner size_t outer_size = 1; for (size_t d = 0; d < axis; ++d) outer_size *= shape_[d]; size_t axis_size = shape_[axis]; size_t inner_size = 1; for (size_t d = axis + 1; d < shape_.size(); ++d) inner_size *= shape_[d]; Tensor result(out_shape, T(0)); for (size_t o = 0; o < outer_size; ++o) for (size_t a = 0; a < axis_size; ++a) for (size_t i = 0; i < inner_size; ++i) result.data()[o * inner_size + i] += data_[(o * axis_size + a) * inner_size + i]; return result; } //============================================================================== // StaticTensor2D: compile-time fixed 2D tensor. //============================================================================== template class StaticTensor2D { public: using value_type = T; //-------------------------------------------------------------------------- // Indexing //! Templated to accept enum class indices (e.g. GlobalTally, TallyResult) //! which don't implicitly convert to integer types. template T& operator()(I0 i, I1 j) { return data_[static_cast(i) * C + static_cast(j)]; } template const T& operator()(I0 i, I1 j) const { return data_[static_cast(i) * C + static_cast(j)]; } //-------------------------------------------------------------------------- // Accessors T* data() { return data_; } const T* data() const { return data_; } constexpr size_t size() const { return R * C; } std::array shape() const { return {R, C}; } //-------------------------------------------------------------------------- // Mutation void fill(T val) { std::fill(data_, data_ + R * C, val); } //-------------------------------------------------------------------------- // Iterators T* begin() { return data_; } T* end() { return data_ + R * C; } const T* begin() const { return data_; } const T* end() const { return data_ + R * C; } //-------------------------------------------------------------------------- // View accessors //! Multi-axis slice (same interface as Tensor/View). template View slice(First first, Rest... rest) { vector sh = {R, C}; vector st = {C, 1}; auto r = detail::compute_slice(sh, st, first, rest...); return {data_ + r.ptr_offset, std::move(r.shape), std::move(r.strides)}; } template View slice(First first, Rest... rest) const { vector sh = {R, C}; vector st = {C, 1}; auto r = detail::compute_slice(sh, st, first, rest...); return {data_ + r.ptr_offset, std::move(r.shape), std::move(r.strides)}; } //! Flat view (1D, contiguous) View flat() { return {data_, {R * C}, {size_t(1)}}; } View flat() const { return {data_, {R * C}, {size_t(1)}}; } private: //-------------------------------------------------------------------------- // Data members T data_[R * C] = {}; }; //============================================================================== // Non-member functions //============================================================================== template Tensor zeros(std::initializer_list shape) { vector s(shape); return Tensor(std::move(s), T(0)); } template Tensor zeros(const vector& shape) { return Tensor(shape, T(0)); } template Tensor ones(std::initializer_list shape) { vector s(shape); return Tensor(std::move(s), T(1)); } template Tensor ones(const vector& shape) { return Tensor(shape, T(1)); } template Tensor zeros_like(const Tensor& o) { return Tensor(o.shape(), T(0)); } template Tensor full_like(const Tensor& o, V val) { return Tensor(o.shape(), static_cast(val)); } //! Return a 1D tensor of n evenly spaced values from start to stop (inclusive) template Tensor linspace(T start, T stop, size_t n) { Tensor result({n}); if (n < 2) { result[0] = start; return result; } for (size_t i = 0; i < n; ++i) { result[i] = start + static_cast(i) * (stop - start) / static_cast(n - 1); } return result; } //! Concatenate two 1D tensors end-to-end template Tensor concatenate(const Tensor& a, const Tensor& b) { size_t total = a.size() + b.size(); Tensor result({total}); std::copy(a.data(), a.data() + a.size(), result.data()); std::copy(b.data(), b.data() + b.size(), result.data() + a.size()); return result; } //! Element-wise natural logarithm template Tensor log(const Tensor& a) { Tensor r(a.shape()); for (size_t i = 0; i < a.size(); ++i) r.data()[i] = std::log(a.data()[i]); return r; } //! Element-wise absolute value template Tensor abs(const Tensor& a) { Tensor r(a.shape()); for (size_t i = 0; i < a.size(); ++i) r.data()[i] = std::abs(a.data()[i]); return r; } //! Element-wise conditional: select from true_val where cond is true, //! otherwise use false_val template Tensor where( const Tensor& cond, const Tensor& true_val, V false_val) { Tensor r(cond.shape()); for (size_t i = 0; i < cond.size(); ++i) r.data()[i] = cond.data()[i] ? true_val.data()[i] : static_cast(false_val); return r; } //! Replace NaN/Inf values with finite substitutes template Tensor nan_to_num(const Tensor& a, T nan_val = T(0), T posinf_val = std::numeric_limits::max(), T neginf_val = std::numeric_limits::lowest()) { Tensor r(a.shape()); for (size_t i = 0; i < a.size(); ++i) { T val = a.data()[i]; if (std::isnan(val)) r.data()[i] = nan_val; else if (std::isinf(val)) r.data()[i] = val > 0 ? posinf_val : neginf_val; else r.data()[i] = val; } return r; } //============================================================================== // Type traits //============================================================================== //! Type trait that is true for Tensor and StaticTensor2D. //! Used by hdf5_interface.h to select the correct write_dataset overload. template struct is_tensor : std::false_type {}; template struct is_tensor> : std::true_type {}; template struct is_tensor> : std::true_type {}; } // namespace tensor } // namespace openmc #endif // OPENMC_TENSOR_H