build: move unit+quantity feature from xo-observable -> xo-unit

This commit is contained in:
Roland Conybeare 2024-04-01 19:34:04 -04:00
commit 3c1f8389c7
20 changed files with 6324 additions and 0 deletions

56
CMakeLists.txt Normal file
View file

@ -0,0 +1,56 @@
# xo-unit/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(xo_unit VERSION 1.0)
enable_language(CXX)
# common XO cmake macros (see proj/xo-cmake)
include(cmake/xo-bootstrap-macros.cmake)
# ----------------------------------------------------------------
# unit test setup
enable_testing()
# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON)
add_code_coverage()
# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc.
# we're not interested in code coverage for these sources.
# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves;
# rather, want coverage on the code that the unit tests exercise.
#
# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target
#
add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*)
# ----------------------------------------------------------------
# c++ settings
# one-time project-specific c++ flags. usually empty
set(PROJECT_CXX_FLAGS "")
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2")
add_definitions(${PROJECT_CXX_FLAGS})
xo_toplevel_compile_options()
# ----------------------------------------------------------------
#add_subdirectory(src/unit)
add_subdirectory(utest)
add_subdirectory(docs)
# ----------------------------------------------------------------
# provide find_package() support for reactor customers
set(SELF_LIB xo_unit)
xo_add_headeronly_library(${SELF_LIB})
xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets)
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
# ----------------------------------------------------------------
# dependencies
#xo_headeronly_dependency(${SELF_LIB} randomgen)
# etc..
# end CMakeLists.txt

View file

@ -0,0 +1,14 @@
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix"))
# default to typical install location for xo-project-macros
set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake)
endif()
if (NOT XO_SUBMODULE_BUILD)
message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
#
include(xo_macros/xo-project-macros)

View file

@ -0,0 +1,17 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
# note: changes to find_dependency() calls here
# must coordinate with xo_dependency() calls
# in xo-reactor/src/reactor/CMakeLists.txt
#
#find_dependency(reflect)
#find_dependency(subsys)
#find_dependency(Eigen3)
#find_dependency(webutil)
#find_dependency(printjson)
#find_dependency(callback)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

60
docs/CMakeLists.txt Normal file
View file

@ -0,0 +1,60 @@
# xo-unit/docs/CMakeLists.txt
# copied from xo-observable/docs/CMakeLists.txt
set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
#set(DOX_DEPS ${PROJECT_SOURCE_DIR}/mainpage.dox) # not yet
set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR})
set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox)
set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html)
#set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html)
#set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html)
#
## sphinx .rst files reachable from cmake-examples/docs
#file(GLOB_RECURSE SPHINX_RST_FILES ${CMAKE_CURRENT_SOURCE_DIR} *.rst)
set(ALL_LIBRARY_TARGETS xo_unit) # todo: automate this from xo-cmake macros
#set(ALL_UTEST_TARGETS "") # todo: automate this from xo-cmake macros
# look for doxygen executable
find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED)
message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}")
if (XO_SUBMODULE_BUILD)
# in submodule build, rely on toplevel docs/CMakeLists.txt file instead
else()
# build docs starting from here only in standalone build.
# otherwise use top-level doxygen setup instead.
# TODO:
# 1. move Doxyfile.in to xo-cmake project
# 2. replace this command section with xo-cmake macro
#
configure_file(
Doxyfile.in ${DOX_CONFIG_FILE}
FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
@ONLY)
file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR})
add_custom_command(
OUTPUT ${DOX_INDEX_FILE}
DEPENDS ${DOX_DEPS} ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS}
COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
MAIN_DEPENDENCY ${DOX_CONFIG_FILE}
COMMENT "Generating docs (doxygen)")
# To build this target
# $ cmake --build .build -j -- doxygen
# or
# $ cd .build
# $ make doxygen
#
add_custom_target(
doxygen
DEPENDS ${DOX_INDEX_FILE}
)
endif()

2814
docs/Doxyfile.in Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,86 @@
/** @file basis_unit.hpp **/
#include "dim_util.hpp"
#include "ratio_util.hpp"
namespace xo {
namespace obs {
/** @class basis_unit
*
* @brief A dimensionless multiple with natively-specified (i.e. at compile-time) dimension
**/
template <dim BasisDim,
native_unit_id NativeUnitId = native_unit_for_v<BasisDim>,
typename InnerScale = std::ratio<1>>
struct basis_unit {
static_assert(ratio_concept<InnerScale>);
static constexpr dim c_native_dim = BasisDim;
static constexpr basis_unit c_native_unit = NativeUnitId;
using scalefactor_type = InnerScale;
};
/** Using struct wrapper so we can partially specialize
* Specializations in [dimension.hpp], see also
**/
template <dim dim_id>
struct native_unit_abbrev_helper;
template <>
struct native_unit_abbrev_helper<dim::mass> {
static constexpr auto value = stringliteral("g");
};
template <>
struct native_unit_abbrev_helper<dim::distance> {
static constexpr auto value = stringliteral("m");
};
template <>
struct native_unit_abbrev_helper<dim::time> {
static constexpr auto value = stringliteral("s");
};
template<>
struct native_unit_abbrev_helper<dim::currency> {
static constexpr auto value = stringliteral("ccy");
};
template<>
struct native_unit_abbrev_helper<dim::price> {
static constexpr auto value = stringliteral("px");
};
template<dim BasisDim>
constexpr auto native_unit_abbrev_v = native_unit_abbrev_helper<BasisDim>::value;
// ----- scaled_native_unit_abbrev_helper -----
namespace units {
/* Require: InnerScale is ratio type; InnerScale >= 1 */
template <dim BasisDim, typename InnerScale>
struct scaled_native_unit_abbrev;
template <dim BasisDim>
struct scaled_native_unit_abbrev<BasisDim, std::ratio<1>> {
static constexpr auto value = native_unit_abbrev_v<BasisDim>;
};
template <dim BasisDim, typename InnerScale>
struct scaled_native_unit_abbrev {
/* e.g. unit of '10000 grams' will have abbrev '1000g' in absence
* of a specialization for scaled_native_unit_abbrev
*/
static constexpr auto value = stringliteral_concat(stringliteral_from_ratio<InnerScale>().value_,
native_unit_abbrev_helper<BasisDim>::value.value_);
};
template <dim BasisDim, typename InnerScale>
constexpr auto scaled_native_unit_abbrev_v = scaled_native_unit_abbrev<BasisDim, InnerScale>::value;
}
} /*namespace obs*/
} /*namespace xo*/
/** end basis_unit.hpp **/

View file

@ -0,0 +1,89 @@
/* @file dimension_concept.hpp */
#pragma once
#include "native_bpu_concept.hpp"
//#include <concepts>
namespace xo {
namespace obs {
/** checks most non-empty BPU (basis power unit) node types;
* cannot check BpuList::rest_type, because concept definition
* can't (as of c++23) be recursive.
*
* As workaround, revert to type traits, seend below.
**/
template <typename BpuList>
concept bpu_node_concept = requires(BpuList bpulist)
{
typename BpuList::front_type;
typename BpuList::rest_type;
}
&& (native_bpu_concept<typename BpuList::front_type>
&& (std::is_integral_v<decltype(BpuList::n_dimension)>)
//&& (std::same_as<typename BpuList::front_type, void>
// or nonempty_bpu_list_concept<BpuList::rest_type>))
);
namespace detail {
// ------------------------------------------------------------
/* check for 'list of native_bpu_concept'.
* Need type trait for this, to access partial specializations
*/
template < typename BpuList >
struct bpu_list_traits;
/* void (representing empty list) is fine */
template <>
struct bpu_list_traits<void> : public std::true_type {};
/* non-void must satisfy bpu-list rules */
template <typename BpuList>
struct bpu_list_traits {
/* checks everything except BpuList::rest_type */
static constexpr bool _value1 = bpu_node_concept<BpuList>;
static constexpr bool _value2 = bpu_list_traits<typename BpuList::rest_type>::value;
static constexpr bool value = (_value1 && _value2);
};
// ----------------------------------------------------------------
template <typename BpuList>
constexpr bool bpu_list_v = bpu_list_traits<BpuList>::value;
}
/* may want to rename this -> native_bpu_list */
template <typename BpuList>
concept bpu_list_concept = detail::bpu_list_v<BpuList>;
// ----------------------------------------------------------------
/* TODO: retire in favor of unit_concept? */
template <typename Dimension>
concept dimension_concept = requires(Dimension dim)
{
typename Dimension::dim_type;
typename Dimension::canon_type;
}
&& (bpu_list_concept<typename Dimension::dim_type>
&& bpu_list_concept<typename Dimension::canon_type>
);
// ----------------------------------------------------------------
template <typename Unit>
concept unit_concept = requires(Unit unit)
{
typename Unit::scalefactor_type;
typename Unit::dim_type;
typename Unit::canon_type;
}
&& (ratio_concept<typename Unit::scalefactor_type>
&& bpu_list_concept<typename Unit::dim_type>
&& bpu_list_concept<typename Unit::canon_type>);
} /*namespace obs*/
} /*namespace xo*/
/* end dimension_concept.hpp */

View file

