OpenMC/include/openmc/tensor.h

1210 lines
34 KiB
C++

//! \file tensor.h
//! \brief Multi-dimensional tensor types for OpenMC.
//!
//! Tensor<T> is the primary type: a dynamic-rank owning container that stores
//! elements contiguously in row-major order. View<T> is a lightweight
//! non-owning reference into a Tensor's storage, returned by the slice()
//! method and flat(). StaticTensor2D<T,R,C> 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 <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <limits>
#include <type_traits>
namespace openmc {
namespace tensor {
//==============================================================================
// Forward declarations
//==============================================================================
template<typename T>
class Tensor;
template<typename T, size_t R, size_t C>
class StaticTensor2D;
//==============================================================================
// Storage type mapping
//
// std::vector<bool> 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<bool> stores one byte per
// element with normal reference semantics.
//==============================================================================
template<typename T>
struct storage_type_map {
using type = T;
};
template<>
struct storage_type_map<bool> {
using type = unsigned char;
};
template<typename T>
using storage_type = typename storage_type_map<T>::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<typename I>
inline
typename std::enable_if<std::is_integral<I>::value || std::is_enum<I>::value,
SliceArg>::type
to_slice_arg(I i)
{
return {SliceArg::INDEX, static_cast<size_t>(i), 0};
}
//! Result of a slice computation: pointer offset + new shape/strides
struct SliceResult {
size_t ptr_offset;
vector<size_t> shape;
vector<size_t> strides;
};
//! Compute the result of applying slice arguments to shape/strides
template<typename First, typename... Rest>
SliceResult compute_slice(const vector<size_t>& shape,
const vector<size_t>& 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<size_t> new_shape;
vector<size_t> 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<T>: 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<typename T>
class View {
public:
//--------------------------------------------------------------------------
// Constructors
View(T* data, vector<size_t> shape, vector<size_t> 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<typename... Indices>
T& operator()(Indices... indices)
{
const size_t idx[] = {static_cast<size_t>(indices)...};
size_t off = 0;
for (size_t d = 0; d < sizeof...(Indices); ++d)
off += idx[d] * strides_[d];
return data_[off];
}
template<typename... Indices>
const T& operator()(Indices... indices) const
{
const size_t idx[] = {static_cast<size_t>(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<size_t>& 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<typename First, typename... Rest>
View<T> 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<typename First, typename... Rest>
View<const T> 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<T> 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<typename U>
View& operator=(const View<U>& 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<typename U>
View& operator=(const Tensor<U>& other);
//! Compound addition from Tensor (forward-declared, defined after Tensor)
template<typename U>
View& operator+=(const Tensor<U>& 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<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<T*> = mutable iterator (from non-const View)
// view_iterator<const T*> = read-only iterator (from const View)
template<typename Ptr>
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<T>;
using difference_type = std::ptrdiff_t;
using pointer = Ptr;
using reference = decltype(*std::declval<Ptr>());
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<difference_type>(count_) -
static_cast<difference_type>(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<int>(ndim_) - 1; d >= 0; --d) {
off += (flat % shape_[d]) * strides_[d];
flat /= shape_[d];
}
return off;
}
};
using iterator = view_iterator<T*>;
using const_iterator = view_iterator<const T*>;
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<int>(shape_.size()) - 1; d >= 0; --d) {
off += (flat % shape_[d]) * strides_[d];
flat /= shape_[d];
}
return off;
}
T* data_;
vector<size_t> shape_;
vector<size_t> strides_;
};
//==============================================================================
// Tensor<T>: dynamic-rank N-dimensional tensor.
//
// Stores elements in a contiguous row-major vector<storage_type<T>>
// with a dynamic shape.
//==============================================================================
template<typename T>
class Tensor {
public:
using value_type = T;
using stored_type = storage_type<T>;
using iterator = typename vector<stored_type>::iterator;
using const_iterator = typename vector<stored_type>::const_iterator;
//--------------------------------------------------------------------------
// Constructors
Tensor() = default;
//! Construct with shape (uninitialized for arithmetic types via vector
//! resize)
explicit Tensor(vector<size_t> shape)
: shape_(std::move(shape)), data_(compute_size())
{}
//! Construct with shape and fill value
Tensor(vector<size_t> shape, T fill)
: shape_(std::move(shape)), data_(compute_size(), fill)
{}
//! Construct from initializer_list shape
explicit Tensor(std::initializer_list<size_t> shape)
: shape_(shape), data_(compute_size())
{}
//! Construct from initializer_list shape with fill
Tensor(std::initializer_list<size_t> 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<typename U>
explicit Tensor(const View<U>& 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<typename U>
Tensor& operator=(const View<U>& 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<T> 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<size_t>& 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<typename... Indices>
stored_type& operator()(Indices... indices)
{
const size_t idx[] = {static_cast<size_t>(indices)...};
size_t off = 0;
for (size_t d = 0; d < sizeof...(Indices); ++d)
off = off * shape_[d] + idx[d];
return data_[off];
}
template<typename... Indices>
const stored_type& operator()(Indices... indices) const
{
const size_t idx[] = {static_cast<size_t>(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<size_t>& shape)
{
shape_ = shape;
data_.resize(compute_size());
}
void resize(std::initializer_list<size_t> shape)
{
shape_.assign(shape.begin(), shape.end());
data_.resize(compute_size());
}
void reshape(const vector<size_t>& 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<typename First, typename... Rest>
View<stored_type> 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<typename First, typename... Rest>
View<const stored_type> 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<stored_type> flat()
{
return {data_.data(), {data_.size()}, {size_t(1)}};
}
View<const stored_type> 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<T> 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<size_t>(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<bool> operator<=(T val) const
{
Tensor<bool> r(shape_);
for (size_t i = 0; i < data_.size(); ++i)
r.data()[i] = data_[i] <= val;
return r;
}
Tensor<bool> operator<(T val) const
{
Tensor<bool> r(shape_);
for (size_t i = 0; i < data_.size(); ++i)
r.data()[i] = data_[i] < val;
return r;
}
Tensor<bool> operator>=(T val) const
{
Tensor<bool> r(shape_);
for (size_t i = 0; i < data_.size(); ++i)
r.data()[i] = data_[i] >= val;
return r;
}
Tensor<bool> operator>(T val) const
{
Tensor<bool> r(shape_);
for (size_t i = 0; i < data_.size(); ++i)
r.data()[i] = data_[i] > val;
return r;
}
Tensor<bool> operator<(const Tensor& o) const
{
Tensor<bool> 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<size_t> compute_strides() const
{
vector<size_t> strides(shape_.size());
if (!shape_.empty()) {
strides.back() = 1;
for (int d = static_cast<int>(shape_.size()) - 2; d >= 0; --d)
strides[d] = strides[d + 1] * shape_[d + 1];
}
return strides;
}
//--------------------------------------------------------------------------
// Data members
vector<size_t> shape_;
vector<storage_type<T>> data_;
};
//==============================================================================
// Non-member operators (scalar op tensor)
//==============================================================================
template<typename T>
Tensor<T> operator*(T val, const Tensor<T>& arr)
{
return arr * val;
}
template<typename T>
Tensor<T> operator+(T val, const Tensor<T>& arr)
{
return arr + val;
}
// Mixed-type arithmetic: Tensor<T1> op Tensor<T2> -> Tensor<double>
// A SFINAE guard is used here, as without !is_same Tensor<T> * Tensor<T>
// would be ambiguous between the member operator* and this non-member function.
template<typename T1, typename T2,
typename = std::enable_if_t<!std::is_same<T1, T2>::value>>
Tensor<double> operator*(const Tensor<T1>& a, const Tensor<T2>& b)
{
Tensor<double> r(a.shape());
for (size_t i = 0; i < a.size(); ++i)
r.data()[i] =
static_cast<double>(a.data()[i]) * static_cast<double>(b.data()[i]);
return r;
}
// Same SFINAE guard as operator* above.
template<typename T1, typename T2,
typename = std::enable_if_t<!std::is_same<T1, T2>::value>>
Tensor<double> operator/(const Tensor<T1>& a, const Tensor<T2>& b)
{
Tensor<double> r(a.shape());
for (size_t i = 0; i < a.size(); ++i)
r.data()[i] =
static_cast<double>(a.data()[i]) / static_cast<double>(b.data()[i]);
return r;
}
//==============================================================================
// Out-of-line method definitions (require complete types)
//==============================================================================
template<typename T>
template<typename U>
View<T>& View<T>::operator=(const Tensor<U>& other)
{
size_t n = size();
for (size_t i = 0; i < n; ++i)
data_[flat_to_offset(i)] = static_cast<T>(other.data()[i]);
return *this;
}
template<typename T>
template<typename U>
View<T>& View<T>::operator+=(const Tensor<U>& o)
{
size_t n = size();
for (size_t i = 0; i < n; ++i)
data_[flat_to_offset(i)] += o.data()[i];
return *this;
}
template<typename T>
Tensor<T> Tensor<T>::sum(size_t axis) const
{
// Build output shape (all dims except the summed axis)
vector<size_t> 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<T> 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<T, R, C>: compile-time fixed 2D tensor.
//==============================================================================
template<typename T, size_t R, size_t C>
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<typename I0, typename I1>
T& operator()(I0 i, I1 j)
{
return data_[static_cast<size_t>(i) * C + static_cast<size_t>(j)];
}
template<typename I0, typename I1>
const T& operator()(I0 i, I1 j) const
{
return data_[static_cast<size_t>(i) * C + static_cast<size_t>(j)];
}
//--------------------------------------------------------------------------
// Accessors
T* data() { return data_; }
const T* data() const { return data_; }
constexpr size_t size() const { return R * C; }
std::array<size_t, 2> 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<typename First, typename... Rest>
View<T> slice(First first, Rest... rest)
{
vector<size_t> sh = {R, C};
vector<size_t> 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<typename First, typename... Rest>
View<const T> slice(First first, Rest... rest) const
{
vector<size_t> sh = {R, C};
vector<size_t> 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<T> flat() { return {data_, {R * C}, {size_t(1)}}; }
View<const T> flat() const { return {data_, {R * C}, {size_t(1)}}; }
private:
//--------------------------------------------------------------------------
// Data members
T data_[R * C] = {};
};
//==============================================================================
// Non-member functions
//==============================================================================
template<typename T>
Tensor<T> zeros(std::initializer_list<size_t> shape)
{
vector<size_t> s(shape);
return Tensor<T>(std::move(s), T(0));
}
template<typename T>
Tensor<T> zeros(const vector<size_t>& shape)
{
return Tensor<T>(shape, T(0));
}
template<typename T>
Tensor<T> ones(std::initializer_list<size_t> shape)
{
vector<size_t> s(shape);
return Tensor<T>(std::move(s), T(1));
}
template<typename T>
Tensor<T> ones(const vector<size_t>& shape)
{
return Tensor<T>(shape, T(1));
}
template<typename T>
Tensor<T> zeros_like(const Tensor<T>& o)
{
return Tensor<T>(o.shape(), T(0));
}
template<typename T, typename V>
Tensor<T> full_like(const Tensor<T>& o, V val)
{
return Tensor<T>(o.shape(), static_cast<T>(val));
}
//! Return a 1D tensor of n evenly spaced values from start to stop (inclusive)
template<typename T>
Tensor<T> linspace(T start, T stop, size_t n)
{
Tensor<T> result({n});
if (n < 2) {
result[0] = start;
return result;
}
for (size_t i = 0; i < n; ++i) {
result[i] =
start + static_cast<T>(i) * (stop - start) / static_cast<T>(n - 1);
}
return result;
}
//! Concatenate two 1D tensors end-to-end
template<typename T>
Tensor<T> concatenate(const Tensor<T>& a, const Tensor<T>& b)
{
size_t total = a.size() + b.size();
Tensor<T> 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<typename T>
Tensor<T> log(const Tensor<T>& a)
{
Tensor<T> 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<typename T>
Tensor<T> abs(const Tensor<T>& a)
{
Tensor<T> 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<typename T, typename V>
Tensor<T> where(
const Tensor<bool>& cond, const Tensor<T>& true_val, V false_val)
{
Tensor<T> r(cond.shape());
for (size_t i = 0; i < cond.size(); ++i)
r.data()[i] =
cond.data()[i] ? true_val.data()[i] : static_cast<T>(false_val);
return r;
}
//! Replace NaN/Inf values with finite substitutes
template<typename T>
Tensor<T> nan_to_num(const Tensor<T>& a, T nan_val = T(0),
T posinf_val = std::numeric_limits<T>::max(),
T neginf_val = std::numeric_limits<T>::lowest())
{
Tensor<T> 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<typename T>
struct is_tensor : std::false_type {};
template<typename T>
struct is_tensor<Tensor<T>> : std::true_type {};
template<typename T, size_t R, size_t C>
struct is_tensor<StaticTensor2D<T, R, C>> : std::true_type {};
} // namespace tensor
} // namespace openmc
#endif // OPENMC_TENSOR_H