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 <cmath> 6 : #include <units/units.hpp> 7 : 8 : #include "scipp/core/element/to_unit.h" 9 : #include "scipp/core/time_point.h" 10 : #include "scipp/variable/arithmetic.h" 11 : #include "scipp/variable/astype.h" 12 : #include "scipp/variable/to_unit.h" 13 : #include "scipp/variable/transform.h" 14 : 15 : namespace scipp::variable { 16 : 17 : namespace { 18 291 : bool greater_than_days(const units::Unit &unit) { 19 : static constexpr auto days_multiplier = 20 : llnl::units::precise::day.multiplier(); 21 291 : if (!unit.has_same_base(units::s)) { 22 1 : throw except::UnitError("Cannot convert unit of datetime with non-time " 23 1 : "unit, got `" + 24 3 : to_string(unit) + "`."); 25 : } 26 290 : return unit.underlying().multiplier() >= days_multiplier; 27 : } 28 : } // namespace 29 : 30 60147 : Variable to_unit(const Variable &var, const units::Unit &unit, 31 : const CopyPolicy copy) { 32 60147 : const auto var_unit = variableFactory().elem_unit(var); 33 60147 : if (unit == var_unit) 34 50890 : return copy == CopyPolicy::Always ? variable::copy(var) : var; 35 9257 : if ((var_unit == units::none) || (unit == units::none)) 36 2 : throw except::UnitError("Unit conversion to / from None is not permitted."); 37 : const auto scale = 38 9255 : llnl::units::quick_convert(var_unit.underlying(), unit.underlying()); 39 9255 : if (std::isnan(scale)) 40 57 : throw except::UnitError("Conversion from `" + to_string(var_unit) + 41 76 : "` to `" + to_string(unit) + "` is not valid."); 42 9391 : if (var.dtype() == dtype<core::time_point> && 43 9390 : (greater_than_days(variableFactory().elem_unit(var)) || 44 136 : greater_than_days(unit))) { 45 30 : throw except::UnitError( 46 : "Unit conversions for datetimes with a unit of days or greater " 47 30 : "are not supported. Attempted conversion from `" + 48 90 : to_string(var_unit) + "` to `" + to_string(unit) + "`. " + 49 : "This limitation exists because such conversions would require " 50 60 : "information about calendars and time zones."); 51 : } 52 9205 : Variable scalevar; 53 : // Need to make sure that errors due to machine precision actually affect 54 : // decimal places, otherwise the approach based on std::round will do nothing. 55 9205 : const auto base_scale = scale > 1e6 ? scale * 1e-6 : scale; 56 18410 : if (const auto iscale = std::round(base_scale); 57 9205 : (std::abs(base_scale - iscale) < 1e-12 * std::abs(base_scale))) { 58 2973 : if (var.dtype() == dtype<int64_t> || var.dtype() == dtype<core::time_point>) 59 96 : scalevar = int64_t{scale > 1e6 ? 1000000 : 1} * 60 96 : static_cast<int64_t>(iscale) * unit; 61 : else 62 2877 : scalevar = (scale > 1e6 ? 1000000 : 1) * iscale * unit; 63 : } else { 64 6232 : scalevar = scale * unit; 65 : } 66 9205 : return variable::transform(var, scalevar, core::element::to_unit, "to_unit"); 67 9205 : } 68 : 69 : } // namespace scipp::variable