LCOV - code coverage report
Current view: top level - include/xo/unit - natural_unit.hpp (source / functions) Hit Total Coverage
Test: out3.info Lines: 73 80 91.2 %
Date: 1980-01-01 00:00:00 Functions: 8 8 100.0 %

          Line data    Source code
       1             : /** @file natural_unit.hpp
       2             :  *
       3             :  *  Author: Roland Conybeare
       4             :  **/
       5             : 
       6             : #pragma once
       7             : 
       8             : #include "bpu.hpp"
       9             : #include <cmath>
      10             : #include <cassert>
      11             : 
      12             : namespace xo {
      13             :     namespace qty {
      14             :         using nu_abbrev_type = flatstring<32>;
      15             : 
      16             :         template <typename Int>
      17             :         class natural_unit;
      18             : 
      19             :         /** @class natural_unit
      20             :          *  @brief an array representing the cartesian product of distinct basis-power-units
      21             :          *
      22             :          *  1. Quantities are represented as a multiple of a natural unit
      23             :          *  2. Each bpu in the array represents a power of a basis dimension,  e.g. "meter" or "second^2".
      24             :          *  3. Each bpu in an array has a different dimension id.
      25             :          *     For example dim::time, if present, appears once.
      26             :          *  4. Basis dimensions can appear in any order.
      27             :          *     Order used for constructing abbreviations: will get @c "kg.m" or @c "m.kg"
      28             :          *     depending on the orderin of @c dim::distance and @c dim::mass in @c bpu_v_
      29             :          **/
      30             :         template <typename Int>
      31             :         class natural_unit {
      32             :         public:
      33             :             using ratio_int_type = Int;
      34             : 
      35             :         public:
      36             :             constexpr natural_unit() : n_bpu_{0} {}
      37             : 
      38         102 :             constexpr std::size_t n_bpu() const { return n_bpu_; }
      39          16 :             constexpr bool is_dimensionless() const { return n_bpu_ == 0; }
      40             : 
      41             :             constexpr bpu<Int> * bpu_v() const { return bpu_v_; }
      42             : 
      43             :             constexpr natural_unit reciprocal() const {
      44             :                 natural_unit retval;
      45             : 
      46             :                 for (std::size_t i = 0; i < this->n_bpu(); ++i)
      47             :                     retval.push_back((*this)[i].reciprocal());
      48             : 
      49             :                 return retval;
      50             :             }
      51             : 
      52          12 :             constexpr nu_abbrev_type abbrev() const {
      53          12 :                 nu_abbrev_type retval;
      54             : 
      55          34 :                 for (std::size_t i = 0; i < n_bpu_; ++i) {
      56          22 :                     if (i > 0)
      57          10 :                         retval.append(".");
      58          22 :                     retval.append(bpu_v_[i].abbrev(), 0, -1);
      59             :                 }
      60             : 
      61          12 :                 return retval;
      62             :             }
      63             : 
      64             :             /** @brief remove bpu at position @p p **/
      65          20 :             constexpr void remove_bpu(size_t p) {
      66          21 :                 for (std::size_t i = p; i+1 < n_bpu_; ++i)
      67           1 :                     bpu_v_[i] = bpu_v_[i+1];
      68             : 
      69          20 :                 --n_bpu_;
      70          20 :             }
      71             : 
      72           5 :             constexpr void push_back(const bpu<Int> & bpu) {
      73           4 :                 if (n_bpu_ < n_dim)
      74           5 :                     bpu_v_[n_bpu_++] = bpu;
      75             :             }
      76             : 
      77          25 :             constexpr bpu<Int> & operator[](std::size_t i) { return bpu_v_[i]; }
      78          47 :             constexpr const bpu<Int> & operator[](std::size_t i) const { return bpu_v_[i]; }
      79             : 
      80             :             template <typename Int2>
      81           4 :             constexpr natural_unit<Int2> to_repr() const {
      82          16 :                 natural_unit<Int2> retval;
      83             : 
      84             :                 std::size_t i = 0;
      85           8 :                 for (; i < n_bpu_; ++i)
      86           8 :                     retval.push_back(bpu_v_[i].template to_repr<Int2>());
      87             : 
      88           4 :                 return retval;
      89             :             }
      90             : 
      91             :         private:
      92             :             /** @brief the number of occupied slots in @c bpu_v_ **/
      93             :             std::size_t n_bpu_;
      94             : 
      95             :             /** @brief storage for basis power units **/
      96             :             bpu<Int> bpu_v_[n_dim];
      97             :         };
      98             : 
      99             :         namespace detail {
     100             :             template <typename Int, typename... Ts>
     101             :             constexpr void
     102             :             push_bpu_array(natural_unit<Int> * p_target, Ts... args);
     103             : 
     104             :             template <typename Int>
     105             :             constexpr void
     106             :             push_bpu_array(natural_unit<Int> * p_target) {}
     107             : 
     108             :             template <typename Int, typename T0, typename... Ts>
     109             :             constexpr void
     110             :             push_bpu_array(natural_unit<Int> * p_target, T0 && bpu0, Ts... args) {
     111             :                 p_target->push_back(bpu0);
     112             :                 push_bpu_array(p_target, args...);
     113             :             }
     114             :         }
     115             : 
     116             :         template <typename Int>
     117             :         struct bpu_array_maker {
     118             :             template <typename... Ts>
     119             :             static constexpr natural_unit<Int>
     120             :             make_bpu_array(Ts... args) {
     121             :                 natural_unit<Int> bpu_array;
     122             :                 detail::push_bpu_array(&bpu_array, args...);
     123             :                 return bpu_array;
     124             :             }
     125             :         };
     126             : 
     127             :         namespace detail {
     128             :             /**
     129             :              *  Given bpu ~ (b.u)^p:
     130             :              *  - b = bpu.scalefactor
     131             :              *  - u = bpu.native_dim
     132             :              *  - p = bpu.power
     133             :              *
     134             :              *  want to rewrite in the form a'.(b'.u)^p
     135             :              *
     136             :              *  Can compute a' exactly iff p is integral.
     137             :              *  In that case:
     138             :              *    (b.u)^p = ((b/b').b'.u)^p
     139             :              *            = (b/b')^p.(b'.u)^p
     140             :              *            = a'.(b'.u)^p   with  a' = (b/b')^p
     141             :              *
     142             :              *  Can write p = p0 + q,  with p0 = floor(p) integral,  q = frac(p) in [0,1)
     143             :              *
     144             :              *  Then
     145             :              *    (b/b')^p = (b/b')^p0 * (b/b')^q
     146             :              *
     147             :              *  we'll compute:
     148             :              *  - (b/b')^p0 exactly (as a ratio)
     149             :              *  - (b/b')^q inexactly (as a double)
     150             :              **/
     151             : 
     152             :             template <typename Int>
     153             :             struct outer_scalefactor_result {
     154          46 :                 constexpr outer_scalefactor_result(const ratio::ratio<Int> & outer_scale_exact,
     155             :                                                    double outer_scale_sq)
     156          46 :                     : outer_scale_exact_{outer_scale_exact},
     157          22 :                       outer_scale_sq_{outer_scale_sq} {}
     158             : 
     159             :                 /* (b/b')^p0 */
     160             :                 ratio::ratio<Int> outer_scale_exact_;
     161             :                 /* (b/b')^q -- until c++26 only allow q=0 or q=1/2 */
     162             :                 double  outer_scale_sq_;
     163             :             };
     164             : 
     165             :             template <typename Int>
     166             :             struct bpu2_rescale_result {
     167          23 :                 constexpr bpu2_rescale_result(const bpu<Int> & bpu_rescaled,
     168             :                                               const ratio::ratio<Int> & outer_scale_exact,
     169             :                                               double outer_scale_sq)
     170          23 :                     : bpu_rescaled_{bpu_rescaled},
     171          23 :                       outer_scale_exact_{outer_scale_exact},
     172          23 :                       outer_scale_sq_{outer_scale_sq}
     173             :                     {}
     174             : 
     175             :                 /* (b'.u)^p */
     176             :                 bpu<Int> bpu_rescaled_;
     177             :                 /* (b/b')^p0 */
     178             :                 ratio::ratio<Int> outer_scale_exact_;
     179             :                 /* [(b/b')^q]^2 -- until c++26 only allow q=0 or q=1/2 */
     180             :                 double  outer_scale_sq_;
     181             :             };
     182             : 
     183             :             template <typename Int>
     184             :             constexpr
     185             :             bpu2_rescale_result<Int>
     186          23 :             bpu2_rescale(const bpu<Int> & orig,
     187             :                          const scalefactor_ratio_type & new_scalefactor)
     188             :             {
     189          23 :                 ratio::ratio<Int> mult = (orig.scalefactor() / new_scalefactor);
     190             : 
     191             :                 /* inv: p_frac in [0, 1) */
     192          23 :                 auto p_frac = orig.power().frac();
     193             : 
     194             :                 /* asof c++26: replace mult_sq with ::pow(mult, p_frac) */
     195          23 :                 double mult_sq = std::numeric_limits<double>::quiet_NaN();
     196             : 
     197          23 :                 if (p_frac.den() == 1) {
     198             :                     mult_sq = 1.0;
     199           0 :                 } else if(p_frac.den() == 2) {
     200           0 :                     mult_sq = mult.template to<double>();
     201             :                 } else {
     202             :                     // remaining possibilities not supported until c++26
     203             :                 }
     204             : 
     205          23 :                 return bpu2_rescale_result<Int>(bpu<Int>(orig.native_dim(),
     206             :                                                           new_scalefactor,
     207             :                                                           orig.power()),
     208          23 :                                                 mult.power(orig.power().floor()),
     209          23 :                                                 mult_sq);
     210             :             }
     211             : 
     212             :             template <typename Int>
     213             :             constexpr
     214             :             outer_scalefactor_result<Int>
     215           3 :             bpu_product_inplace(bpu<Int> * p_target_bpu,
     216             :                                 const bpu<Int> & rhs_bpu_orig)
     217             :             {
     218           3 :                 assert(rhs_bpu_orig.native_dim() == p_target_bpu->native_dim());
     219             : 
     220           3 :                 bpu2_rescale_result<Int> rhs_bpu_rr = bpu2_rescale(rhs_bpu_orig,
     221             :                                                                    p_target_bpu->scalefactor());
     222             : 
     223           3 :                 *p_target_bpu = bpu<Int>(p_target_bpu->native_dim(),
     224             :                                          p_target_bpu->scalefactor(),
     225           3 :                                          p_target_bpu->power() + rhs_bpu_orig.power());
     226             : 
     227             :                 return outer_scalefactor_result<Int>(rhs_bpu_rr.outer_scale_exact_,
     228           3 :                                                      rhs_bpu_rr.outer_scale_sq_);
     229             :             }
     230             : 
     231             :             template <typename Int>
     232             :             constexpr
     233             :             outer_scalefactor_result<Int>
     234          20 :             bpu_ratio_inplace(bpu<Int> * p_target_bpu,
     235             :                               const bpu<Int> & rhs_bpu_orig)
     236             :             {
     237          20 :                 assert(rhs_bpu_orig.native_dim() == p_target_bpu->native_dim());
     238             : 
     239          20 :                 bpu2_rescale_result<Int> rhs_bpu_rr = bpu2_rescale(rhs_bpu_orig,
     240             :                                                                    p_target_bpu->scalefactor());
     241             : 
     242          20 :                 *p_target_bpu = bpu<Int>(p_target_bpu->native_dim(),
     243             :                                          p_target_bpu->scalefactor(),
     244          20 :                                          p_target_bpu->power() - rhs_bpu_orig.power());
     245             : 
     246          20 :                 return outer_scalefactor_result<Int>(power_ratio_type(1,1) / rhs_bpu_rr.outer_scale_exact_,
     247          20 :                                                      1.0 / rhs_bpu_rr.outer_scale_sq_);
     248             :             }
     249             : 
     250             :             template <typename Int>
     251             :             constexpr
     252             :             outer_scalefactor_result<Int>
     253           3 :             nu_product_inplace(natural_unit<Int> * p_target,
     254             :                                const bpu<Int> & bpu)
     255             :             {
     256           3 :                 std::size_t i = 0;
     257           3 :                 for (; i < p_target->n_bpu(); ++i) {
     258           3 :                     auto * p_target_bpu = &((*p_target)[i]);
     259             : 
     260           3 :                     if (p_target_bpu->native_dim() == bpu.native_dim()) {
     261           3 :                         outer_scalefactor_result<Int> retval = bpu_product_inplace(p_target_bpu, bpu);
     262             : 
     263           3 :                         if (p_target_bpu->power().is_zero()) {
     264             :                             /* dimension assoc'd with *p_target_bpu has been cancelled */
     265           0 :                             p_target->remove_bpu(i);
     266             :                         }
     267             : 
     268           3 :                         return retval;
     269             :                     }
     270             :                 }
     271             : 
     272             :                 /* control here: i=p_target->n_bpu()
     273             :                  * Dimension represented by bpu does not already appear in *p_target.
     274             :                  * Adopt bpu's scalefactor
     275             :                  */
     276             : 
     277           0 :                 p_target->push_back(bpu);
     278             : 
     279           0 :                 return outer_scalefactor_result<Int>
     280           0 :                     (ratio::ratio<Int>(1, 1) /*outer_scale_exact*/,
     281           0 :                      1.0 /*outer_scale_sq*/);
     282             :             }
     283             : 
     284             :             template <typename Int>
     285             :             constexpr
     286             :             outer_scalefactor_result<Int>
     287          21 :             nu_ratio_inplace(natural_unit<Int> * p_target,
     288             :                              const bpu<Int> & bpu)
     289             :             {
     290          21 :                 std::size_t i = 0;
     291          23 :                 for (; i < p_target->n_bpu(); ++i) {
     292          22 :                     auto * p_target_bpu = &((*p_target)[i]);
     293             : 
     294          22 :                     if (p_target_bpu->native_dim() == bpu.native_dim()) {
     295          20 :                         outer_scalefactor_result<Int> retval = bpu_ratio_inplace(p_target_bpu, bpu);
     296             : 
     297          20 :                         if (p_target_bpu->power().is_zero()) {
     298             :                             /* dimension assoc'd with *p_target_bpu has been cancelled */
     299          20 :                             p_target->remove_bpu(i);
     300             :                         }
     301             : 
     302          20 :                         return retval;
     303             :                     }
     304             :                 }
     305             : 
     306             :                 /* here: i=p_target->n_bpu()
     307             :                  * Dimension represented by bpu does not already appear in *p_target.
     308             :                  * Adopt bpu's scalefactor
     309             :                  */
     310             : 
     311           2 :                 p_target->push_back(bpu.reciprocal());
     312             : 
     313           1 :                 return outer_scalefactor_result<Int>
     314           1 :                     (ratio::ratio<Int>(1, 1) /*outer_scale_exact*/,
     315           1 :                      1.0 /*outer_scale_sq*/);
     316             :             }
     317             : 
     318             :         } /*namespace detail*/
     319             : 
     320             :         namespace nu2 {
     321             :             constexpr auto nanogram = bpu_array_maker<std::int64_t>::make_bpu_array(make_unit_power<std::int64_t>(bu::nanogram));
     322             :             constexpr auto microgram = bpu_array_maker<std::int64_t>::make_bpu_array(make_unit_power<std::int64_t>(bu::microgram));
     323             :         }
     324             :     } /*namespace qty*/
     325             : } /*namespace xo*/
     326             : 
     327             : /** end natural_unit.hpp **/

Generated by: LCOV version 1.0