diff --git a/include/xo/unit/Quantity2.hpp b/include/xo/unit/Quantity2.hpp new file mode 100644 index 00000000..afebb074 --- /dev/null +++ b/include/xo/unit/Quantity2.hpp @@ -0,0 +1,94 @@ +/** @file Quantity2.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "quantity2_concept.hpp" +#include "scaled_unit2.hpp" +#include "unit2.hpp" + +namespace xo { + namespace unit { + /** @class quantity + * @brief represent a scalar quantity with attached units. enforce dimensional consistency. + * + * Constexpr implementation, but units are explicitly represented: + * sizeof(Quantity2) > sizeof(Repr) + * + * Explicit unit representation allows introducing units at runtime, + * for example in python bindings + * + * Require: + * - Repr supports numeric operations (+, -, *, /) + * - Repr supports conversion from double. + **/ + template + class Quantity2 { + public: + using repr_type = Repr; + using unit_type = unit2; + using ratio_int_type = Int; + + public: + constexpr Quantity2(Repr scale, const unit2 & unit) + : scale_{scale}, unit_{unit} {} + + constexpr const repr_type & scale() const { return scale_; } + constexpr const unit_type & unit() const { return unit_; } + + constexpr Quantity2 unit_qty() const { return Quantity2(1, unit_); } + + constexpr Quantity2 reciprocal() const { return Quantity2(1.0 / scale_, unit_.reciprocal()); } + + template + static constexpr + auto multiply(const Quantity2 & x, const OtherQuantity & y) { + using r_repr_type = std::common_type_t; + using r_int_type = std::common_type_t; + + auto rr = detail::nu_product(x.unit(), y.unit()); + + r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_) + * rr.outer_scale_exact_.template to() + * static_cast(x.scale()) + * static_cast(y.scale())); + + return Quantity2(r_scale, + rr.natural_unit_); + } + + private: + /** @brief quantity represents this multiple of a unit amount **/ + Repr scale_ = Repr{}; + /** @brief unit for this quantity **/ + unit2 unit_; + }; /*Quantity2*/ + + /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) + **/ + template + inline constexpr Quantity2 + unit_qty(const scaled_unit2 & u) { + return Quantity2(u.outer_scale_exact_.template to() * ::sqrt(u.outer_scale_sq_), + u.natural_unit_); + } + + /** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr) + **/ + template + constexpr auto + operator* (const Quantity & x, const OtherQuantity & y) + { + return Quantity::multiply(x, y); + } + } /*namespace unit*/ +} /*namespace xo*/ + + +/** end Quantity2.hpp **/ diff --git a/include/xo/unit/Quantity2_iostream.hpp b/include/xo/unit/Quantity2_iostream.hpp new file mode 100644 index 00000000..0a1f2893 --- /dev/null +++ b/include/xo/unit/Quantity2_iostream.hpp @@ -0,0 +1,29 @@ +/** @file Quantity2_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Quantity2.hpp" +#include + +namespace xo { + namespace unit { + template + inline std::ostream & + operator<< (std::ostream & os, + const Quantity2 & x) + { + os << ""; + + return os; + } + } /*namespace unit*/ +} /*namespace xo*/ + +/** end Quantity2_iostream.hpp **/ diff --git a/include/xo/unit/bpu_store.hpp b/include/xo/unit/bpu_store.hpp new file mode 100644 index 00000000..b66fcd51 --- /dev/null +++ b/include/xo/unit/bpu_store.hpp @@ -0,0 +1,162 @@ +/** @file bpu_store.hpp **/ + +#pragma once + +#include "native_bpu2.hpp" + +namespace xo { + namespace unit { + /** @class basis_unit2_store + * @brief Store known basis units for runtime + **/ + template + struct basis_unit2_store { + basis_unit2_store() : bu_abbrev_vv_(static_cast(dim::n_dim)) { + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + this->bu_establish_abbrev_for(); + + this->bu_establish_abbrev_for(); + + this->bu_establish_abbrev_for(); + } + + /* e.g. + * [(1/1000000000, "nm"), (1/1000000, "um"), (1/1000, "mm"), (1/1, "m"), (1000/1, "km")] + */ + using native_scale_v = std::vector>; + + /** @brief get basis-unit abbreviation at runtime **/ + basis_unit2_abbrev_type bu_abbrev(dim basis_dim, + const scalefactor_ratio_type & scalefactor) const + { + const auto & bu_abbrev_v = bu_abbrev_vv_[static_cast(basis_dim)]; + + std::size_t i_abbrev = bu_abbrev_lub_ix(basis_dim, scalefactor, bu_abbrev_v); + + if ((i_abbrev < bu_abbrev_v.size()) + && (bu_abbrev_v[i_abbrev].first == scalefactor)) + { + return bu_abbrev_v[i_abbrev].second; + } else { + return units::bu_fallback_abbrev(basis_dim, scalefactor); + } + } + + /** @brief get basis-power-unit abbreviation at runtime **/ + bpu2_abbrev_type bpu_abbrev(dim basis_dim, + const scalefactor_ratio_type & scalefactor, + const power_ratio_type & power) + { + return abbrev::bpu2_abbrev(basis_dim, + scalefactor, + power); + } + + template + void bu_establish_abbrev_for() { + this->bu_establish_abbrev + (basis_unit2(BasisDim, + scalefactor_ratio_type(InnerScaleNum, InnerScaleDen)), + units::scaled_native_unit2_abbrev_v); + } + + /** @brief establish abbreviation @p abbrev for basis unit @p bu + **/ + void bu_establish_abbrev(const basis_unit2 & bu, + const basis_unit2_abbrev_type & abbrev) { + + auto & bu_abbrev_v = bu_abbrev_vv_[static_cast(bu.native_dim())]; + + std::int32_t i_abbrev = 0; + + if (!bu_abbrev_v.empty()) { + i_abbrev = bu_abbrev_lub_ix(bu.native_dim(), + bu.scalefactor(), + bu_abbrev_v); + } + + auto entry = std::make_pair(bu.scalefactor(), abbrev); + + if ((i_abbrev < bu_abbrev_v.size()) + && (bu_abbrev_v[i_abbrev].first == bu.scalefactor())) + { + bu_abbrev_v[i_abbrev] = entry; + } else { + bu_abbrev_v.insert(bu_abbrev_v.begin() + i_abbrev, entry); + } + } + + private: + /** @brief get least-upper-bound index position in bu_abbrev_v[] + * + * return value in [0, n] where n = bu_abbrev_v.size() + **/ + static std::size_t bu_abbrev_lub_ix(dim basis_dim, + const scalefactor_ratio_type & scalefactor, + const native_scale_v & bu_abbrev_v) + { + std::size_t n = bu_abbrev_v.size(); + + if (n == 0) + return 0; + + std::size_t lo = 0; + std::size_t hi = n-1; + + if (scalefactor <= bu_abbrev_v[lo].first) + return 0; + + auto cmp = (scalefactor <=> bu_abbrev_v[hi].first); + + if (cmp > 0) + return n; + + if (cmp == 0) + return hi; + + while (hi-lo > 1) { + /* inv: + * bu_abbrev_v[lo].first < scalefactor <= bu_abbrev_v[hi].first + */ + + std::size_t mid = lo + (hi - lo)/2; + + if (scalefactor > bu_abbrev_v[mid].first) + lo = mid; + else + hi = mid; + } + + return hi; + } + + private: + /* bu_abbrev_v[dim] holds known units for native unit dim */ + std::vector bu_abbrev_vv_; + }; + } /*namespace unit*/ +} /*namespace xo*/ + +/** end bpu_store.hpp **/ diff --git a/include/xo/unit/dim_iostream.hpp b/include/xo/unit/dim_iostream.hpp new file mode 100644 index 00000000..24b31326 --- /dev/null +++ b/include/xo/unit/dim_iostream.hpp @@ -0,0 +1,21 @@ +/** @file dim_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "dim_util.hpp" +#include + +namespace xo { + namespace unit { + inline std::ostream & + operator<<(std::ostream & os, dim x) { + os << dim2str(x); + return os; + } + } /*namespace unit*/ +} /*namespace xo*/ + +/** end dim_iostream.hpp **/ diff --git a/include/xo/unit/native_bpu2_iostream.hpp b/include/xo/unit/native_bpu2_iostream.hpp new file mode 100644 index 00000000..a748670c --- /dev/null +++ b/include/xo/unit/native_bpu2_iostream.hpp @@ -0,0 +1,29 @@ +/** @file native_bpu2_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/ratio/ratio_iostream.hpp" +#include "native_bpu2.hpp" +#include + +namespace xo { + namespace unit { + template + inline std::ostream & + operator<<(std::ostream & os, const bpu2 & x) { + os << ""; + + return os; + } + } /*namespace unit*/ +} /*namespace xo*/ + + +/** end native_bpu2_iostream.hpp **/ diff --git a/include/xo/unit/natural_unit.hpp b/include/xo/unit/natural_unit.hpp new file mode 100644 index 00000000..ecfea289 --- /dev/null +++ b/include/xo/unit/natural_unit.hpp @@ -0,0 +1,229 @@ +/** @file natural_unit.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "native_bpu2.hpp" +#include +#include + +namespace xo { + namespace unit { + /** @class natural_unit + * @brief an array representing the cartesian product of distinct basis-power-units + * + * 1. Each bpu in the array represents a power of a basis dimension, e.g. "meter" or "second^2". + * 2. Each bpu in an array has a different dimension id. + * For example dim::time, if present, appears once. + * 3. Basis dimensions can appear in any order. + * Order used for constructing abbreviations: will get @c "kg.m" or @c "m.kg" + * depending on the orderin of @c dim::distance and @c dim::mass in @c bpu_v_ + **/ + template + class natural_unit { + public: + using ratio_int_type = Int; + + public: + constexpr natural_unit() : n_bpu_{0} {} + + constexpr std::size_t n_bpu() const { return n_bpu_; } + constexpr bpu2 * bpu_v() const { return bpu_v_; } + + constexpr void push_back(const bpu2 & bpu) { + if (n_bpu_ < n_dim) + bpu_v_[n_bpu_++] = bpu; + } + + constexpr bpu2 & operator[](std::size_t i) { return bpu_v_[i]; } + constexpr const bpu2 & operator[](std::size_t i) const { return bpu_v_[i]; } + + private: + /** @brief the number of occupied slots in @c bpu_v_ **/ + std::size_t n_bpu_; + + /** @brief storage for basis power units **/ + bpu2 bpu_v_[n_dim]; + }; + + namespace detail { + template + constexpr void + push_bpu_array(natural_unit * p_target, Ts... args); + + template + constexpr void + push_bpu_array(natural_unit * p_target) {} + + template + constexpr void + push_bpu_array(natural_unit * p_target, T0 && bpu0, Ts... args) { + p_target->push_back(bpu0); + push_bpu_array(p_target, args...); + } + } + + template + struct bpu_array_maker { + template + static constexpr natural_unit + make_bpu_array(Ts... args) { + natural_unit bpu_array; + detail::push_bpu_array(&bpu_array, args...); + return bpu_array; + } + }; + + namespace detail { + /** + * Given bpu ~ (b.u)^p: + * - b = bpu.scalefactor + * - u = bpu.native_dim + * - p = bpu.power + * + * want to rewrite in the form a'.(b'.u)^p + * + * Can compute a' exactly iff p is integral. + * In that case: + * (b.u)^p = ((b/b').b'.u)^p + * = (b/b')^p.(b'.u)^p + * = a'.(b'.u)^p with a' = (b/b')^p + * + * Can write p = p0 + q, with p0 = floor(p) integral, q = frac(p) in [0,1) + * + * Then + * (b/b')^p = (b/b')^p0 * (b/b')^q + * + * we'll compute: + * - (b/b')^p0 exactly (as a ratio) + * - (b/b')^q inexactly (as a double) + **/ + + template + struct outer_scalefactor_result { + constexpr outer_scalefactor_result(const ratio::ratio & outer_scale_exact, + double outer_scale_sq) + : outer_scale_exact_{outer_scale_exact}, + outer_scale_sq_{outer_scale_sq} {} + + /* (b/b')^p0 */ + ratio::ratio outer_scale_exact_; + /* (b/b')^q -- until c++26 only allow q=0 or q=1/2 */ + double outer_scale_sq_; + }; + + template + struct bpu2_rescale_result { + constexpr bpu2_rescale_result(const bpu2 & bpu_rescaled, + const ratio::ratio & outer_scale_exact, + double outer_scale_sq) + : bpu_rescaled_{bpu_rescaled}, + outer_scale_exact_{outer_scale_exact}, + outer_scale_sq_{outer_scale_sq} + {} + + /* (b'.u)^p */ + bpu2 bpu_rescaled_; + /* (b/b')^p0 */ + ratio::ratio outer_scale_exact_; + /* [(b/b')^q]^2 -- until c++26 only allow q=0 or q=1/2 */ + double outer_scale_sq_; + }; + + template + constexpr + bpu2_rescale_result + bpu2_rescale(const bpu2 & orig, + const scalefactor_ratio_type & new_scalefactor) + { + ratio::ratio mult = (orig.scalefactor() / new_scalefactor); + + /* inv: p_frac in [0, 1) */ + auto p_frac = orig.power().frac(); + + /* asof c++26: replace mult_sq with ::pow(mult, p_frac) */ + double mult_sq = std::numeric_limits::quiet_NaN(); + + if (p_frac.den() == 1) { + mult_sq = 1.0; + } else if(p_frac.den() == 2) { + mult_sq = mult.template to(); + } else { + // remaining possibilities not supported until c++26 + } + + return bpu2_rescale_result(bpu2(orig.native_dim(), + new_scalefactor, + orig.power()), + mult.power(orig.power().floor()), + mult_sq); + } + + template + constexpr + outer_scalefactor_result + bpu_product_inplace(bpu2 * p_target_bpu, + const bpu2 & rhs_bpu_orig) + { + assert(rhs_bpu_orig.native_dim() == p_target_bpu->native_dim()); + + bpu2_rescale_result rhs_bpu_rr = bpu2_rescale(rhs_bpu_orig, + p_target_bpu->scalefactor()); + + *p_target_bpu = bpu2(p_target_bpu->native_dim(), + p_target_bpu->scalefactor(), + p_target_bpu->power() + rhs_bpu_orig.power()); + + return outer_scalefactor_result(rhs_bpu_rr.outer_scale_exact_, + rhs_bpu_rr.outer_scale_sq_); + } + + template + constexpr + outer_scalefactor_result + bpu_array_product_inplace(natural_unit * p_target, + const bpu2 & bpu) + { + std::size_t i = 0; + for (; i < p_target->n_bpu(); ++i) { + if ((*p_target)[i].native_dim() == bpu.native_dim()) { + outer_scalefactor_result retval = bpu_product_inplace(&((*p_target)[i]), bpu); + + /* TODO: strip 0 power */ + + return retval; + } + } + + /* control here: i=p_target->n_bpu() */ + p_target->push_back(bpu); + + return outer_scalefactor_result + (ratio::ratio(1, 1) /*outer_scale_exact*/, + 1.0 /*outer_scale_sq*/); + } + + template + constexpr natural_unit + nu_reciprocal(const natural_unit & nu) + { + natural_unit retval; + + for (std::size_t i = 0; i < nu.n_bpu(); ++i) + retval.push_back(nu[i].reciprocal()); + + return retval; + } /*nunit_reciprocal*/ + + } /*namespace detail*/ + + namespace nu2 { + constexpr auto nanogram = bpu_array_maker::make_bpu_array(make_unit_power(bu2::nanogram)); + constexpr auto microgram = bpu_array_maker::make_bpu_array(make_unit_power(bu2::microgram)); + } + } /*namespace unit*/ +} /*namespace xo*/ + +/** end natural_unit.hpp **/ diff --git a/include/xo/unit/natural_unit_iostream.hpp b/include/xo/unit/natural_unit_iostream.hpp new file mode 100644 index 00000000..931d8f22 --- /dev/null +++ b/include/xo/unit/natural_unit_iostream.hpp @@ -0,0 +1,28 @@ +/** @file natural_unit_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "natural_unit.hpp" +#include + +namespace xo { + namespace unit { + template + inline std::ostream & + operator<<(std::ostream & os, const natural_unit & x) { + os << " 0) + os << ", "; + os << x[i]; + } + os << "]>"; + return os; + } + } /*namespace unit*/ +} /*namespace xo*/ + +/** end natural_unit_iostream.hpp **/ diff --git a/include/xo/unit/quantity2.hpp b/include/xo/unit/quantity2.hpp new file mode 100644 index 00000000..9fb74ac2 --- /dev/null +++ b/include/xo/unit/quantity2.hpp @@ -0,0 +1,28 @@ +/** @file quantity2.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "bpu_array.hpp" + +namespace xo { + namespace unit { + /** @class quantity + * @brief represent a scalar quantity with attached units. enforce dimensional consistency. + * + * Constexpr implementation, can compute units at compile time + **/ + template + class quantity2 { + public: + using repr_type = Repr; + + private: + }; + } /*namespace unit*/ +} /*namespace xo*/ + + +/** end quantity2.hpp **/ diff --git a/include/xo/unit/quantity2_concept.hpp b/include/xo/unit/quantity2_concept.hpp new file mode 100644 index 00000000..45fe8748 --- /dev/null +++ b/include/xo/unit/quantity2_concept.hpp @@ -0,0 +1,23 @@ +/** @file quantity2_concept.hpp **/ + +#pragma once + +#include "unit_concept.hpp" +#include "numeric_concept.hpp" + +namespace xo { + namespace unit { + template + concept quantity2_concept = requires(Quantity qty, typename Quantity::repr_type repr) + { + typename Quantity::unit_type; + typename Quantity::repr_type; + + { qty.scale() } -> std::same_as; + //{ Quantity::unit_cstr() } -> std::same_as; + //{ Quantity::unit_quantity() } -> std::same_as; + //{ Quantity::promote(repr) } -> std::same_as; + } && (true //unit_concept + && numeric_concept); + } /*namespace unit*/ +} /*namespace xo*/ diff --git a/include/xo/unit/scaled_unit2.hpp b/include/xo/unit/scaled_unit2.hpp new file mode 100644 index 00000000..c6655a3e --- /dev/null +++ b/include/xo/unit/scaled_unit2.hpp @@ -0,0 +1,126 @@ +/** @file scaled_unit2.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "natural_unit.hpp" +//#include + +namespace xo { + namespace unit { + /** @class bpu2_array_rescale_result + * @brief Represents the product sqrt(outer_scale_sq) * outer_scale_exact * nat_unit + **/ + template + struct scaled_unit2 { + constexpr scaled_unit2(const natural_unit & nat_unit, + ratio::ratio outer_scale_exact, + double outer_scale_sq) + : natural_unit_{nat_unit}, + outer_scale_exact_{outer_scale_exact}, + outer_scale_sq_{outer_scale_sq} + {} + + constexpr scaled_unit2 reciprocal() const { + return scaled_unit2(nu_reciprocal(natural_unit_, + outer_scale_exact_.reciprocal(), + 1.0 / outer_scale_sq_)); + } + + natural_unit natural_unit_; + ratio::ratio outer_scale_exact_; + double outer_scale_sq_; + }; + + namespace detail { + template + constexpr auto make_unit_rescale_result(const natural_unit & bpuv) { + return scaled_unit2(bpuv, + ratio::ratio(1, 1), + 1.0); + } + } + + namespace su2 { + constexpr auto nanogram = detail::make_unit_rescale_result(nu2::nanogram); + constexpr auto microgram = detail::make_unit_rescale_result(nu2::microgram); + } + + namespace detail { + template + constexpr + detail::bpu2_rescale_result + bpu2_product(const bpu2 & lhs_bpu, + const bpu2 & rhs_bpu) + { + assert(lhs_bpu.native_dim() == rhs_bpu.native_dim()); + + bpu2 prod_bpu = lhs_bpu; + auto rr = bpu_product_inplace(&prod_bpu, rhs_bpu); + + return bpu2_rescale_result(prod_bpu, rr.outer_scale_exact_, rr.outer_scale_sq_); + } + + template + constexpr + scaled_unit2 + nu_product(const natural_unit & lhs_bpu_array, + const bpu2 & rhs_bpu) + { + natural_unit prod = lhs_bpu_array; + auto rr = bpu_array_product_inplace(&prod, rhs_bpu); + + return scaled_unit2(prod, + rr.outer_scale_exact_, + rr.outer_scale_sq_); + }; + + template + constexpr + scaled_unit2 + nu_product(const natural_unit & lhs_bpu_array, + const natural_unit & rhs_bpu_array) + { + natural_unit prod = lhs_bpu_array; + + /* accumulate product of scalefactors spun off by rescaling + * any basis-units in rhs_bpu_array that conflict with the same dimension + * in lh_bpu_array + */ + auto sfr = (detail::outer_scalefactor_result + (scalefactor_ratio_type(1, 1) /*outer_scale_exact*/, + 1.0 /*outer_scale_sq*/)); + + for (std::size_t i = 0; i < rhs_bpu_array.n_bpu(); ++i) { + auto sfr2 = bpu_array_product_inplace(&prod, rhs_bpu_array[i]); + + sfr.outer_scale_exact_ = sfr.outer_scale_exact_ * sfr2.outer_scale_exact_; + sfr.outer_scale_sq_ *= sfr2.outer_scale_sq_; + } + + return scaled_unit2(prod, + sfr.outer_scale_exact_, + sfr.outer_scale_sq_); + } + + } + + template + inline constexpr scaled_unit2 + operator* (const scaled_unit2 & x_unit, + const scaled_unit2 & y_unit) + { + auto rr = detail::nu_product(x_unit.natural_unit_, + y_unit.natural_unit_); + + return (scaled_unit2 + (rr.natural_unit_, + rr.outer_scale_exact_ * x_unit.outer_scale_exact_ * y_unit.outer_scale_exact_, + rr.outer_scale_sq_ * x_unit.outer_scale_sq_ * y_unit.outer_scale_sq_)); + } + } /*namespace unit*/ +} /*namespace xo*/ + +/** end scaled_unit2.hpp **/ diff --git a/include/xo/unit/scaled_unit_iostream.hpp b/include/xo/unit/scaled_unit_iostream.hpp new file mode 100644 index 00000000..64525991 --- /dev/null +++ b/include/xo/unit/scaled_unit_iostream.hpp @@ -0,0 +1,27 @@ +/** @file scaled_unit_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "scaled_unit2.hpp" +#include + +namespace xo { + namespace unit { + template + inline std::ostream & + operator<<(std::ostream & os, const scaled_unit2 & x) { + os << ""; + + return os; + }; + } /*namespace unit*/ +} /*namespace xo*/ + +/** end scaled_unit_iostream.hpp **/ diff --git a/include/xo/unit/unit2.hpp b/include/xo/unit/unit2.hpp new file mode 100644 index 00000000..3fe9b622 --- /dev/null +++ b/include/xo/unit/unit2.hpp @@ -0,0 +1,24 @@ +/** @file unit2.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "natural_unit.hpp" + +namespace xo { + namespace unit { + /** @class unit2 + * @brief represent an arbitrary unit along with dimension details + * + * For example, + * kg.m.s^-2 or + * (kilogram * meter) / (second * second) + **/ + template + using unit2 = natural_unit; + } /*namespace unit*/ +} /*namespace xo*/ + +/** end unit2.hpp **/