@ -0,0 +1,516 @@
/* @file dimension_impl.hpp */
#pragma once
#include "dimension_concept.hpp"
#include "native_bpu.hpp"
#include "dim_util.hpp"
#include "ratio_util.hpp"
#include <json/value.h>
#include <ratio>
#include <algorithm>
#include <numeric>
#include <string_view>
namespace xo {
/* TODO:
* - bpu_list -> bpu_node
*/
namespace obs {
// ----- lookup_bpu -----
/**
* Select from dimension_impl by known index value
*
* Example:
* using t1 = native_bpu<dim::currency, std::ratio<1>, std::ratio<1,1>>;
* using t2 = native_bpu<dim::time, std::ratio<60>, std::ratio<-1,2>>;
* using dim = dimension_impl<t1,t2>
*
* then
* lookup_bpu<dim,0> --> t1
* lookup_bpu<dim,1> --> t2
**/
template <typename Dim, int Index>
struct lookup_bpu {
using power_unit_type = lookup_bpu<typename Dim::rest_type, Index-1>::power_unit_type;
};
template <typename Dim>
struct lookup_bpu<Dim, 0> {
using power_unit_type = Dim::front_type;
};
// ----- di_find_bpu -----
/**
* @brief Select from dimension_impl by native_dim_id
*
* Example:
* using t1 = native_bpu<dim::time, std::ratio<60>, std::ratio<-2>>;
* using t2 = native_bpu<dim::currency, std::ratio<1>, std::ratio<1>>;
* using di = dimension_impl<t1,t2>;
*
* then
* di_find_bpu<dim::time> -> t1
* di_find_bpu<dim::currency> -> t2
* di_find_bpu<dim::mass> -> native_bpu {dim::mass, std::ratio<1>, std::ratio<0>}
**/
template <typename BpuList, dim BasisDim>
struct di_find_bpu;
/**
* @brief Aux template helper for di_find_bpu<..>
**/
template <typename Front, typename Rest, dim BasisDim, bool MatchesFront = (Front::c_native_dim == BasisDim)>
struct di_find_bpu_aux;
/** specialization for non-empty BpuList **/
template <typename BpuList, dim BasisDim>
struct di_find_bpu {
using type = di_find_bpu_aux<typename BpuList::front_type, typename BpuList::rest_type, BasisDim>::type;
};
/** specialization for empty BpuList **/
template <dim BasisDim>
struct di_find_bpu<void, BasisDim> {
using type = bpu<BasisDim, std::ratio<1>, std::ratio<0>>;
};
template <typename Front, typename Rest, dim BasisDim>
struct di_find_bpu_aux<Front, Rest, BasisDim, /*MatchesFront*/ true> {
using type = Front;
};
template <typename Front, typename Rest, dim BasisDim>
struct di_find_bpu_aux<Front, Rest, BasisDim, /*MatchesFront*/ false> {
using type = di_find_bpu<Rest, BasisDim>::type;
};
// ----------------------------------------------------------------
/**
* Promise:
* - bpu_list::front_type
* - bpu_list::rest_type
* - bpu_node_concept<bpulist<P,D>>
**/
template <typename P, typename D = void>
struct bpu_node;
// ----------------------------------------------------------------
template <typename Front,
typename Rest>
constexpr bool FrontHasZeroPower = (Front::power_type::num == 0); //std::ratio_equal_v< typename Front::power_type, std::ratio<0,1> >;
template <typename Front,
typename Rest,
bool FHZP = FrontHasZeroPower<Front, Rest>>
struct bpu_smart_cons;
template <typename Front,
typename Rest>
struct bpu_smart_cons<Front, Rest, /*FrontHasZeroPower*/ true> {
using type = Rest;
};
template <typename Front,
typename Rest>
struct bpu_smart_cons<Front, Rest, /*FrontHasZeroPower*/ false> {
using type = bpu_node<Front, Rest>;
};
template <typename Front, typename Rest>
using bpu_smart_cons_t = bpu_smart_cons<Front, Rest>::type;
// ----------------------------------------------------------------
/** @class bwp
@brief represent (compile-time) result of search in a bpu_list<> type
short for (basis-with-native-power-unit)
**/
template <int index_arg, dim basis_arg>
struct bwp {
static constexpr int c_index = index_arg;
static constexpr dim c_basis = basis_arg;
};
template <typename T>
using bwp_incr_pos_type = bwp<T::c_index + 1, T::c_basis>;
// ----- lo_basis_with_pos_type -----
template < typename BasisWithPos1, typename BasisWithPos2>
using lo_basis_with_pos_type = std::conditional_t<(BasisWithPos1::c_basis < BasisWithPos2::c_basis),
BasisWithPos1, BasisWithPos2>;
// ----- native_lo_bwp_of -----
/* helper for canonically-ordering native dimension power-units */
template <typename Dim>
struct native_lo_bwp_of {
using _bwp_front = bwp<0, Dim::front_type::c_native_dim>;
using _pu_rest = native_lo_bwp_of<typename Dim::rest_type>;
using _bwp_rest = typename _pu_rest::bwp_type;
using bwp_type = lo_basis_with_pos_type<_bwp_front,
bwp_incr_pos_type<_bwp_rest>>;
};
template <typename P0>
struct native_lo_bwp_of<bpu_node<P0>> {
using bwp_type = bwp<0, P0::c_native_dim>;
};
// ----- without_elt -----
template <typename Dim, int Index>
struct without_elt {
using _without_rest_type = typename without_elt<typename Dim::rest_type, Index - 1>::dim_type;
using dim_type = bpu_node< typename Dim::front_type, _without_rest_type >;
};
template <typename Dim>
struct without_elt<Dim, 0> {
using dim_type = typename Dim::rest_type;
};
// ----- bpu_list -----
/** Represents the cartesian product of a list of 'native basis power units';
* represents something with dimensions
*
* Expect:
* - P isa native_bpu type
* - D satisfies bpu_list_concept
**/
template <typename P, typename D>
struct bpu_node {
static_assert(native_bpu_concept<P>);
static_assert(bpu_list_concept<D>);
/** For example:
* using b1 = basis_power_unit<dim::currency, std::ratio<1, 1>>;
* using b2 = basis_power_unit<dim::time, std::ratio<-1, 2>>;
* using foo = dimension_impl<b1,dimension_impl<b2>>;
*
* then
* foo::lookup_bpu<0> -> b1
* foo::lookup_bpu<1> -> b2
* foo::lookup_bpu<2> -> not defined
**/
using front_type = P;
using rest_type = D;
static constexpr std::uint32_t n_dimension = rest_type::n_dimension + 1;
};
/** @class dimension
@brief represent a composite dimension
**/
template <typename P0>
struct bpu_node<P0, void> {
static_assert(native_bpu_concept<P0>);
static_assert(bpu_list_concept<void>);
using front_type = P0;
using rest_type = void;
/** For example:
* using b1 = basis_power_unit<dim::time, std::ratio<-1, 2>>;
* using foo = dimension_impl<b1>;
* then
* foo::lookup_bpu<0> --> b1
* foo::lookup_bpu<1> --> not defined
**/
/** number of dimensions represented by this struct **/
static constexpr std::uint32_t n_dimension = 1;
};
// ----- bpu_cartesian_product -----
/** Require:
* - B isa native_bpu type
* - DI_Front is a native_bpu type
* - DI_Rest is a dimension_impl type
*
* Promise:
* - type isa dimension_impl type
**/
template < typename B,
typename DI_Front,
typename DI_Rest,
bool MatchesFront = (B::c_native_dim == DI_Front::c_native_dim) >
struct bpu_cartesian_product_helper;
/** require:
* - B isa native_bpu type
* - DI isa (bpu_list | void) type
**/
template < typename B, typename DI >
struct bpu_cartesian_product {
static_assert(native_bpu_concept<B>);
static_assert(bpu_list_concept<DI>);
using _tmp = bpu_cartesian_product_helper<B,
typename DI::front_type,
typename DI::rest_type>;
using outer_scalefactor_type = typename _tmp::outer_scalefactor_type;
static constexpr double c_outer_scalefactor_inexact = _tmp::c_outer_scalefactor_inexact;
using bpu_list_type = typename _tmp::bpu_list_type;
static_assert(ratio_concept<outer_scalefactor_type>);
static_assert(bpu_list_concept<bpu_list_type>);
};
/** Reminder: void represents the 'no dimension' of a dimensionless quantity **/
template < typename B >
struct bpu_cartesian_product<B, void> {
using outer_scalefactor_type = std::ratio<1>;
static constexpr double c_outer_scalefactor_inexact = 1.0;
using bpu_list_type = bpu_node<B, void>;
static_assert(ratio_concept<outer_scalefactor_type>);
static_assert(bpu_list_concept<bpu_list_type>);
};
/* specialize for matching front */
template <typename B, typename DI_Front, typename DI_Rest>
struct bpu_cartesian_product_helper<B, DI_Front, DI_Rest, /*MatchesFront*/ true> {
static_assert(native_bpu_concept<B>);
static_assert(native_bpu_concept<DI_Front>);
static_assert(bpu_list_concept<DI_Rest>);
/* _mult_type may have zero exponent (power_type);
* in that case bpu_smart_cons will collapse to DI_Rest
*/
using _front_mult_type = bpu_product<B, DI_Front>;
using _front_type = typename _front_mult_type::native_bpu_type;
using _rest_type = DI_Rest;
using outer_scalefactor_type = typename _front_mult_type::outer_scalefactor_type;
static constexpr double c_outer_scalefactor_inexact = _front_mult_type::c_outer_scalefactor_inexact;
using bpu_list_type = bpu_smart_cons_t<_front_type, DI_Rest>;
static_assert(ratio_concept<outer_scalefactor_type>);
static_assert(bpu_list_concept<bpu_list_type>);
};
/* specialize for not-matching-front */
template <typename B, typename DI_Front, typename DI_Rest>
struct bpu_cartesian_product_helper<B, DI_Front, DI_Rest, /*MatchesFront*/ false> {
static_assert(native_bpu_concept<B>);
static_assert(native_bpu_concept<DI_Front>);
static_assert(bpu_list_concept<DI_Rest>);
using _rest_mult_type = bpu_cartesian_product< B, DI_Rest >;
using _front_type = DI_Front;
using _rest_type = typename _rest_mult_type::bpu_list_type;
using outer_scalefactor_type = typename _rest_mult_type::outer_scalefactor_type;
static constexpr double c_outer_scalefactor_inexact = _rest_mult_type::c_outer_scalefactor_inexact;
using bpu_list_type = bpu_node<DI_Front, _rest_type>;
static_assert(ratio_concept<outer_scalefactor_type>);
static_assert(bpu_list_concept<bpu_list_type>);
};
// ----- di_cartesian_product -----
template < typename D1, typename D2 > struct di_cartesian_product;
// ----- bpu_cartesian_product1 -----
template < typename B1, typename R1, typename D2 >
struct di_cartesian_product1 {
static_assert(native_bpu_concept<B1>);
static_assert(bpu_list_concept<R1>);
static_assert(bpu_list_concept<D2>);
using _tmp1_mult_type = bpu_cartesian_product<B1, D2>;
using _tmp1_scalefactor_type = _tmp1_mult_type::outer_scalefactor_type;
using _tmp1_bpu_list_type = _tmp1_mult_type::bpu_list_type;
using _tmp2_mult_type = di_cartesian_product<R1, _tmp1_bpu_list_type>;
using _tmp2_scalefactor_type = _tmp2_mult_type::outer_scalefactor_type;
using _tmp2_bpu_list_type = _tmp2_mult_type::bpu_list_type;
using outer_scalefactor_type = std::ratio_multiply<
_tmp1_scalefactor_type,
_tmp2_scalefactor_type>;
static constexpr double c_outer_scalefactor_inexact = (_tmp1_mult_type::c_outer_scalefactor_inexact
* _tmp2_mult_type::c_outer_scalefactor_inexact);
using bpu_list_type = _tmp2_bpu_list_type;
static_assert(ratio_concept<outer_scalefactor_type>);
static_assert(bpu_list_concept<bpu_list_type>);
};
template < typename B1, typename D2 >
struct di_cartesian_product1<B1, void, D2> {
static_assert(native_bpu_concept<B1>);
static_assert(bpu_list_concept<D2>);
using _tmp_mult_type = bpu_cartesian_product<B1, D2>;
using outer_scalefactor_type = _tmp_mult_type::outer_scalefactor_type;
static constexpr double c_outer_scalefactor_inexact = _tmp_mult_type::c_outer_scalefactor_inexact;
using bpu_list_type = _tmp_mult_type::bpu_list_type;
};
// ----- di_invert -----
/* note: rescaling never required here,
* since not combining basis dimensions.
*/
template <typename BpuList>
struct di_invert;
template <>
struct di_invert<void> {
using type = void;
};
template <typename BpuList>
struct di_invert {
using type = bpu_node<
typename bpu_invert<typename BpuList::front_type>::type,
typename di_invert<typename BpuList::rest_type>::type
>;
};
// ----- di_cartesian_product -----
template < typename D1, typename D2 >
struct di_cartesian_product {
static_assert(bpu_list_concept<D1>);
static_assert(bpu_list_concept<D2>);
using _mult_type = di_cartesian_product1<
typename D1::front_type,
typename D1::rest_type,
D2>;
using outer_scalefactor_type = _mult_type::outer_scalefactor_type;
static constexpr double c_outer_scalefactor_inexact = _mult_type::c_outer_scalefactor_inexact;
using bpu_list_type = _mult_type::bpu_list_type;
static_assert(ratio_concept<outer_scalefactor_type>);
static_assert(bpu_list_concept<bpu_list_type>);
};
template < typename D2 >
struct di_cartesian_product< void, D2 > {
static_assert(bpu_list_concept<D2>);
using outer_scalefactor_type = std::ratio<1>;
static constexpr double c_outer_scalefactor_inexact = 1.0;
using bpu_list_type = D2;
};
template <typename D1 >
struct di_cartesian_product< D1, void > {
static_assert(bpu_list_concept<D1>);
using outer_scalefactor_type = std::ratio<1>;
static constexpr double c_outer_scalefactor_inexact = 1.0;
using bpu_list_type = D1;
};
// ----- di_assemble_abbrev -----
/* reminder: can't partially specialize a template function -> need struct wrapper */
template < typename DI >
struct di_assemble_abbrev;
/** Expect:
* - P isa native_bpu type
* - P::power_type = std::ratio<..>
* - P::c_native_dim :: dim
* - P::c_num :: int
* - P::c_den :: int
* - D isa dimension_impl type
* - D::front_type = native_bpu<..>
* - D::rest_type = dimension_impl<..>
* - D::n_dimension :: int
**/
template <typename P, typename D>
struct di_assemble_abbrev_helper {
static_assert(native_bpu_concept<P>);
static_assert(bpu_list_concept<D>);
static constexpr auto _prefix = bpu_assemble_abbrev<P>();
static constexpr auto _suffix = di_assemble_abbrev<D>::value;
static constexpr auto value = stringliteral_concat(_prefix.value_,
".",
_suffix.value_);
};
template <typename P>
struct di_assemble_abbrev_helper<P, void> {
static constexpr auto value = bpu_assemble_abbrev<P>();
};
template < typename DI >
struct di_assemble_abbrev {
static_assert(bpu_list_concept<DI>);
using _helper_type = di_assemble_abbrev_helper <typename DI::front_type, typename DI::rest_type>;
static constexpr auto value = _helper_type::value;
};
template <>
struct di_assemble_abbrev<void> {
static constexpr auto value = stringliteral("");
};
// ----- canonical_impl -----
template <typename D>
struct canonical_impl {
/*
* bwp_front::c_index
* bwp_front::c_native_dim
*/
using _bwp_front = native_lo_bwp_of<D>::bwp_type;
using _front_type = typename lookup_bpu<D, _bwp_front::c_index>::power_unit_type;
using _rest0_type = typename without_elt<D, _bwp_front::c_index>::dim_type;
using _rest_type = canonical_impl<_rest0_type>::dim_type;
using dim_type = bpu_node<_front_type, _rest_type>;
};
/** compute canonical renumbering of a dimension
**/
template <>
struct canonical_impl<void> {
using dim_type = void;
};
template<typename D>
using canonical_t = canonical_impl<D>::dim_type;
} /*namespace obs*/
} /*namespace xo*/
/* end dimension_impl.hpp */

View file

