LCOV - code coverage report
Current view: top level - variable - variable.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 202 210 96.2 %
Date: 2024-11-17 01:47:58 Functions: 46 47 97.9 %

          Line data    Source code
       1             : // SPDX-License-Identifier: BSD-3-Clause
       2             : // Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
       3             : /// @file
       4             : /// @author Simon Heybrock
       5             : #include <units/units.hpp>
       6             : 
       7             : #include "scipp/variable/variable.h"
       8             : 
       9             : #include "scipp/core/dtype.h"
      10             : #include "scipp/core/eigen.h"
      11             : #include "scipp/variable/arithmetic.h"
      12             : #include "scipp/variable/except.h"
      13             : #include "scipp/variable/variable_concept.h"
      14             : #include "scipp/variable/variable_factory.h"
      15             : 
      16             : namespace scipp::variable {
      17             : 
      18             : namespace {
      19             : /// Types that default to unit=units::dimensionless. Everything else is
      20             : /// units::none.
      21             : const std::tuple<double, float, int64_t, int32_t, core::time_point,
      22             :                  Eigen::Vector3d, Eigen::Matrix3d, Eigen::Affine3d,
      23             :                  core::Quaternion, core::Translation>
      24             :     default_dimensionless_dtypes;
      25             : 
      26             : template <class... Ts>
      27      202589 : bool is_default_dimensionless(DType type, std::tuple<Ts...>) {
      28      202589 :   return ((type == dtype<Ts>) || ...);
      29             : }
      30             : } // namespace
      31             : 
      32      202589 : units::Unit default_unit_for(const DType type) {
      33      202589 :   return is_default_dimensionless(type, default_dimensionless_dtypes)
      34      202589 :              ? units::dimensionless
      35      202589 :              : units::none;
      36             : }
      37             : 
      38             : /// Construct from parent with same dtype, unit, and has_variances but new dims.
      39             : ///
      40             : /// In the case of bucket variables the buffer size is set to zero.
      41       42628 : Variable::Variable(const Variable &parent, const Dimensions &dims)
      42       42628 :     : m_dims(dims), m_strides(dims),
      43       42628 :       m_object(parent.data().makeDefaultFromParent(dims.volume())) {}
      44             : 
      45             : // TODO there is no size check here!
      46     1709954 : Variable::Variable(const Dimensions &dims, VariableConceptHandle data)
      47     1709954 :     : m_dims(dims), m_strides(dims), m_object(std::move(data)) {}
      48             : 
      49           2 : Variable::Variable(const llnl::units::precise_measurement &m)
      50           2 :     : Variable(m.value() * units::Unit(m.units())) {}
      51             : 
      52             : namespace {
      53     1802170 : void check_nested_in_assign(const Variable &lhs, const Variable &rhs) {
      54     1802170 :   if (!rhs.is_valid() || rhs.dtype() != dtype<Variable>) {
      55     1802141 :     return;
      56             :   }
      57             :   // In principle we should also check when the RHS contains DataArrays or
      58             :   // Datasets. But those are copied when stored in Variables,
      59             :   // so no check needed here.
      60          49 :   for (const auto &nested : rhs.values<Variable>()) {
      61          35 :     if (&lhs == &nested) {
      62          10 :       throw std::invalid_argument("Cannot assign Variable, the right hand side "
      63             :                                   "contains a reference to the left hand side. "
      64          20 :                                   "Reference cycles are not allowed.");
      65             :     }
      66          25 :     check_nested_in_assign(lhs, nested);
      67          29 :   }
      68             : }
      69             : } // namespace
      70             : 
      71         884 : Variable &Variable::operator=(const Variable &other) {
      72         884 :   return *this = Variable(other);
      73             : }
      74             : 
      75     1802145 : Variable &Variable::operator=(Variable &&other) {
      76     1802145 :   check_nested_in_assign(*this, other);
      77     1802135 :   m_dims = other.m_dims;
      78     1802135 :   m_strides = other.m_strides;
      79     1802135 :   m_offset = other.m_offset;
      80     1802135 :   m_object = std::move(other.m_object);
      81     1802135 :   m_readonly = other.m_readonly;
      82     1802135 :   m_aligned = other.m_aligned;
      83     1802135 :   return *this;
      84             : }
      85             : 
      86      161946 : void Variable::setDataHandle(VariableConceptHandle object) {
      87      161946 :   if (object->size() != m_object->size())
      88           1 :     throw except::DimensionError("Cannot replace by model of different size.");
      89      161945 :   m_object = object;
      90      161945 : }
      91             : 
      92    22778467 : const Dimensions &Variable::dims() const {
      93    22778467 :   if (!is_valid())
      94           1 :     throw std::runtime_error("invalid variable");
      95    22778466 :   return m_dims;
      96             : }
      97             : 
      98      167852 : scipp::index Variable::ndim() const {
      99      167852 :   if (!is_valid())
     100           0 :     throw std::runtime_error("invalid variable");
     101      167852 :   return m_dims.ndim();
     102             : }
     103             : 
     104    53576119 : DType Variable::dtype() const { return data().dtype(); }
     105             : 
     106     9322985 : bool Variable::has_variances() const { return data().has_variances(); }
     107             : 
     108     1334461 : void Variable::expect_can_set_unit(const units::Unit &unit) const {
     109     1334461 :   if (this->unit() != unit && is_slice())
     110          17 :     throw except::UnitError("Partial view on data of variable cannot be used "
     111          34 :                             "to change the unit.");
     112     1334444 : }
     113             : 
     114     6405927 : const units::Unit &Variable::unit() const { return m_object->unit(); }
     115             : 
     116      865313 : void Variable::setUnit(const units::Unit &unit) {
     117      865313 :   expect_writable();
     118      865302 :   expect_can_set_unit(unit);
     119      865292 :   m_object->setUnit(unit);
     120      865288 : }
     121             : 
     122       40521 : Dim Variable::dim() const {
     123       40521 :   core::expect::ndim_is(dims(), 1);
     124       40514 :   return dims().inner();
     125             : }
     126             : 
     127             : namespace {
     128      423883 : bool compare(const Variable &a, const Variable &b, bool equal_nan) {
     129      423883 :   if (equal_nan && a.is_same(b))
     130        1396 :     return true;
     131      422487 :   if (!a.is_valid() || !b.is_valid())
     132           2 :     return a.is_valid() == b.is_valid();
     133             :   // Note: Not comparing strides
     134      422485 :   if (a.unit() != b.unit())
     135         244 :     return false;
     136      422241 :   if (a.dims() != b.dims())
     137         557 :     return false;
     138      421684 :   if (a.dtype() != b.dtype())
     139         162 :     return false;
     140      421522 :   if (a.has_variances() != b.has_variances())
     141         157 :     return false;
     142      421365 :   if (a.dims().volume() == 0 && a.dims() == b.dims())
     143        7055 :     return true;
     144      828620 :   return a.dims() == b.dims() &&
     145      828620 :          (equal_nan ? a.data().equals_nan(a, b) : a.data().equals(a, b));
     146             : }
     147             : } // namespace
     148             : 
     149      420030 : bool Variable::operator==(const Variable &other) const {
     150      420030 :   return compare(*this, other, false);
     151             : }
     152             : 
     153        3853 : bool equals_nan(const Variable &a, const Variable &b) {
     154        3853 :   return compare(a, b, true);
     155             : }
     156             : 
     157        7566 : bool Variable::operator!=(const Variable &other) const {
     158        7566 :   return !(*this == other);
     159             : }
     160             : 
     161    70507645 : const VariableConcept &Variable::data() const & { return *m_object; }
     162             : 
     163     3682226 : VariableConcept &Variable::data() & {
     164     3682226 :   expect_writable();
     165     3682208 :   return *m_object;
     166             : }
     167             : 
     168      208898 : const VariableConceptHandle &Variable::data_handle() const { return m_object; }
     169             : 
     170      431783 : scipp::span<const scipp::index> Variable::strides() const {
     171      431783 :   return scipp::span<const scipp::index>(&*m_strides.begin(),
     172      863566 :                                          &*m_strides.begin() + dims().ndim());
     173             : }
     174             : 
     175      803807 : scipp::index Variable::stride(const Dim dim) const {
     176      803807 :   return m_strides[dims().index(dim)];
     177             : }
     178             : 
     179           9 : scipp::index Variable::offset() const { return m_offset; }
     180             : 
     181     8276857 : core::ElementArrayViewParams Variable::array_params() const {
     182     8276857 :   return {m_offset, dims(), m_strides, {}};
     183             : }
     184             : 
     185      808664 : Variable Variable::slice(const Slice params) const {
     186      808664 :   core::expect::validSlice(dims(), params);
     187      808564 :   Variable out(*this);
     188      808564 :   if (params == Slice{})
     189        3751 :     return out;
     190      804813 :   const auto dim = params.dim();
     191      804813 :   const auto begin = params.begin();
     192      804813 :   const auto end = params.end();
     193      804813 :   const auto stride = params.stride();
     194      804813 :   const auto index = out.m_dims.index(dim);
     195      804813 :   out.m_offset += begin * m_strides[index];
     196      804813 :   if (end == -1) {
     197      118791 :     out.m_strides.erase(index);
     198      118791 :     out.m_dims.erase(dim);
     199             :   } else {
     200      686022 :     static_cast<Sizes &>(out.m_dims) = out.m_dims.slice(params);
     201      686022 :     out.m_strides[index] *= stride;
     202             :   }
     203      804813 :   return out;
     204           0 : }
     205             : 
     206        3990 : void Variable::validateSlice(const Slice &s, const Variable &data) const {
     207        3990 :   core::expect::validSlice(this->dims(), s);
     208        3990 :   if (variableFactory().has_variances(data) !=
     209        3990 :       variableFactory().has_variances(*this)) {
     210           2 :     auto variances_message = [](const auto &variable) {
     211             :       return "does" +
     212           4 :              std::string(variableFactory().has_variances(variable) ? ""
     213             :                                                                    : " NOT") +
     214           4 :              " have variances.";
     215             :     };
     216           2 :     throw except::VariancesError("Invalid slice operation. Slice " +
     217           3 :                                  variances_message(data) + "Variable " +
     218           2 :                                  variances_message(*this));
     219             :   }
     220        3989 :   if (variableFactory().elem_unit(data) != variableFactory().elem_unit(*this))
     221           1 :     throw except::UnitError(
     222           1 :         "Invalid slice operation. Slice has unit: " +
     223           3 :         to_string(variableFactory().elem_unit(data)) +
     224           4 :         " Variable has unit: " + to_string(variableFactory().elem_unit(*this)));
     225        3988 :   if (variableFactory().elem_dtype(data) != variableFactory().elem_dtype(*this))
     226           2 :     throw except::TypeError("Invalid slice operation. Slice has dtype " +
     227           3 :                             to_string(variableFactory().elem_dtype(data)) +
     228           2 :                             ". Variable has dtype " +
     229           2 :                             to_string(variableFactory().elem_dtype(*this)));
     230        3987 : }
     231             : 
     232        3929 : Variable &Variable::setSlice(const Slice params, const Variable &data) {
     233        3929 :   validateSlice(params, data);
     234        3929 :   copy(data, slice(params));
     235        3915 :   return *this;
     236             : }
     237             : 
     238       83014 : Variable Variable::broadcast(const Dimensions &target) const {
     239       83014 :   expect::includes(target, dims());
     240       83012 :   auto out = target.volume() == dims().volume() ? *this : as_const();
     241       83012 :   out.m_dims = target;
     242       83012 :   out.m_strides.clear();
     243      146667 :   for (const auto &d : target.labels())
     244       63655 :     out.m_strides.push_back(dims().contains(d) ? m_strides[dims().index(d)]
     245             :                                                : 0);
     246       83012 :   return out;
     247           0 : }
     248             : 
     249         259 : Variable Variable::fold(const Dim dim, const Dimensions &target) const {
     250         259 :   auto out(*this);
     251         259 :   out.m_dims = core::fold(dims(), dim, target);
     252         258 :   out.m_strides.clear();
     253         258 :   const Strides substrides(target);
     254         555 :   for (scipp::index i_in = 0; i_in < dims().ndim(); ++i_in) {
     255         297 :     if (dims().label(i_in) == dim)
     256         803 :       for (scipp::index i_target = 0; i_target < target.ndim(); ++i_target)
     257         545 :         out.m_strides.push_back(m_strides[i_in] * substrides[i_target]);
     258             :     else
     259          39 :       out.m_strides.push_back(m_strides[i_in]);
     260             :   }
     261         516 :   return out;
     262         259 : }
     263             : 
     264        6554 : Variable Variable::transpose(const scipp::span<const Dim> order) const {
     265        6554 :   auto transposed(*this);
     266        6562 :   transposed.m_strides = core::transpose(m_strides, dims(), order);
     267        6546 :   transposed.m_dims = core::transpose(dims(), order);
     268        6546 :   return transposed;
     269           8 : }
     270             : 
     271             : /// Return new variable with renamed dimensions.
     272             : ///
     273             : /// The `fail_on_unknown` option is used internally for implementing rename_dims
     274             : /// in DataArray and related classes.
     275        1990 : Variable Variable::rename_dims(const std::vector<std::pair<Dim, Dim>> &names,
     276             :                                const bool fail_on_unknown) const {
     277        1990 :   auto out(*this);
     278             :   // names is a vector for predictable order
     279        1990 :   out.m_dims = out.m_dims.rename_dims(names, fail_on_unknown);
     280        1989 :   return out;
     281           1 : }
     282             : 
     283    26130519 : bool Variable::is_valid() const noexcept { return m_object.operator bool(); }
     284             : 
     285       73251 : bool Variable::is_slice() const {
     286             :   // TODO Is this condition sufficient?
     287       73251 :   return m_offset != 0 || m_dims.volume() != data().size();
     288             : }
     289             : 
     290      137754 : bool Variable::is_readonly() const noexcept { return m_readonly; }
     291             : 
     292        6916 : bool Variable::is_same(const Variable &other) const noexcept {
     293        6916 :   return std::tie(m_dims, m_strides, m_offset, m_object) ==
     294        6916 :          std::tie(other.m_dims, other.m_strides, other.m_offset,
     295       13832 :                   other.m_object);
     296             : }
     297             : 
     298      361618 : bool Variable::is_aligned() const noexcept { return m_aligned; }
     299             : 
     300      409970 : void Variable::set_aligned(bool aligned) noexcept { m_aligned = aligned; }
     301             : 
     302        3593 : void Variable::setVariances(const Variable &v) {
     303        3593 :   expect_writable();
     304        3582 :   if (is_slice())
     305          11 :     throw except::VariancesError(
     306          22 :         "Cannot add variances via sliced view of Variable.");
     307        3571 :   if (v.is_valid()) {
     308        3538 :     core::expect::equals(unit(), v.unit());
     309        3534 :     core::expect::equals(dims(), v.dims());
     310             :   }
     311        3567 :   data().setVariances(v);
     312        3562 : }
     313             : 
     314             : namespace detail {
     315           0 : void throw_keyword_arg_constructor_bad_dtype(const DType dtype) {
     316           0 :   throw except::TypeError("Cannot create the Variable with type " +
     317           0 :                           to_string(dtype) +
     318           0 :                           " with such values and/or variances.");
     319             : }
     320             : } // namespace detail
     321             : 
     322      952707 : Variable Variable::bin_indices() const {
     323      952707 :   auto out{*this};
     324      952707 :   out.m_object = data().bin_indices();
     325      952707 :   return out;
     326           0 : }
     327             : 
     328       70679 : Variable Variable::as_const() const {
     329       70679 :   Variable out(*this);
     330       70679 :   out.m_readonly = true;
     331       70679 :   return out;
     332             : }
     333             : 
     334     4551132 : void Variable::expect_writable() const {
     335     4551132 :   if (m_readonly)
     336          40 :     throw except::VariableError("Read-only flag is set, cannot mutate data.");
     337     4551092 : }
     338             : } // namespace scipp::variable

Generated by: LCOV version 1.14