@ -0,0 +1,259 @@
/* @file native_bpu.hpp */
#pragma once
#include "native_bpu_concept.hpp"
#include "basis_unit.hpp"
#include <ratio>
namespace xo {
namespace obs {
// ----- native_bpu -----
/** @class native_bpu
@brief represent product of a compile-time scale-factor with a rational power of a native unit
Example:
native_bpu<universal::time, ratio<1>, ratio<-1,2>> represents unit of 1/sqrt(t)
**/
template<
dim DimId,
typename InnerScale,
typename Power = std::ratio<1> >
struct bpu : basis_unit<DimId, native_unit_for_v<DimId>, InnerScale> {
static_assert(ratio_concept<Power>);
/* native_unit provides
* - scalefactor_type --> std::ratio
* - c_native_dim :: dim
* - c_native_unit :: native_unit
*/
using power_type = Power;
static const int c_num = Power::num;
static const int c_den = Power::den;
};
/** @class bpu_assemble_abbrev
*
* @brief generate abbreviation literal.
*
* Abbreviation literal ignores outer scale factor;
* (outer scale factor should be multiplied by run-time scale when printing a quantity)
*
* Separate template from native_bpu so that abbrev can independently be specialized
**/
template < dim dim_id,
typename InnerScale,
typename Power = std::ratio<1> >
constexpr auto bpu_assemble_abbrev_helper() {
static_assert(ratio_concept<Power>);
return stringliteral_concat(units::scaled_native_unit_abbrev_v<dim_id, InnerScale>.value_,
stringliteral_from_exponent<Power>().value_);
};
/** Expect:
* - BPU is a native_bpu type:
* - BPU::scalefactor_type = std::ratio<..>
* - BPU::c_native_dim :: dim
* - BPU::power_type = std::ratio<..>
* - BPU::c_num :: int
* - BPU::c_den :: int
**/
template < typename BPU >
constexpr auto bpu_assemble_abbrev() {
static_assert(native_bpu_concept<BPU>);
return bpu_assemble_abbrev_helper< BPU::c_native_dim,
typename BPU::scalefactor_type,
typename BPU::power_type >();
};
// ----- bpu_rescale -----
/**
* Part I
* ------
* We have B satisfying native_bpu_concept:
* B represents a basis-power-unit
* p
* (b.u)
*
* with
* b = B::scalefactor_type, e.g. 60 for a 1-minute unit
* u = B::dim, e.g. 1second for time
* p = B::power_type
*
* We want to construct something with similar form:
*
* p
* a'.(b'.u)
*
* representing the same dimensioned unit,
* i.e.
* p p'
* (b.u) = a'.(b'.u)
*
* with NewInnerScale -> b'
*
* p p p p
* (b.u) = (b/b') . (b'.u) = a'.(b'.u)
*
* p
* with a' = (b/b')
*
* For example: if we have B(b=60,u=time,p=2), NewInnerScale=1:
* then we want a'=3600, B'(b=1,u=time,p=2)
*
* Result represented with
* bpu_rescale<B,NewInnerScale>::outer_scalefactor_type -> 'a
* bpu_rescale<B,NewInnerScale>::native_bpu_type -> B'
*
* Part II
* -------
* Want ability to rescale when p is a non-integer rational.
* In that case a' = (b/b')^p won't in general be exactly-representable,
* so we are forced to accept some loss of precision.
*
* Want to write:
* p as p' + q' with:
* p' = integer part of p
* q' = fractional part of p
* Then we can write
* a' as c'.d' with:
* c' = (b/b')^p' [exactly represented]
* d' = (b/b')^q' [floating point]
**/
template <typename B,
typename NewInnerScale>
struct bpu_rescale {
static_assert(native_bpu_concept<B>);
static_assert(ratio_concept<NewInnerScale>);
/* TODO:
* - native_unit::c_scale -> std::ratio, call it c_inner_scalefactor
* - ++ native_bpu::c_outer_scalefactor, will be a std::ratio
*/
/* b/b' */
using _t1_type = std::ratio_divide
< typename B::scalefactor_type, NewInnerScale >;
/* p' */
using p1_type = ratio_floor_t<typename B::power_type>;
/* q' */
using q1_type = ratio_frac_t<typename B::power_type>;
/** require p must be integral **/
static_assert(p1_type::den == 1);
/* note: constexpr from c++26, but already present in earlier gcc */
static constexpr double c_outer_scalefactor_inexact = ::pow(from_ratio<double, _t1_type>(),
from_ratio<double, q1_type>());
/** p
* a' = (b/b')
**/
using outer_scalefactor_type = ratio_power_t< _t1_type, p1_type::num >;
/**
* p
* (b'.u)
**/
using native_bpu_type = bpu < B::c_native_dim,
NewInnerScale,
typename B::power_type >;
};
// ----- bpu_invert -----
/** invert a native bpu: create type for space 1/B **/
template <typename B>
struct bpu_invert {
using type = bpu <
B::c_native_dim,
typename B::scalefactor_type,
std::ratio_multiply<std::ratio<-1>, typename B::power_type>
>;
};
// ----- bpu_product -----
/** Suppose we have two native_bpu's {B1, B2} that scale the same native basis unit u.
* B1,B2 may be using different units {b1,b2} for u
*
* p1
* B1 (b1, u, p1) = (b1.u)
*
* p2
* B2 (b2, u, p2) = (b2.u)
*
* we want a representation in similar form:
*
* p'
* a' . B' (b', u, p') = a'.(b'.u)
*
* for the product (B1 x B2), i.e.
*
* p' p1 p2
* a'.(b'.u) = (b1.u) (b2.u)
*
* We can use bpu_rescale to rewrite B2 in the form
*
* p2
* B2' = (c'.d').(b1.u)
*
* where c' is exact, d' is inexact.
* (note however d' will be exactly 1.0 whenever p2 is integral)
*
* so we have
*
* p1 p2
* (B1 x B2) = (b1.u) (c'.d').(b1.u)
*
* p1+p2
* = (c'.d').(b1.u)
*
**/
template < typename B1, typename B2 >
struct bpu_product {
static_assert(native_bpu_concept<B1>);
static_assert(native_bpu_concept<B2>);
static_assert(B1::c_native_dim == B2::c_native_dim);
/* c'.d'.B2' = c'.d'.(b1.u)^p2
*
* _b2p_rescaled_type::native_bpu_type -> B2' (b1, u, p2) [same basis scalefactor as B1]
* _b2p_rescaled_type::outer_scalefactor_type -> c' [exact factor]
* _b2p_rescaled_type::c_outer_scalefactor_type -> d' [inexact factor, from fractional powers]
*/
using _b2p_rescaled_type = bpu_rescale<B2,
typename B1::scalefactor_type>;
/* (b1.u)^p2 */
using _b2p_sf_bpu_type = _b2p_rescaled_type::native_bpu_type;
/* p1+p2 */
using _p_type = std::ratio_add<
typename B1::power_type,
typename B2::power_type
>;
/* c' */
using outer_scalefactor_type = _b2p_rescaled_type::outer_scalefactor_type;
/* d' */
static constexpr double c_outer_scalefactor_inexact = _b2p_rescaled_type::c_outer_scalefactor_inexact;
/* (b1.u)^(p1+p2) */
using native_bpu_type = bpu <
B1::c_native_dim,
typename B1::scalefactor_type,
_p_type /*Power*/ >;
};
} /*namespace obs*/
} /*namespace xo*/
/* end native_bpu.hpp */

View file

@ -0,0 +1,42 @@
/* @file native_bpu_concept.hpp */
#pragma once
#include "ratio_concept.hpp"
#include "dim_util.hpp"
#include <concepts>
namespace xo {
namespace obs {
/**
* e.g. see native_bpu<native_dim_id, std::ratio<..>>
*
* bpu short for 'basis power unit'.
*
* NOTE: in typical c++ use, there won't be a reason to declare
* a variable of type NativeBpu. Instead will appear
* as a template argument.
**/
template <typename NativeBpu>
concept native_bpu_concept = requires(NativeBpu bpu)
{
typename NativeBpu::scalefactor_type;
typename NativeBpu::power_type;
// NativeBpu::c_native_dim :: native_dim_id
// NativeBpu::c_scale :: std::intmax_t
// NativeBpu::num :: int
// NativeBpu::den :: int
}
&& ((std::is_same_v<decltype(NativeBpu::c_native_dim), const dim>)
&& ratio_concept<typename NativeBpu::scalefactor_type>
&& ratio_concept<typename NativeBpu::power_type>
&& (std::is_signed_v<decltype(NativeBpu::c_num)>)
&& (std::is_signed_v<decltype(NativeBpu::c_den)>))
// && std::copyable<Foo>
;
} /*namespace obs*/
} /*namespace xo*/
/* end native_bpu_concept.hpp */

View file

@ -0,0 +1,501 @@
/* @file quantity.hpp */
#pragma once
#include "quantity_concept.hpp"
#include "unit.hpp"
//#include "xo/reflect/Reflect.hpp"
//#include "xo/indentlog/scope.hpp"
namespace xo {
namespace obs {
/** @class promoter
*
* Aux class assister for quantity::promote()
**/
template <typename Unit, typename Repr, bool Dimensionless = dimensionless_v<Unit> >
struct promoter;
// ----- quantity -----
/** @class quantity
*
* @brief represets a scalar quantity; enforces dimensional consistency at compile time
*
* Repr representation.
* Unit unit
* Assert use to specify required unit dimension
*
* Require:
* - Repr copyable, assignable
* - Repr = 0
* - Repr = 1
* - Repr + Repr -> Repr
* - Repr - Repr -> Repr
* - Repr * Repr -> Repr
* - Repr / Repr -> Repr
**/
template <typename Unit, typename Repr> //, typename RequiredDimensionType = Unit>
class quantity {
public:
using unit_type = Unit;
using repr_type = Repr;
/* non-unity compile-time scale factors can arise during unit conversion;
* for example see method quantity::in_units_of()
*/
static_assert(std::same_as< typename Unit::scalefactor_type, std::ratio<1> >);
static_assert(std::same_as< typename Unit::canon_type, typename Unit::canon_type >);
public:
constexpr quantity() = default;
constexpr quantity(quantity const & x) = default;
constexpr quantity(quantity && x) = default;
template <dim BasisDim>
using find_bpu_t = unit_find_bpu_t<unit_type, BasisDim>;
/**
* For example:
* auto q = qty::milliseconds(5) * qty::seconds(1);
* then
* q.basis_power<dim::time> -> 2
* q.basis_power<dim::mass> -> 0
**/
template <dim BasisDim, typename PowerRepr = int>
static constexpr PowerRepr c_basis_power = from_ratio<PowerRepr, typename find_bpu_t<BasisDim>::power_type>();
/** @brief get scale value (relative to unit) (@ref scale_) **/
constexpr Repr scale() const { return scale_; }
/** @brief abbreviation for this quantity's units **/
static constexpr char const * unit_cstr() { return unit_abbrev_v<unit_type>.c_str(); }
/** @brief return unit quantity -- amount with this Unit that has representation = 1 **/
static constexpr quantity unit_quantity() { return quantity(1); }
/** @brief promote representation to quantity. Same as multiplying by Unit **/
static constexpr auto promote(Repr x) {
//std::cerr << "quantity<U,R>::promote: x=" << x << ", R=" << reflect::Reflect::require<Repr>()->canonical_name() << std::endl;
return promoter<Unit, Repr>::promote(x);
}
template <typename Quantity2>
auto multiply(Quantity2 y) const {
//constexpr bool c_debug_flag = false;
//using Reflect = xo::reflect::Reflect;
//scope log(XO_DEBUG(c_debug_flag));
static_assert(quantity_concept<Quantity2>);
/* unit: may have non-unit scalefactor_type */
using unit_product_type = unit_cartesian_product<Unit, typename Quantity2::unit_type>;
using exact_unit_type = unit_product_type::exact_unit_type;
using norm_unit_type = normalize_unit_t<exact_unit_type>;
using exact_scalefactor_type = exact_unit_type::scalefactor_type;
constexpr double c_scalefactor_inexact = unit_product_type::c_scalefactor_inexact;
using repr_type = std::common_type_t<repr_type, typename Quantity2::repr_type>;
repr_type r_scale = ((scale() * y.scale() * c_scalefactor_inexact * exact_scalefactor_type::num)
/ exact_scalefactor_type::den);
# ifdef NOT_USING_DEBUG
log && log(xtag("unit_product_type", Reflect::require<unit_product_type>()->canonical_name()));
log && log(xtag("exact_unit_type", Reflect::require<exact_unit_type>()->canonical_name()));
log && log(xtag("norm_unit_type", Reflect::require<norm_unit_type>()->canonical_name()));
log && log(xtag("exact_scalefactor_type", Reflect::require<exact_scalefactor_type>()->canonical_name()));
log && log(xtag("c_scalefactor_inexact", c_scalefactor_inexact));
log && log(xtag("repr_type", Reflect::require<repr_type>()->canonical_name()));
log && log(xtag("repr_type", Reflect::require<repr_type>()->canonical_name()));
# endif
return quantity<norm_unit_type, repr_type>::promote(r_scale);
}
template <typename Quantity2>
auto divide(Quantity2 y) const {
using unit_divide_type = unit_divide<Unit, typename Quantity2::unit_type>;
using exact_unit_type = unit_divide_type::exact_unit_type;
using norm_unit_type = normalize_unit_t<exact_unit_type>;
using exact_scalefactor_type = exact_unit_type::scalefactor_type;
constexpr double c_scalefactor_inexact = unit_divide_type::c_scalefactor_inexact;
using repr_type = std::common_type_t<repr_type, typename Quantity2::repr_type>;
repr_type r_scale = ((scale() * c_scalefactor_inexact * unit_type::scalefactor_type::num)
/ (y.scale() * unit_type::scalefactor_type::den));
return quantity<norm_unit_type, repr_type>::promote(r_scale);
}
// quantity operator*=()
// quantity operator/=()
/**
* scale by dimensionless number
**/
template <typename Repr2>
auto scale_by(Repr2 x) const {
static_assert(!quantity_concept<Repr2>);
using r_repr_type = std::common_type_t<repr_type, Repr2>;
r_repr_type r_scale = this->scale_ * x;
//std::cerr << "quantity::scale_by: scale=" << scale << ", repr_type=" << reflect::Reflect::require<repr_type>()->canonical_name() << std::endl;
return quantity<unit_type, r_repr_type>::promote(r_scale);
}
/**
* divide by dimensionless number
**/
template <typename Repr2>
auto divide_by(Repr2 x) const {
using r_repr_type = std::common_type_t<repr_type, Repr2>;
r_repr_type r_scale = this->scale_ / x;
return quantity<unit_type, r_repr_type>::promote(r_scale);
}
/**
* divide dimensionless number by this quantity
**/
template <typename Repr2>
auto divide_into(Repr2 x) const {
using r_unit_type = unit_invert_t<Unit>;
using r_repr_type = std::common_type_t<repr_type, Repr2>;
r_repr_type r_scale = ((x * r_unit_type::scalefactor_type::num)
/ (this->scale_ * r_unit_type::scalefactor_type::den));
return quantity<r_unit_type, r_repr_type>::promote(r_scale);
}
template <typename Unit2, typename Repr2>
Repr2 in_units_of() const {
// static_assert(dimension_of<Unit> == dimension_of<Unit2>); // discard all the scaling values
static_assert(same_dimension_v<Unit, Unit2>);
using _convert_to_u2_type = unit_cartesian_product<Unit, unit_invert_t<Unit2>>;
using exact_scalefactor_type = _convert_to_u2_type::exact_unit_type::scalefactor_type;
constexpr double c_scalefactor_inexact = _convert_to_u2_type::c_scalefactor_inexact;
// _convert_u2_type
// - scalefactor_type
// - dim_type
// - canon_type
/* if _convert_u2_type isn't dimensionless, then {Unit2, Unit} have different dimensions */
return ((this->scale_ * c_scalefactor_inexact * exact_scalefactor_type::num) / exact_scalefactor_type::den);
}
template <typename Quantity2>
quantity operator+=(Quantity2 y) {
static_assert(std::same_as<
typename unit_type::canon_type,
typename Quantity2::unit_type::canon_type >);
/* relying on assignment that correctly converts-to-lhs-units */
quantity y2 = y;
this->scale_ += y2.scale();
return *this;
}
template <typename Quantity2>
quantity operator-=(Quantity2 y) {
static_assert(std::same_as<
typename unit_type::canon_type,
typename Quantity2::unit_type::canon_type >);
quantity y2 = y;
/* relying on assignment that correctly converts-to-lhs-units */
this->scale_ -= y2.scale();
return *this;
}
/* convert to quantity with same dimension, different {unit_type, repr_type} */
template <typename Quantity2>
operator Quantity2 () const {
/* avoid truncating precision when converting:
* use best available representation
*/
using tmp_repr_type = std::common_type_t<repr_type, typename Quantity2::repr_type>;
return Quantity2::promote(this->in_units_of<typename Quantity2::unit_type, tmp_repr_type>());
}
void display(std::ostream & os) const {
os << this->scale() << unit_cstr();
}
quantity & operator=(quantity const & x) = default;
quantity & operator=(quantity && x) = default;
private:
explicit constexpr quantity(Repr x) : scale_{x} {}
friend class promoter<Unit, Repr, true>;
friend class promoter<Unit, Repr, false>;
private:
/** @brief quantity represents this multiple of a unit (that has compile-time outer-scalefactor of 1) **/
Repr scale_ = 0;
}; /*quantity*/
// ----- promoter -----
/* collapse dimensionless quantity to its repr_type> */
template <typename Unit, typename Repr>
struct promoter<Unit, Repr, /*Dimensionless*/ true> {
static constexpr Repr promote(Repr x) { return x; };
};
template <typename Unit, typename Repr>
struct promoter<Unit, Repr, /*Dimensionless*/ false> {
static constexpr quantity<Unit, Repr> promote(Repr x) { return quantity<Unit, Repr>(x); }
};
// ----- operator+ -----
template <typename Quantity1, typename Quantity2>
inline Quantity1 operator+ (Quantity1 x, Quantity2 y) {
static_assert(same_dimension_v<typename Quantity1::unit_type, typename Quantity2::unit_type>);
/* convert y to match units used by x;
* would fail at compile time if this isn't well-defined
*/
Quantity1 y2 = y;
return Quantity1::promote(x.scale() + y2.scale());
}
template <typename Quantity1, typename Quantity2>
inline Quantity1 operator- (Quantity1 x, Quantity2 y) {
static_assert(std::same_as
< typename Quantity1::unit_type::dimension_type::canon_type,
typename Quantity2::unit_type::dimension_type::canon_type >);
/* convert y to match units used by x */
Quantity1 y2 = y;
return Quantity1::promote(x.scale() - y2.scale());
}
template <typename Quantity>
inline Quantity operator- (Quantity x) {
return Quantity::promote(- x.scale());
}
template <typename Quantity1, typename Quantity2>
inline auto operator* (Quantity1 x, Quantity2 y) {
static_assert(quantity_concept<Quantity1>);
static_assert(quantity_concept<Quantity2>);
return x.multiply(y);
}
/** e.g. DECLARE_LH_MULT(int32_t) **/
# define DECLARE_LH_MULT(lhtype) \
template <typename Quantity> \
inline auto \
operator* (lhtype x, Quantity y) { \
static_assert(quantity_concept<Quantity>); \
return y.scale_by(x); \
}
DECLARE_LH_MULT(int8_t);
DECLARE_LH_MULT(uint8_t);
DECLARE_LH_MULT(int16_t);
DECLARE_LH_MULT(uint16_t);
DECLARE_LH_MULT(int32_t);
DECLARE_LH_MULT(uint32_t);
DECLARE_LH_MULT(int64_t);
DECLARE_LH_MULT(uint64_t);
DECLARE_LH_MULT(float);
DECLARE_LH_MULT(double);
# undef DECLARE_LH_MULT
/** e.g. DECLARE_RH_MULT(int32_t) **/
# define DECLARE_RH_MULT(rhtype) \
template <typename Quantity> \
inline auto \
operator* (Quantity x, rhtype y) { \
static_assert(quantity_concept<Quantity>); \
return x.scale_by(y); \
}
DECLARE_RH_MULT(int8_t);
DECLARE_RH_MULT(uint8_t);
DECLARE_RH_MULT(int16_t);
DECLARE_RH_MULT(uint16_t);
DECLARE_RH_MULT(int32_t);
DECLARE_RH_MULT(uint32_t);
DECLARE_RH_MULT(int64_t);
DECLARE_RH_MULT(uint64_t);
DECLARE_RH_MULT(float);
DECLARE_RH_MULT(double);
# undef DECLARE_LH_MULT
template <typename Quantity1, typename Quantity2>
inline auto operator/ (Quantity1 x, Quantity2 y) {
static_assert(quantity_concept<Quantity1>);
static_assert(quantity_concept<Quantity2>);
return x.divide(y);
}
# define DECLARE_LH_DIV(lhtype) \
template <typename Quantity> \
inline auto \
operator/ (lhtype x, Quantity y) { \
static_assert(quantity_concept<Quantity>); \
return y.divide_into(x); \
}
DECLARE_LH_DIV(int8_t);
DECLARE_LH_DIV(uint8_t);
DECLARE_LH_DIV(int16_t);
DECLARE_LH_DIV(uint16_t);
DECLARE_LH_DIV(int32_t);
DECLARE_LH_DIV(uint32_t);
DECLARE_LH_DIV(int64_t);
DECLARE_LH_DIV(uint64_t);
DECLARE_LH_DIV(float);
DECLARE_LH_DIV(double);
# undef DECLARE_LH_DIV
# define DECLARE_RH_DIV(rhtype) \
template <typename Quantity> \
inline auto \
operator/ (Quantity x, rhtype y) { \
static_assert(quantity_concept<Quantity>); \
return x.divide_by(y); \
}
DECLARE_RH_DIV(int8_t)
DECLARE_RH_DIV(uint8_t)
DECLARE_RH_DIV(int16_t)
DECLARE_RH_DIV(uint16_t)
DECLARE_RH_DIV(int32_t)
DECLARE_RH_DIV(uint32_t)
DECLARE_RH_DIV(int64_t)
DECLARE_RH_DIV(uint64_t)
DECLARE_RH_DIV(float)
DECLARE_RH_DIV(double)
# undef DECLARE_RH_DIV
template <typename Unit, typename Repr>
inline std::ostream &
operator<< (std::ostream & os, quantity<Unit, Repr> const & x) {
x.display(os);
return os;
}
namespace qty {
// ----- mass -----
template <typename Repr = double>
inline auto milligrams(Repr x) -> quantity<units::milligram, Repr> {
return quantity<units::milligram, Repr>::promote(x);
};
template <typename Repr = double>
inline auto grams(Repr x) -> quantity<units::gram, Repr> {
return quantity<units::gram, Repr>::promote(x);
};
template <typename Repr = double>
inline auto kilograms(Repr x) -> quantity<units::gram, Repr> {
return quantity<units::kilogram, Repr>::promote(x);
};
// ----- distance -----
template <typename Repr = double>
inline auto millimeters(Repr x) -> quantity<units::millimeter, Repr> {
return quantity<units::millimeter, Repr>::promote(x);
}
template <typename Repr = double>
inline auto meters(Repr x) -> quantity<units::meter, Repr> {
return quantity<units::meter, Repr>::promote(x);
}
template <typename Repr = double>
inline auto kilometers(Repr x) -> quantity<units::kilometer, Repr> {
return quantity<units::kilometer, Repr>::promote(x);
}
// ----- time -----
template <typename Repr = double>
inline auto nanoseconds(Repr x) -> quantity<units::nanosecond, Repr> {
return quantity<units::nanosecond, Repr>::promote(x);
}
template <typename Repr = double>
inline auto microseconds(Repr x) -> quantity<units::microsecond, Repr> {
return quantity<units::microsecond, Repr>::promote(x);
}
template <typename Repr = double>
inline auto milliseconds(Repr x) -> quantity<units::millisecond, Repr> {
return quantity<units::millisecond, Repr>::promote(x);
}
template <typename Repr = double>
inline auto seconds(Repr x) -> quantity<units::second, Repr> {
return quantity<units::second, Repr>::promote(x);
}
template <typename Repr = double>
inline auto minutes(Repr x) -> quantity<units::minute, Repr> {
return quantity<units::minute, Repr>::promote(x);
}
template <typename Repr = double>
inline auto hours(Repr x) -> quantity<units::hour, Repr> {
return quantity<units::hour, Repr>::promote(x);
}
template <typename Repr = double>
inline auto days(Repr x) -> quantity<units::day, Repr> {
return quantity<units::day, Repr>::promote(x);
}
// ----- time/volatility -----
/** quantity in units of 1/sqrt(1dy) **/
template <typename Repr = double>
inline auto volatility1d(Repr x) -> quantity<units::volatility_1d, Repr> {
return quantity<units::volatility_1d, Repr>::promote(x);
}
/** quantity in units of 1/sqrt(30days)
**/
template <typename Repr = double>
inline auto volatility30d(Repr x) -> quantity<units::volatility_30d, Repr> {
return quantity<units::volatility_30d, Repr>::promote(x);
}
/** quantity in units of 1/sqrt(250days)
**/
template <typename Repr = double>
inline auto volatility250d(Repr x) -> quantity<units::volatility_250d, Repr> {
return quantity<units::volatility_250d, Repr>::promote(x);
}
} /*namespace qty*/
} /*namespace obs*/
} /*namespace xo*/
/* end quantity.hpp */

View file

@ -0,0 +1,21 @@
/** @file quantity_concept.hpp **/
#pragma once
#include <concepts>
namespace xo {
namespace obs {
template <typename Quantity>
concept quantity_concept = requires(Quantity qty, typename Quantity::repr_type repr)
{
typename Quantity::unit_type;
typename Quantity::repr_type;
{ qty.scale() } -> std::same_as<typename Quantity::repr_type>;
{ Quantity::unit_cstr() } -> std::same_as<char const *>;
{ Quantity::unit_quantity() } -> std::same_as<Quantity>;
{ Quantity::promote(repr) } -> std::same_as<Quantity>;
};
} /*namespace obs*/
} /*namespace xo*/

View file

@ -0,0 +1,13 @@
/* @file ratio_concept.hpp */
#pragma once
#include <concepts>
namespace xo {
namespace obs {
template <typename Ratio>
concept ratio_concept = (std::is_signed_v<decltype(Ratio::num)>
&& std::is_signed_v<decltype(Ratio::den)>);
} /*namespace obs*/
} /*namespace xo*/

View file

@ -0,0 +1,242 @@
/* @file ratio_util.hpp */
#pragma once
#include "ratio_concept.hpp"
#include "stringliteral.hpp"
#include <ratio>
#include <numeric>
namespace xo {
namespace obs {
// ----- ratio_floor -----
template <typename Ratio>
struct ratio_floor {
using type = std::ratio<Ratio::num/Ratio::den>;
};
template <typename Ratio>
using ratio_floor_t = ratio_floor<Ratio>::type;
// ----- ratio_frac -----
template <typename Ratio>
struct ratio_frac {
static_assert(ratio_concept<Ratio>);
using type = std::ratio_subtract<typename Ratio::type,
ratio_floor_t<Ratio>>::type;
};
template <typename Ratio>
using ratio_frac_t = ratio_frac<Ratio>::type;
// ----- ratio_power -----
/** ratio to an integer power
*
* ratio_power<Base,Power>::type
*
* yields a std::ratio representing Base^Power.
**/
template <typename Base, int Power, bool IsNegative = std::signbit(Power)>
struct ratio_power_aux;
template <typename Base, int Power>
struct ratio_power_aux<Base, Power, true> {
/** std::ratio representing Base^(-Power) **/
using _inverse_ratio_type = typename ratio_power_aux<Base, -Power>::type;
/** Base^(-Power)^-1 = Base^Power **/
using type = std::ratio_divide<std::ratio<1>, _inverse_ratio_type>;
static_assert(ratio_concept<type>);
};
template <typename Base>
struct ratio_power_aux<Base, 0, false> {
using type = std::ratio<1>;
};
template <typename Base>
struct ratio_power_aux<Base, 1, false> {
using type = Base;
};
template <typename Base, int Power>
struct ratio_power_aux<Base, Power, false> {
/** Base^(Power/2) **/
using _p2_ratio_type = ratio_power_aux<Base, Power/2>::type;
/** Base^(Power%2) :
* - Base^0 if Power is even
* - Base^1 if Power is odd
**/
using _x_ratio_type = ratio_power_aux<Base, Power%2>::type;
using type = std::ratio_multiply< _x_ratio_type,
std::ratio_multiply<_p2_ratio_type,
_p2_ratio_type> >;
static_assert(ratio_concept<type>);
};
// ----- ratio_power_v -----
/** compute Base^Power at compile time **/
template <typename Base, int Power>
using ratio_power_t = ratio_power_aux<Base, Power>::type;
// ----- from_ratio -----
/** Example:
* from_ratio<double, std::ratio<1,10> --> 0.1
* from_ratio<double, std::ratio<4,5> --> 0.8
**/
template <typename Repr, typename Ratio>
constexpr Repr from_ratio() {
static_assert(ratio_concept<Ratio>);
return Ratio::num / static_cast<Repr>(Ratio::den);
}
// ----- ratio2str_aux -----
/* note: using struct wrapper because partial specialization of function templates
* is not allowed
*/
template <int Num,
int Den,
bool Signbit = std::signbit(Num) ^ std::signbit(Den)>
struct ratio2str_aux;
template <int Num,
int Den>
struct ratio2str_aux<Num, Den, false> {
static constexpr auto value = stringliteral_concat("(",
stringliteral_from_int_v<Num>().value_,
"/",
stringliteral_from_int_v<Den>().value_,
")");
};
template <int Num,
int Den>
struct ratio2str_aux<Num, Den, true> {
/* note: using struct wrapper because partial specialization of function templates
* is not allowed
*/
static constexpr auto value = stringliteral_concat("-(",
stringliteral_from_int_v<-Num>().value_,
"/",
stringliteral_from_int_v<Den>().value_,
")");
};
template <int Num>
struct ratio2str_aux<Num, /*Den*/ 1, true> {
static constexpr auto value = stringliteral_concat("-",
stringliteral_from_int_v<-Num>().value_);
};
template <int Num>
struct ratio2str_aux<Num, /*Den*/ 1, false> {
static constexpr auto value = stringliteral_from_int_v<Num>();
};
template <>
struct ratio2str_aux<1, 1, false> {
static constexpr auto value = stringliteral("");
};
// ----- stringliteral_from_ratio -----
/** Example:
* - stringliteral_from_ratio<std::ratio<2,3> -> "^(2,3)"
**/
template <typename Ratio>
constexpr auto stringliteral_from_ratio() {
static_assert(ratio_concept<Ratio>);
using lowest_terms_type = Ratio::type;
return ratio2str_aux<lowest_terms_type::num, lowest_terms_type::den>().value;
};
template <typename Ratio>
constexpr char const * cstr_from_ratio() {
static_assert(ratio_concept<Ratio>);
using lowest_terms_type = Ratio::type;
return ratio2str_aux<lowest_terms_type::num, lowest_terms_type::den>().value.c_str();
};
// ----- exponent2str_aux -----
/* note: using struct wrapper because partial specialization of function templates
* is not allowed
*/
template <int Num,
int Den,
bool Signbit = std::signbit(Num) ^ std::signbit(Den)>
struct exponent2str_aux;
template <int Num,
int Den>
struct exponent2str_aux<Num, Den, false> {
static constexpr auto value = stringliteral_concat("^(",
stringliteral_from_int_v<Num>().value_,
"/",
stringliteral_from_int_v<Den>().value_,
")");
};
template <int Num,
int Den>
struct exponent2str_aux<Num, Den, true> {
/* note: using struct wrapper because partial specialization of function templates
* is not allowed
*/
static constexpr auto value = stringliteral_concat("^-(",
stringliteral_from_int_v<-Num>().value_,
"/",
stringliteral_from_int_v<Den>().value_,
")");
};
template <int Num>
struct exponent2str_aux<Num, 1, false> {
static constexpr auto value = stringliteral_concat("^",
stringliteral_from_int_v<Num>().value_);
};
template <int Num>
struct exponent2str_aux<Num, 1, true> {
/* Example:
* - exponent2str_aux<-1, 1, true> --> "^-1"
* - exponent2str_aux<-2, 1, true> --> "^-2"
*/
static constexpr auto value = stringliteral_concat("^",
stringliteral_from_int_v<Num>().value_);
};
template <>
struct exponent2str_aux<1, 1, false> {
static constexpr auto value = stringliteral("");
};
// ----- stringliteral_from_exponent -----
template <typename Ratio>
constexpr auto stringliteral_from_exponent() {
static_assert(ratio_concept<Ratio>);
return exponent2str_aux<Ratio::type::num, Ratio::type::den>().value;
};
} /*namespace obs*/
} /*namespace xo*/
/* end ratio_util.hpp */

View file

@ -0,0 +1,170 @@
/* @file stringliteral.hpp */
#pragma once
#include <cmath>
#include <string_view>
#include <algorithm>
#include <cstdint>
namespace xo {
namespace obs {
// ----- stringliteral -----
/** @class stringliteral
*
* @brief class to represent a literal string at compile time, for use as template argument
**/
template <std::size_t N>
struct stringliteral {
constexpr stringliteral() { if (N > 0) value_[0] = '\0'; }
constexpr stringliteral(const char (&str)[N]) { std::copy_n(str, N, value_); }
constexpr int size() const { return N; }
constexpr char const * c_str() const { return value_; }
char value_[N];
};
template < typename First, typename... Rest >
constexpr auto
all_same_v = std::conjunction_v< std::is_same<First, Rest>... >;
/** args and result must be stringliteral **/
template < typename... Ts>
constexpr auto
stringliteral_concat(Ts && ... args)
{
#ifdef NOT_USING
static_assert(all_same_v<std::decay_t<Ts>...>,
"string must share the same char type");
using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >;
#endif
using char_type = char;
/** n1: total number of bytes used by arguments **/
constexpr size_t n1 = (sizeof(Ts) + ...);
/** z1: each string arg has a null terminator included in its size,
* z1 is the number of arguments in parameter pack Ts,
* which equals the number of null terminators used
**/
constexpr size_t z1 = sizeof...(Ts);
/** n: number of chars in concatenated string. +1 for final null **/
constexpr size_t n
= (n1 / sizeof(char_type)) - z1 + 1;
stringliteral<n> result;
size_t pos = 0;
auto detail_concat = [ &pos, &result ](auto && arg) {
constexpr auto count = (sizeof(arg) - sizeof(char_type)) / sizeof(char_type);
std::copy_n(arg, count, result.value_ + pos);
pos += count;
};
(detail_concat(args), ...);
return result;
}
template <std::size_t N1, std::size_t N2>
constexpr auto
stringliteral_compare(stringliteral<N1> && s1, stringliteral<N2> && s2)
{
return std::string_view(s1.value_) <=> std::string_view(s2.value_);
}
// ----- literal_size -----
/** @brief compute number of chars needed to stringify an int **/
template < int d, bool signbit = std::signbit(d) >
struct literal_size;
template < int d >
struct literal_size<d, true> {
/* d < 0 */
static constexpr int size = 1 + literal_size<-d, false>::size;
};
template < int d >
struct literal_size<d, false> {
/* d >= 0 */
static constexpr int size = 1 + literal_size<d/10, false>::size;
};
template <> struct literal_size<0, false> { static constexpr int size = 1; };
template <> struct literal_size<1, false> { static constexpr int size = 1; };
template <> struct literal_size<2, false> { static constexpr int size = 1; };
template <> struct literal_size<3, false> { static constexpr int size = 1; };
template <> struct literal_size<4, false> { static constexpr int size = 1; };
template <> struct literal_size<5, false> { static constexpr int size = 1; };
template <> struct literal_size<6, false> { static constexpr int size = 1; };
template <> struct literal_size<7, false> { static constexpr int size = 1; };
template <> struct literal_size<8, false> { static constexpr int size = 1; };
template <> struct literal_size<9, false> { static constexpr int size = 1; };
template < int d >
constexpr int literal_size_v = literal_size<d>::size;
// ----- literal_from_digit -----
constexpr auto /*stringliteral<2>*/ stringliteral_from_digit( int d ) {
return stringliteral({ static_cast<char>('0' + d), '\0' });
}
#ifdef NOT_YET_22mar24
template < int d >
struct literal_from_digit;
template <> struct literal_from_digit<0> { static constexpr auto value = stringliteral("0"); };
template <> struct literal_from_digit<1> { static constexpr auto value = stringliteral("1"); };
template <> struct literal_from_digit<2> { static constexpr auto value = stringliteral("2"); };
template <> struct literal_from_digit<3> { static constexpr auto value = stringliteral("3"); };
template <> struct literal_from_digit<4> { static constexpr auto value = stringliteral("4"); };
template <> struct literal_from_digit<5> { static constexpr auto value = stringliteral("5"); };
template <> struct literal_from_digit<6> { static constexpr auto value = stringliteral("6"); };
template <> struct literal_from_digit<7> { static constexpr auto value = stringliteral("7"); };
template <> struct literal_from_digit<8> { static constexpr auto value = stringliteral("8"); };
template <> struct literal_from_digit<9> { static constexpr auto value = stringliteral("9"); };
template < int d >
constexpr auto literal_from_digit_v() { return literal_from_digit<d>::value; }
#endif
// ----- stringliteral_from_int -----
template < int D, int N = literal_size_v<D>, bool signbit = std::signbit(D) >
struct stringliteral_from_int;
template < int D, int N = literal_size_v<D>, bool signbit = std::signbit(D) >
constexpr auto stringliteral_from_int_v() { return stringliteral_from_int<D, N, signbit>::value; }
template < int D >
struct stringliteral_from_int< D, 1, false > {
static constexpr auto value = stringliteral_from_digit(D);
};
template < int D, int N >
struct stringliteral_from_int< D, N, false > {
static constexpr auto _prefix = stringliteral_from_int_v< D / 10, N - 1, false >();
static constexpr auto _suffix = stringliteral_from_digit(D % 10);
static constexpr auto value = stringliteral_concat(_prefix.value_, _suffix.value_);
};
template < int D, int N >
struct stringliteral_from_int< D, N, true > {
static constexpr auto _suffix = stringliteral_from_int_v< -D, N - 1, false>();
static constexpr auto value = stringliteral_concat("-", _suffix.value_);
};
} /*namespace obs*/
} /*namespace xo*/
/* end stringliteral.hpp */

394
include/xo/unit/unit.hpp Normal file
View file

@ -0,0 +1,394 @@
/* @file dimension.hpp */
#pragma once
#include "dimension_impl.hpp"
namespace xo {
namespace obs {
// ----- wrap_unit -----
template <typename Scalefactor, typename BpuList>
struct wrap_unit {
static_assert(ratio_concept<Scalefactor>);
static_assert(bpu_list_concept<BpuList>);
//using _norm_type = bpu_normalize<BpuList>;
using scalefactor_type = Scalefactor;
using dim_type = BpuList;
/* canon_type just orders dimensions by increasing native_dim_id */
using canon_type = canonical_impl<dim_type>::dim_type;
static_assert(bpu_list_concept<canon_type>);
};
// ----- normalize_unit -----
template <typename Unit>
struct normalize_unit {
static_assert(unit_concept<Unit>);
using type = wrap_unit<std::ratio<1>, typename Unit::dim_type>;
};
template <typename Unit>
using normalize_unit_t = normalize_unit<Unit>::type;
// ----- dimensionless_v -----
template <typename Unit>
constexpr auto dimensionless_v = std::same_as<typename Unit::dim_type, void>;
// ----- unit_find_bpu -----
/** @brief find basis-power-unit matching native_dim_id
*
* Constructs dimensionless native_bpu if no match
**/
template <typename U, dim BasisDim>
struct unit_find_bpu {
using type = di_find_bpu<typename U::dim_type, BasisDim>::type;
};
template <typename U, dim BasisDim>
using unit_find_bpu_t = unit_find_bpu<U, BasisDim>::type;
// ----- unit_abbrev_v -----
/** @brief canonical stringliteral abbreviation for dimension D. **/
template <typename U>
constexpr auto unit_abbrev_v = di_assemble_abbrev<typename U::canon_type>::value;
// ----- unit_invert -----
template <typename U>
struct unit_invert {
static_assert(unit_concept<U>);
using _sf = std::ratio_divide<std::ratio<1>, typename U::scalefactor_type>;
using _di = di_invert< typename U::dim_type >::type;
using type = wrap_unit< _sf, _di >;
static_assert(unit_concept<type>);
};
template <typename U>
using unit_invert_t = unit_invert<U>::type;
// ----- unit_cartesian_product -----
template <typename U1, typename U2>
struct unit_cartesian_product {
/* when a basis dimension (represented by a bpu<..>::c_native_dim)
* is present in both {U1, U2}, we need to pick a common unit
* (represented by bpu<..>::scalefactor_type).
*
* scale factors from such conversions are collected in:
* 1a. _mult_type::outer_scalefactor_type (compile-time exact representation using std::ratio)
* 1b. _mult_type::outer_scalefactor_inexact (compile-time constexpr)
*/
static_assert(unit_concept<U1>);
static_assert(unit_concept<U2>);
/* _mult_type -> describes product dimension */
using _mult_type = di_cartesian_product<
typename U1::dim_type,
typename U2::dim_type>;
/* compile-time exact scalefactor for product dimension
* (distilled from any forced rescaling)
*/
using _mult_sf_type = _mult_type::outer_scalefactor_type;
/* bpulist specifying basis factors (possibly to rational powers) in product dimension */
using _mult_di_type = _mult_type::bpu_list_type;
/* note: inexact scalefactor doesn't come up here.
* It's not present in unit types, only appears as byproduct
* of products/ratios of units
*/
using _sf1_type = typename std::ratio_multiply<
typename U1::scalefactor_type,
typename U2::scalefactor_type>::type;
using _sf_type = typename std::ratio_multiply<_mult_sf_type, _sf1_type>::type;
/* note: we can compute inexact scale factor,
* but can't make it a template argument
*/
using exact_unit_type = wrap_unit< _sf_type, _mult_di_type >;
static constexpr double c_scalefactor_inexact = _mult_type::c_outer_scalefactor_inexact;
static_assert(unit_concept<exact_unit_type>);
};
/* WARNING: omits inexact scalefactor */
template <typename U1, typename U2>
using unit_cartesian_product_t = unit_cartesian_product<U1, U2>::exact_unit_type;
// ----- unit_divide -----
template <typename U1, typename U2>
struct unit_divide {
static_assert(unit_concept<U1>);
static_assert(unit_concept<U2>);
using _mult_type = unit_cartesian_product<U1, unit_invert_t<U2>>;
using exact_unit_type = _mult_type::exact_unit_type;
static constexpr double c_scalefactor_inexact = _mult_type::c_scalefactor_inexact;
};
/* WARNING: omits inexact scalefactor */
template <typename U1, typename U2>
using unit_divide_t = unit_divide<U1, U2>::exact_unit_type;
// ----- same_dimension -----
/* true iff U1 and U2 represent the same dimension, i.e. differ only in dimensionless scaling factor
*
* To verify scale also, use same_unit<U1,U2> instead
*/
template <typename U1, typename U2>
struct same_dimension {
static_assert(unit_concept<U1>);
static_assert(unit_concept<U2>);
using _unit_ratio_type = typename unit_cartesian_product<U1, unit_invert_t<U2>>::exact_unit_type;
static_assert(std::same_as<typename _unit_ratio_type::dim_type, void>);
static constexpr bool value = std::same_as<typename _unit_ratio_type::dim_type, void>;
};
template <typename U1, typename U2>
constexpr bool same_dimension_v = same_dimension<U1, U2>::value;
// ----- same_unit -----
template <typename U1, typename U2>
struct same_unit {
static_assert(unit_concept<U1>);
static_assert(unit_concept<U2>);
using _unit_ratio_type = unit_cartesian_product<U1, unit_invert_t<U2>>;
using _unit_exact_type = typename _unit_ratio_type::exact_unit_type;
using _unit_scalefactor_type = _unit_exact_type::scalefactor_type;
static constexpr double c_unit_ratio_inexact = _unit_ratio_type::c_scalefactor_inexact;
static_assert(std::same_as<_unit_scalefactor_type, std::ratio<1>>);
static_assert(std::same_as<typename _unit_exact_type::dim_type, void>);
static constexpr bool value = (std::same_as<_unit_scalefactor_type, std::ratio<1>>
&& (c_unit_ratio_inexact == 1.0)
&& std::same_as<typename _unit_exact_type::dim_type, void>);
};
template <typename U1, typename U2>
constexpr bool same_unit_v = same_unit<U1, U2>::value;
// ----- unit_conversion_factor -----
template <typename U1, typename U2>
struct unit_conversion_factor {
static_assert(same_dimension_v<U1, U2>);
using _unit_ratio_type = typename unit_cartesian_product<U1, unit_invert_t<U2>>::exact_unit_type;
using type = _unit_ratio_type::scalefactor_type;
static constexpr double c_scalefactor_inexact = _unit_ratio_type::c_scalefactor_inexact;
};
/** conversion factor from U1 to U2:
* U1 = x.U2
* with:
* x = R::num / R::den
* R = unit_conversion_factor_t<U1,U2>
*
* WARNING: omits inexact scalefactor unit_conversion_factor<U1,U2>::c_scalefactor_inexact
**/
template <typename U1, typename U2>
using unit_conversion_factor_t = unit_conversion_factor<U1, U2>::type;
// ----- units -----
namespace units {
/* computing abbreviations:
* - unit_abbrev_v<Unit> :: stringliteral<...>
* - unit_abbrev_v<Unit>.c_str() :: const char *
*
* relies on
* - di_assemble_abbrev, di_assemble_abbrev_helper [dimension_impl.hpp]
*
* - bpu_assemble_abbrev<native_bpu>() [native_bpu.hpp]
* - bpu_assemble_abbrev_helper< native_bpu::c_native_dim,
* native_bpu::scalefactor_type,
* native_bpu::power_type >
* -> stringliteral
*
* + can specialize for specific combinations
*
* - native_unit_abbrev_helper< native_bpu::c_native_dim,
* native_bpu::power_type >
*/
// ----- weight -----
using milligram = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::mass,
std::ratio<1, 1000>> > >;
template <>
struct scaled_native_unit_abbrev<dim::mass, std::ratio<1, 1000>> {
static constexpr auto value = stringliteral("mg");
};
using gram = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::mass,
std::ratio<1>> > >;
using kilogram = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::mass,
std::ratio<1000>> > >;
template <>
struct scaled_native_unit_abbrev<dim::mass, std::ratio<1000>> {
static constexpr auto value = stringliteral("kg");
};
// ----- distance -----
using millimeter = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::distance,
std::ratio<1,1000>> > >;
template <>
struct scaled_native_unit_abbrev<dim::distance, std::ratio<1,1000>> {
static constexpr auto value = stringliteral("mm");
};
using meter = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<1>> > >;
using kilometer = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<1000>> > >;
template <>
struct scaled_native_unit_abbrev<dim::distance, std::ratio<1000>> {
static constexpr auto value = stringliteral("km");
};
// ----- time -----
using nanosecond = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<1, 1000000000>> > >;
template <>
struct scaled_native_unit_abbrev<dim::time, std::ratio<1,1000000000>> {
static constexpr auto value = stringliteral("ns");
};
using microsecond = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<1, 1000000>> > >;
template <>
struct scaled_native_unit_abbrev<dim::time, std::ratio<1,1000000>> {
static constexpr auto value = stringliteral("us");
};
using millisecond = wrap_unit< std::ratio<1>, bpu_node< bpu<dim::time,
std::ratio<1,1000>> > >;
template <>
struct scaled_native_unit_abbrev<dim::time, std::ratio<1,1000>> {
static constexpr auto value = stringliteral("ms");
};
using second = wrap_unit< std::ratio<1>, bpu_node< bpu<dim::time,
std::ratio<1>> > >;
using minute = wrap_unit< std::ratio<1>, bpu_node< bpu<dim::time,
std::ratio<60>> > >;
template <>
struct scaled_native_unit_abbrev<dim::time, std::ratio<60>> {
static constexpr auto value = stringliteral("min");
};
using hour = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<3600>> > >;
template <>
struct scaled_native_unit_abbrev<dim::time, std::ratio<3600>> {
static constexpr auto value = stringliteral("hr");
};
using day = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<24*3600>> > >;
template <>
struct scaled_native_unit_abbrev<dim::time, std::ratio<24*3600>> {
static constexpr auto value = stringliteral("dy");
};
using month = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<30*24*3600>> > >;
template <>
struct scaled_native_unit_abbrev<dim::time, std::ratio<30*24*3600>> {
static constexpr auto value = stringliteral("mo");
};
using yr250 = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<250*24*3600>> > >;
template <>
struct scaled_native_unit_abbrev<dim::time, std::ratio<250*24*3600>> {
static constexpr auto value = stringliteral("yr250");
};
// ------ volatility ------
/* volatility in units of 1/sqrt(1day)
* volatility^2 in units of 1/day
*/
using volatility_1d = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<24*3600>,
std::ratio<-1,2>> > >;
/* volatility in units of 1/sqrt(30day)
* volatility^2 in units of 1/(30day)
*/
using volatility_30d = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<30*24*3600>,
std::ratio<-1,2>> > >;
/* volatility in units of 1/sqrt(250day)
* volatility^2 in units of 1/(250day)
*/
using volatility_250d = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::time,
std::ratio<250*24*3600>,
std::ratio<-1,2>> > >;
using currency = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::currency,
std::ratio<1>> > >;
using price = wrap_unit< std::ratio<1>,
bpu_node< bpu<dim::price,
std::ratio<1>> > >;
}
} /*namespace obs*/
} /*namespace xo*/
/* end dimension.hpp */

25
utest/CMakeLists.txt Normal file
View file

@ -0,0 +1,25 @@
# build unittest observable/utest
set(SELF_EXECUTABLE_NAME utest.unit)
set(SELF_SOURCE_FILES
unit_utest_main.cpp
unit.test.cpp quantity.test.cpp)
add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES})
xo_include_options2(${SELF_EXECUTABLE_NAME})
add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME})
target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL)
# ----------------------------------------------------------------
# internal dependencies: logutil, ...
xo_self_dependency(${SELF_EXECUTABLE_NAME} xo_unit)
xo_dependency(${SELF_EXECUTABLE_NAME} reflect)
# ----------------------------------------------------------------
# 3rd party dependency: catch2:
xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2)
# end CMakeLists.txt

653
utest/quantity.test.cpp Normal file
View file

@ -0,0 +1,653 @@
/* @file quantity.test.cpp */
#include "xo/unit/quantity.hpp"
#include "xo/reflect/Reflect.hpp"
//#include <xo/randomgen/random_seed.hpp>
//#include <xo/randomgen/xoshiro256.hpp>
#include <xo/indentlog/scope.hpp>
#include <xo/indentlog/print/tag.hpp>
#include <catch2/catch.hpp>
namespace xo {
using xo::obs::quantity;
using xo::obs::qty::milliseconds;
using xo::obs::qty::seconds;
using xo::obs::qty::minutes;
using xo::obs::qty::volatility30d;
using xo::obs::qty::volatility250d;
using xo::obs::unit_find_bpu_t;
using xo::obs::unit_conversion_factor_t;
using xo::obs::unit_cartesian_product_t;
using xo::obs::unit_cartesian_product;
using xo::obs::unit_invert_t;
using xo::obs::unit_abbrev_v;
using xo::obs::same_dimension_v;
using xo::obs::dim;
using xo::obs::from_ratio;
using xo::obs::stringliteral_from_ratio;
using xo::obs::ratio2str_aux;
using xo::obs::cstr_from_ratio;
using xo::reflect::Reflect;
namespace units = xo::obs::units;
namespace ut {
/* use 'testcase' snippet to add test cases here */
TEST_CASE("quantity", "[quantity]") {
//constexpr bool c_debug_flag = false;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
//scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.quantity"), xtag("foo", foo), ...);
//log && log("(A)", xtag("foo", foo));
quantity<units::second, int64_t> t = seconds(1L);
REQUIRE(t.scale() == 1);
static_assert(t.c_basis_power<dim::time> == 1);
static_assert(t.c_basis_power<dim::mass> == 0);
} /*TEST_CASE(quantity)*/
TEST_CASE("add1", "[quantity]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add1"));
quantity<units::second, int64_t> t1 = seconds(1);
quantity<units::second, int64_t> t2 = seconds(2);
static_assert(std::same_as<decltype(t1)::unit_type, units::second>);
static_assert(std::same_as<decltype(t2)::unit_type, units::second>);
auto sum = t1 + t2;
CHECK(strcmp(sum.unit_cstr(), "s") == 0);
static_assert(std::same_as<decltype(sum)::unit_type, units::second>);
static_assert(t1.c_basis_power<dim::time> == 1);
static_assert(t2.c_basis_power<dim::time> == 1);
REQUIRE(sum.scale() == 3);
} /*TEST_CASE(add1)*/
TEST_CASE("add2", "[quantity]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add2"));
quantity<units::second, int64_t> t1 = seconds(1);
{
CHECK(strcmp(t1.unit_cstr(), "s") == 0);
CHECK(t1.scale() == 1);
}
auto m2 = minutes(2);
{
static_assert(m2.c_basis_power<dim::time> == 1);
log && log(xtag("m2.scale", m2.scale()), xtag("m2.unit", m2.unit_cstr()));
CHECK(m2.scale() == 2);
CHECK(strcmp(m2.unit_cstr(), "min") == 0);
}
{
auto m2_sec = m2.in_units_of<units::second, int64_t>();
static_assert(std::same_as<decltype(m2_sec), int64_t>);
log && log(XTAG(m2_sec));
CHECK(m2_sec == 120);
}
quantity<units::second, int64_t> t2 = m2;
{
auto sum = t1 + t2;
static_assert(std::same_as<decltype(sum)::unit_type, units::second>);
static_assert(sum.c_basis_power<dim::time> == 1);
log && log(xtag("t1.unit", t1.unit_cstr()), xtag("t2.unit", t2.unit_cstr()));
log && log(xtag("sum.unit", sum.unit_cstr()));
CHECK(strcmp(t2.unit_cstr(), "s") == 0);
CHECK(strcmp(sum.unit_cstr(), "s") == 0);
CHECK(sum.scale() == 121);
}
} /*TEST_CASE(add2)*/
TEST_CASE("add3", "[quantity]") {
quantity<units::second, int64_t> t1 = seconds(1);
quantity<units::minute, int64_t> t2 = minutes(2);
/* sum will take unit from lhs argument to + */
auto sum = t1 + t2;
static_assert(sum.c_basis_power<dim::time> == 1);
static_assert(std::same_as<decltype(sum)::unit_type, units::second>);
REQUIRE(sum.scale() == 121);
} /*TEST_CASE(add3)*/
TEST_CASE("add4", "[quantity]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add4"));
using u_kgps_result = unit_cartesian_product<units::kilogram, unit_invert_t<units::second>>;
using u_kgps = u_kgps_result::exact_unit_type;
using u_gpm_result = unit_cartesian_product<units::gram, unit_invert_t<units::minute>>;
using u_gpm = u_gpm_result::exact_unit_type;
{
static_assert(u_kgps_result::c_scalefactor_inexact == 1.0);
static_assert(std::same_as<unit_find_bpu_t<u_kgps, dim::mass>::power_type, std::ratio<1>>);
static_assert(std::same_as<unit_find_bpu_t<u_kgps, dim::time>::power_type, std::ratio<-1>>);
static_assert(std::same_as<unit_find_bpu_t<u_gpm, dim::mass>::power_type, std::ratio<1>>);
static_assert(std::same_as<unit_find_bpu_t<u_gpm, dim::time>::power_type, std::ratio<-1>>);
log && log(xtag("u_kgps", unit_abbrev_v<u_kgps>.c_str()));
log && log(xtag("u_gpm", unit_abbrev_v<u_gpm>.c_str()));
CHECK(strcmp(unit_abbrev_v<u_kgps>.c_str(), "kg.s^-1") == 0);
CHECK(strcmp(unit_abbrev_v<u_gpm>.c_str(), "g.min^-1") == 0);
static_assert(same_dimension_v<u_kgps, u_gpm>);
}
using convert_type = unit_conversion_factor_t<u_kgps, u_gpm>;
{
log && log(xtag("u_kgps->u_gpm", cstr_from_ratio<convert_type>()));
CHECK(strcmp(cstr_from_ratio<convert_type>(), "60000") == 0);
CHECK(from_ratio<int64_t, convert_type>() == 60000);
}
/* note: in practice probably write
* kilograms(0.1) / seconds(1);
* but
* 1. don't want to exercise quantity {*,/} here;
* 2. want to force unit representation
*/
auto q1 = quantity<u_kgps, double>::promote(0.1);
auto q2 = quantity<u_gpm, double>();
{
q2 = q1;
static_assert(q1.c_basis_power<dim::mass> == 1);
static_assert(q1.c_basis_power<dim::time> == -1);
static_assert(q2.c_basis_power<dim::mass> == 1);
static_assert(q2.c_basis_power<dim::time> == -1);
log && log(XTAG(q1), XTAG(q2));
CHECK(strcmp(q1.unit_cstr(), "kg.s^-1") == 0);
CHECK(q1.scale() == 0.1);
CHECK(strcmp(q2.unit_cstr(), "g.min^-1") == 0);
CHECK(q2.scale() == 6000.0);
}
} /*TEST_CASE(add4)*/
TEST_CASE("add5", "[quantity][fractional_dimension]") {
constexpr bool c_debug_flag = false;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add5"));
//log && log("(A)", xtag("foo", foo));
auto vol_250d = volatility250d(0.2);
{
log && log(xtag("vol_250d", vol_250d));
CHECK(strcmp(vol_250d.unit_cstr(), "yr250^-(1/2)") == 0);
CHECK(vol_250d.scale() == 0.2);
}
/* scaling factor from 30-day vol to 250-day vol is sqrt(250/30) ~ 2.88675
* so 0.1 -> 0.288675
*/
auto vol_30d = volatility30d(0.1);
{
log && log(xtag("vol_30d", vol_30d));
CHECK(strcmp(vol_30d.unit_cstr(), "mo^-(1/2)") == 0);
CHECK(vol_30d.scale() == Approx(0.1).epsilon(1e-6));
}
/* conversion from monthly vol to (250-day) annual vol */
using u_vol250d = units::volatility_250d;
{
quantity<u_vol250d, double> q = vol_30d;
log && log(xtag("q", q));
CHECK(strcmp(q.unit_cstr(), "yr250^-(1/2)") == 0);
CHECK(q.scale() == Approx(0.288675).epsilon(1e-6));
}
{
auto sum = vol_250d + vol_30d;
static_assert(sum.c_basis_power<dim::time, double> == -0.5);
log && log(XTAG(sum));
CHECK(strcmp(sum.unit_cstr(), "yr250^-(1/2)") == 0);
/* 0.1mo^-(1/2) ~ 0.288675yr250^-(1/2) */
CHECK(sum.scale() == Approx(0.4886751).epsilon(1e-6));
}
} /*TEST_CASE(add5)*/
TEST_CASE("mult1", "[quantity]") {
constexpr bool c_debug_flag = false;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.mult1"));
//log && log("(A)", xtag("foo", foo));
auto q0 = milliseconds(5);
auto q1 = seconds(60);
auto q2 = minutes(1);
{
auto r = q0 * q1;
static_assert(r.c_basis_power<dim::time> == 2);
log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0*q1", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* taking unit from LHS */
REQUIRE(strcmp(r.unit_cstr(), "ms^2") == 0);
REQUIRE(r.scale() == 300000);
}
{
auto r = q1 * q2;
static_assert(r.c_basis_power<dim::time> == 2);
log && log(xtag("q1", q1), xtag("q2", q2), xtag("q1*q2", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* taking unit from LHS */
REQUIRE(strcmp(r.unit_cstr(), "s^2") == 0);
REQUIRE(r.scale() == 3600);
}
{
auto r = q2 * q1;
static_assert(r.c_basis_power<dim::time> == 2);
log && log(xtag("q1", q1), xtag("q2", q2), xtag("r=q2*q1", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* taking unit from LHS */
CHECK(strcmp(r.unit_cstr(), "min^2") == 0);
CHECK(r.scale() == 1);
}
{
auto r = q2 * 60;
static_assert(r.c_basis_power<dim::time> == 1);
static_assert(std::same_as<decltype(r)::repr_type, int>);
log && log(xtag("q2*60", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* preserve units of existing quantity */
CHECK(strcmp(r.unit_cstr(), "min") == 0);
CHECK(r.scale() == 60);
}
{
auto r = q2 * 60U;
static_assert(r.c_basis_power<dim::time> == 1);
static_assert(std::same_as<decltype(r)::repr_type, uint32_t>);
log && log(xtag("q2*60U", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* preserve units of existing quantity */
CHECK(strcmp(r.unit_cstr(), "min") == 0);
CHECK(r.scale() == 60U);
}
{
auto r = (q2 * 60.5);
static_assert(r.c_basis_power<dim::time> == 1);
/* verify dimension */
static_assert(std::same_as<decltype(r)::repr_type, double>);
log && log(xtag("q2*60.5", q2*60.5));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* preserve units of existing quantity */
REQUIRE(strcmp(r.unit_cstr(), "min") == 0);
REQUIRE(r.scale() == 60.5);
}
{
log && log(xtag("q2*60.5f", q2*60.5f));
auto r = (q2 * 60.5f);
/* verify dimension */
static_assert(r.c_basis_power<dim::time> == 1);
static_assert(std::same_as<decltype(r)::repr_type, float>);
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* preserve units of existing quantity */
REQUIRE(strcmp(r.unit_cstr(), "min") == 0);
REQUIRE(r.scale() == 60.5f);
}
{
auto r = 60 * q2;
static_assert(r.c_basis_power<dim::time> == 1);
static_assert(std::same_as<decltype(r)::repr_type, int>);
log && log(xtag("60*q2", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* preserve units of existing quantity */
CHECK(strcmp(r.unit_cstr(), "min") == 0);
CHECK(r.scale() == 60);
}
{
log && log(xtag("60.5*q2", 60.5*q2));
auto r = 60.5 * q2;
static_assert(r.c_basis_power<dim::time> == 1);
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
static_assert(std::same_as<decltype(r)::repr_type, double>);
/* preserve units of existing quantity */
CHECK(strcmp(r.unit_cstr(), "min") == 0);
CHECK(r.scale() == 60.5);
}
{
log && log(xtag("60.5f*q2", 60.5f*q2));
auto r = 60.5f * q2;
static_assert(r.c_basis_power<dim::time> == 1);
static_assert(std::same_as<decltype(r)::repr_type, float>);
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* preserve units of existing quantity */
CHECK(strcmp(r.unit_cstr(), "min") == 0);
CHECK(r.scale() == 60.5);
}
} /*TEST_CASE(mult1)*/
TEST_CASE("div1", "[quantity]") {
constexpr bool c_debug_flag = false;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div1"));
//log && log("(A)", xtag("foo", foo));
auto q0 = milliseconds(5);
auto q1 = milliseconds(10);
{
/* repr_type adopts argument to milliseconds() */
static_assert(std::same_as<decltype(q0)::repr_type, int>);
static_assert(std::same_as<decltype(q1)::repr_type, int>);
auto r = q0/q1;
log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/q1", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* verify dimensionless + no type promotion */
static_assert(std::same_as<decltype(r), int>);
/* verify scale (truncate)*/
REQUIRE(r == 0);
}
auto q0p = milliseconds(5.0);
{
static_assert(std::same_as<decltype(q0p)::repr_type, double>);
auto r = q0p/q1;
static_assert(std::same_as<decltype(r), double>);
log && log(XTAG(q0p), xtag("q0p/q1", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* verify dimension */
static_assert(std::same_as<decltype(r), double>);
/* verify scale */
REQUIRE(r == 0.5);
}
auto r1 = 1.0 / q0;
{
log && log(XTAG(q0), xtag("r1=1.0/q0", r1));
/* verify dimension */
static_assert(r1.c_basis_power<dim::time> == -1);
/* verify scale */
REQUIRE(r1.scale() == 0.2);
}
} /*TEST_CASE(div1)*/
TEST_CASE("div2", "[quantity]") {
constexpr bool c_debug_flag = false;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div2"));
auto q0 = milliseconds(5);
auto q1 = milliseconds(20.0);
{
auto r = q0/q1;
log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/q1", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* verify dimension */
static_assert(std::same_as<decltype(r), double>);
/* verify scale */
REQUIRE(r == 0.25);
}
{
auto r = q1/q0;
log && log(xtag("q0", q0), xtag("q1", q1), xtag("q1/q0", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* verify dimension */
static_assert(std::same_as<decltype(r), double>);
/* verify scale */
REQUIRE(r == 4.0);
}
{
auto r = q0/(q1*q1);
log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/(q1*q1)", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* verify dimension */
static_assert(r.c_basis_power<dim::time> == -1);
/* verify scale */
REQUIRE(r.scale() == 0.0125);
}
{
auto r = (q0*q0)/q1;
log && log(xtag("q0", q0), xtag("q1", q1), xtag("(q0*q0)/q1", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* verify dimension */
static_assert(r.c_basis_power<dim::time> == 1);
/* verify scale */
REQUIRE(r.scale() == 1.25);
}
} /*TEST_CASE(div2)*/
TEST_CASE("div3", "[quantity]") {
constexpr bool c_debug_flag = false;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div3"));
auto q0 = milliseconds(5);
auto q1 = milliseconds(20.0);
{
auto r = q0/q1;
log && log(XTAG(q0), XTAG(q1), xtag("q0/q1", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* verify dimension */
static_assert(std::same_as<decltype(r), double>);
/* verify scale */
REQUIRE(r == 0.25);
}
{
auto r = q1/q0;
log && log(xtag("q0", q0), xtag("q1", q1), xtag("q1/q0", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* verify dimension */
static_assert(std::same_as<decltype(r), double>);
/* verify scale */
REQUIRE(r == 4.0);
}
{
auto r = q0/(q1*q1);
log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/(q1*q1)", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* verify dimension */
static_assert(r.c_basis_power<dim::time> == -1);
/* verify scale */
REQUIRE(r.scale() == 0.0125);
}
{
auto r = (q0*q0)/q1;
log && log(xtag("q0", q0), xtag("q1", q1), xtag("(q0*q0)/q1", r));
log && log(xtag("r.type", Reflect::require<decltype(r)>()->canonical_name()));
/* verify dimension */
static_assert(r.c_basis_power<dim::time> == 1);
/* verify scale */
REQUIRE(r.scale() == 1.25);
}
} /*TEST_CASE(div3)*/
TEST_CASE("div4", "[quantity]") {
constexpr bool c_debug_flag = false;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.mult2"));
//log && log("(A)", xtag("foo", foo));
auto q1 = volatility250d(0.2);
auto q2 = volatility30d(0.1);
auto r = q1/q2;
/* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy),
* so q1/q2 ~ 0.6928
*/
log && log(XTAG(q1), XTAG(q2), XTAG(q1/q2));
/* verify dimensionless result */
static_assert(std::same_as<decltype(r), double>);
/* verify scale of result */
CHECK(r == Approx(0.692820323).epsilon(1e-6));
} /*TEST_CASE(div4)*/
} /*namespace ut*/
} /*namespace xo*/
/* end quantity.test.cpp */

346
utest/unit.test.cpp Normal file
View file

@ -0,0 +1,346 @@
/* @file dimension.test.cpp */
#include "xo/unit/unit.hpp"
#include "xo/reflect/Reflect.hpp"
#include "xo/cxxutil/demangle.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <catch2/catch.hpp>
#include <iostream>
namespace xo {
namespace ut {
/* compile-time tests */
using xo::reflect::Reflect;
using xo::obs::dim;
using xo::obs::native_unit_abbrev_v;
using xo::obs::units::scaled_native_unit_abbrev_v;
//using xo::obs::native_dim_abbrev;
using xo::obs::stringliteral_compare;
using xo::obs::literal_size_v;
using xo::obs::stringliteral_from_digit;
using xo::obs::stringliteral_from_int_v;
using xo::obs::stringliteral;
#ifndef __clang__
using xo::obs::stringliteral_concat;
using xo::obs::stringliteral_from_ratio;
using xo::obs::bpu_assemble_abbrev_helper;
using xo::obs::bpu_assemble_abbrev;
#endif
using xo::obs::bpu_node;
using xo::obs::wrap_unit;
using xo::obs::unit_abbrev_v;
//using xo::obs::dim_abbrev_v;
using xo::obs::di_cartesian_product;
using xo::obs::di_cartesian_product1;
using xo::obs::unit_cartesian_product_t;
using xo::obs::bpu_cartesian_product;
using xo::obs::bpu_cartesian_product_helper;
using xo::obs::unit_invert_t;
using xo::obs::units::gram;
using xo::obs::units::second;
using xo::print::ccs;
template <typename T>
int unused()
{
return 1;
}
template <typename T1, typename T2>
constexpr bool unused_same(typename std::enable_if_t<std::is_same<T1, T2>::value, bool> result = true)
{
return result;
}
TEST_CASE("native_unit_abbrev", "[native_dim_abbrev]") {
constexpr bool c_debug_flag = true;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.native_dim_abbrev"));
//log && log("(A)", xtag("foo", foo));
/* NOTE: the .value_ expression below will fail to compile if missing specialization for
* native_dim_abbrev on native_dim_id::foo; that's the point :)
*/
REQUIRE(strcmp(scaled_native_unit_abbrev_v<dim::mass, std::ratio<1>>.value_, "g") == 0);
REQUIRE(strcmp(scaled_native_unit_abbrev_v<dim::time, std::ratio<1>>.value_, "s") == 0);
REQUIRE(strcmp(scaled_native_unit_abbrev_v<dim::currency, std::ratio<1>>.value_, "ccy") == 0);
REQUIRE(strcmp(scaled_native_unit_abbrev_v<dim::price, std::ratio<1>>.value_, "px") == 0);
#ifdef OBSOLETE
REQUIRE(strcmp(native_dim_abbrev<dim::mass>().value_, "") != 0);
REQUIRE(strcmp(native_dim_abbrev<dim::time>().value_, "") != 0);
REQUIRE(strcmp(native_dim_abbrev<dim::currency>().value_, "") != 0);
REQUIRE(strcmp(native_dim_abbrev<dim::price>().value_, "") != 0);
#endif
static_assert(stringliteral_compare(stringliteral_from_digit(0), stringliteral("0")) == 0);
static_assert(stringliteral_compare(stringliteral_from_digit(1), stringliteral("1")) == 0);
static_assert(stringliteral_compare(stringliteral_from_digit(9), stringliteral("9")) == 0);
static_assert(literal_size_v<0> == 1);
static_assert(literal_size_v<10> == 2);
static_assert(literal_size_v<99> == 2);
static_assert(literal_size_v<100> == 3);
static_assert(literal_size_v<999> == 3);
static_assert(stringliteral_compare(stringliteral_from_int_v<0>(), stringliteral("0")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<9>(), stringliteral("9")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<1, 1, false>(), stringliteral("1")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<9, 1, false>(), stringliteral("9")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<9>(), stringliteral("9")) == 0);
/* NOTE: clang16 complains starting here; gcc is fine */
#ifndef __clang__
if constexpr (stringliteral_concat("a", "b").size() == 3) {
REQUIRE(true);
} else {
REQUIRE(false);
}
static_assert(stringliteral_compare(stringliteral_concat("hello", " ", "world"),
stringliteral("hello world")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<10, 2, false>(), stringliteral("10")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<10>(), stringliteral("10")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<99, 2, false>(), stringliteral("99")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<99>(), stringliteral("99")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<100, 3, false>(), stringliteral("100")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<100>(), stringliteral("100")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<999, 3, false>(), stringliteral("999")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<999>(), stringliteral("999")) == 0);
//std::cerr << "test=" << stringliteral_from_int_v<-1, 2, true>().value_ << std::endl;
static_assert(stringliteral_compare(stringliteral_from_int_v<-1, 2, true>(), stringliteral("-1")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-1>(), stringliteral("-1")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-9, 2, true>(), stringliteral("-9")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-9>(), stringliteral("-9")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-10, 3, true>(), stringliteral("-10")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-10>(), stringliteral("-10")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-99, 3, true>(), stringliteral("-99")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-99>(), stringliteral("-99")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-100, 4, true>(), stringliteral("-100")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-100>(), stringliteral("-100")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-999, 4, true>(), stringliteral("-999")) == 0);
static_assert(stringliteral_compare(stringliteral_from_int_v<-999>(), stringliteral("-999")) == 0);
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<2,3>>(), stringliteral("(2/3)")) == 0);
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<4,6>>(), stringliteral("(2/3)")) == 0);
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<-1>>(), stringliteral("-1")) == 0);
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<-2>>(), stringliteral("-2")) == 0);
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<-6,3>>(), stringliteral("-2")) == 0);
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<-3,2>>(), stringliteral("-(3/2)")) == 0);
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<3,-2>>(), stringliteral("-(3/2)")) == 0);
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<-1,2>>(), stringliteral("-(1/2)")) == 0);
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<1,2>>(), stringliteral("(1/2)")) == 0);
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<3,2>>(), stringliteral("(3/2)")) == 0);
//log && log(xtag("ratio<2>", stringliteral_from_ratio<std::ratio<2>>().c_str()));
static_assert(stringliteral_compare(stringliteral_from_ratio<std::ratio<2>>(), stringliteral("2")) == 0);
static_assert(stringliteral_compare(bpu_assemble_abbrev_helper<dim::mass, std::ratio<1>, std::ratio<1>>(), stringliteral("g")) == 0);
//log && log(xtag("s^(-1/2)", bpu_assemble_abbrev_helper<dim::time, std::ratio<1>, std::ratio<-1,2>>().c_str()));
static_assert(stringliteral_compare(bpu_assemble_abbrev_helper<dim::time, std::ratio<1>, std::ratio<-1,2>>(), stringliteral("s^-(1/2)")) == 0);
//stringliteral_compare(stringliteral_from_ratio<std::ratio<2>>(), stringliteral("^2")) == 0);
#endif
//static_assert(stringliteral_compare(stringliteral_from_int_v<10>(), obs::stringliteral("10")) == 0);
//REQUIRE(strcmp(obs::stringliteral_from_digit(1).value_, "1") == 0);
//REQUIRE(strcmp(obs::ratio2str<std::ratio<1>>().value_, "") == 0);
} /*TEST_CASE(native_dim_abbrev)*/
TEST_CASE("dimension", "[dimension]") {
constexpr bool c_debug_flag = true;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension"));
//log && log("(A)", xtag("foo", foo));
using t1 = obs::bpu<obs::dim::currency, std::ratio<1,1>>;
static_assert(t1::c_native_dim == obs::dim::currency);
static_assert(t1::power_type::num == 1);
static_assert(t1::power_type::den == 1);
using t2 = obs::bpu<obs::dim::time, std::ratio<1>, std::ratio<-1,2>>;
static_assert(t2::c_native_dim == obs::dim::time);
static_assert(t2::power_type::num == -1);
static_assert(t2::power_type::den == 2);
using dim1 = wrap_unit<std::ratio<1>, bpu_node<t1>>;
using d1 = dim1::dim_type; /* ccy */
REQUIRE(unused_same<d1::front_type, t1>());
REQUIRE(unused_same<obs::lookup_bpu<d1, 0>::power_unit_type, t1>());
#ifdef NOT_USING
static_assert(obs::lo_basis_elt_of<d1>::c_lo_basis == t1::c_basis);
#endif
static_assert(obs::native_lo_bwp_of<d1>::bwp_type::c_index == 0);
static_assert(obs::native_lo_bwp_of<d1>::bwp_type::c_basis == obs::dim::currency);
using dim2 = wrap_unit<std::ratio<1>, bpu_node<t2>>;
using d2 = dim2::dim_type; /* t^(-1/2) */
REQUIRE(unused_same<d2::front_type, t2>());
REQUIRE(unused_same<obs::lookup_bpu<d2, 0>::power_unit_type, t2>());
static_assert(obs::native_lo_bwp_of<d2>::bwp_type::c_index == 0);
static_assert(obs::native_lo_bwp_of<d2>::bwp_type::c_basis == obs::dim::time);
using dim3 = wrap_unit<std::ratio<1>, bpu_node<t1, bpu_node<t2>>>;
using d3 = dim3::dim_type; /* ccy.t^(-1/2) */
REQUIRE(unused_same<obs::lookup_bpu<d3, 0>::power_unit_type, t1>());
{
using type = obs::lookup_bpu<d3, 1>::power_unit_type;
//std::cerr << "obs::power_unit_of<d3,1>::power_unit_type" << xtag("type", reflect::type_name<type>()) << std::endl;
REQUIRE(unused_same<type, t2>());
}
#ifdef NOT_USING
static_assert(obs::lo_basis_elt_of<d3>::c_lo_basis == t2::c_basis);
#endif
/* lowest is in pos 1, beacuse t2=time before t1=currency */
static_assert(obs::native_lo_bwp_of<d3>::bwp_type::c_index == 1);
static_assert(unused_same<obs::without_elt<d3, 0>::dim_type, d2>());
//using type = obs::without_elt<d3, 1>::dim_type;
//std::cerr << "obs::without_elt<d3,1>::dim_type" << xtag("type", reflect::type_name<type>()) << std::endl;
static_assert(unused_same<obs::without_elt<d3, 1>::dim_type, d1>());
using d3b = wrap_unit<std::ratio<1>,
bpu_node<t2, bpu_node<t1>>>::dim_type; /* t^(-1/2).ccy */
//using d3b = obs::dimension_impl<t2, obs::dimension_impl<t1>>; /* t^(-1/2).ccy */
REQUIRE(unused_same<obs::lookup_bpu<d3b, 0>::power_unit_type, t2>());
REQUIRE(unused_same<obs::lookup_bpu<d3b, 1>::power_unit_type, t1>());
/* lowest is in pos 0 */
static_assert(obs::native_lo_bwp_of<d3b>::bwp_type::c_index == 0);
static_assert(unused_same<obs::without_elt<d3b, 0>::dim_type, d1>());
static_assert(unused_same<obs::without_elt<d3b, 1>::dim_type, d2>());
static_assert(unused_same<obs::canonical_t<d3>, obs::canonical_t<d3b>>());
log && log(xtag("d1.abbrev", unit_abbrev_v<dim1>.c_str()));
log && log(xtag("d2.abbrev", unit_abbrev_v<dim2>.c_str()));
log && log(xtag("d3.abbrev", unit_abbrev_v<dim3>.c_str()));
}
TEST_CASE("dimension2", "[dimension2]") {
constexpr bool c_debug_flag = true;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension2"));
//log && log("(A)", xtag("foo", foo));
using di = di_cartesian_product<typename gram::dim_type, typename second::dim_type>;
log && log(xtag("di", Reflect::require<di>()->canonical_name()));
log && log(xtag("di::outer_scalefactor_type", Reflect::require<di::outer_scalefactor_type>()->canonical_name()));
log && log(xtag("di::bpu_list_type", Reflect::require<di::bpu_list_type>()->canonical_name()));
using u1 = unit_cartesian_product_t<gram, second>;
log && log(xtag("u1", Reflect::require<u1>()->canonical_name()));
log && log(xtag("u1", ccs(unit_abbrev_v<u1>.value_)));
} /*TEST_CASE(dimension2)*/
TEST_CASE("dimension3", "[dimension3]") {
constexpr bool c_debug_flag = true;
// can get bits from /dev/random by uncommenting the 2nd line below
//uint64_t seed = xxx;
//rng::Seed<xoshio256ss> seed;
//auto rng = xo::rng::xoshiro256ss(seed);
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension3"));
//log && log("(A)", xtag("foo", foo));
using u1 = unit_invert_t<second>;
log && log(xtag("second^-1", Reflect::require<u1>()->canonical_name()));
log && log(xtag("u1", unit_abbrev_v<u1>.c_str()));
REQUIRE(strcmp(unit_abbrev_v<u1>.c_str(), "s^-1") == 0);
using u2 = second;
log && log(xtag("second", Reflect::require<u2>()->canonical_name()));
log && log(xtag("u2", unit_abbrev_v<u2>.c_str()));
using u1u2 = unit_cartesian_product_t<u1, u2>;
log && log(xtag("u1u2", Reflect::require<u1u2>()->canonical_name()));
#ifdef NOT_USING
using di1 = d1::dim_type;
using di2 = d2::dim_type;
using di1di2 = di_cartesian_product<di1,di2>::type;
log && log(xtag("di1di2", Reflect::require<di1di2>()->canonical_name()));
#endif
using f1 = u1::dim_type::front_type;
using r1 = u1::dim_type::rest_type;
using tmp = di_cartesian_product1<f1, r1, u2::dim_type>;
log && log(xtag("f1", Reflect::require<f1>()->canonical_name()));
log && log(xtag("r1", Reflect::require<r1>()->canonical_name()));
log && log(xtag("(f1.r1).outer_scalefactor_type", Reflect::require<tmp::outer_scalefactor_type>()->canonical_name()));
log && log(xtag("(f1.r1).bpu_list_type", Reflect::require<tmp::bpu_list_type>()->canonical_name()));
using tmp2 = bpu_cartesian_product<f1, u2::dim_type>;
log && log(xtag("(f1.u2).outer_scalefactor_type", Reflect::require<tmp2::outer_scalefactor_type>()->canonical_name()));
log && log(xtag("(f1.u2).bpu_list_type", Reflect::require<tmp2::bpu_list_type>()->canonical_name()));
using f2 = u2::dim_type::front_type;
log && log(xtag("f2", Reflect::require<f2>()->canonical_name()));
using tmp3 = bpu_cartesian_product_helper<f1, f2, void>;
log && log(xtag("(f1.f2).outer_scalefactor_type", Reflect::require<tmp3::outer_scalefactor_type>()->canonical_name()));
log && log(xtag("(f1.f2).bpu_list_type", Reflect::require<tmp3::bpu_list_type>()->canonical_name()));
} /*TEST_CASE(dimension3)*/
} /*namespace ut*/
} /*namespace xo*/
/* end dimension.test.cpp */

View file

@ -0,0 +1,6 @@
/* file unit_utest_main.cpp */
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
/* end unit_utest_main.cpp */