build: move unit+quantity feature from xo-observable -> xo-unit
This commit is contained in:
parent
2f911bc109
commit
3c1f8389c7
20 changed files with 6324 additions and 0 deletions
56
CMakeLists.txt
Normal file
56
CMakeLists.txt
Normal 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
|
||||
14
cmake/xo-bootstrap-macros.cmake
Normal file
14
cmake/xo-bootstrap-macros.cmake
Normal 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)
|
||||
17
cmake/xo_unitConfig.cmake.in
Normal file
17
cmake/xo_unitConfig.cmake.in
Normal 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
60
docs/CMakeLists.txt
Normal 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
2814
docs/Doxyfile.in
Normal file
File diff suppressed because it is too large
Load diff
86
include/xo/unit/basis_unit.hpp
Normal file
86
include/xo/unit/basis_unit.hpp
Normal 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 **/
|
||||
89
include/xo/unit/dimension_concept.hpp
Normal file
89
include/xo/unit/dimension_concept.hpp
Normal 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 */
|
||||
516
include/xo/unit/dimension_impl.hpp
Normal file
516
include/xo/unit/dimension_impl.hpp
Normal 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 */
|
||||
259
include/xo/unit/native_bpu.hpp
Normal file
259
include/xo/unit/native_bpu.hpp
Normal 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 */
|
||||
42
include/xo/unit/native_bpu_concept.hpp
Normal file
42
include/xo/unit/native_bpu_concept.hpp
Normal 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 */
|
||||
501
include/xo/unit/quantity.hpp
Normal file
501
include/xo/unit/quantity.hpp
Normal 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 */
|
||||
21
include/xo/unit/quantity_concept.hpp
Normal file
21
include/xo/unit/quantity_concept.hpp
Normal 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*/
|
||||
13
include/xo/unit/ratio_concept.hpp
Normal file
13
include/xo/unit/ratio_concept.hpp
Normal 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*/
|
||||
242
include/xo/unit/ratio_util.hpp
Normal file
242
include/xo/unit/ratio_util.hpp
Normal 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 */
|
||||
170
include/xo/unit/stringliteral.hpp
Normal file
170
include/xo/unit/stringliteral.hpp
Normal 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
394
include/xo/unit/unit.hpp
Normal 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
25
utest/CMakeLists.txt
Normal 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
653
utest/quantity.test.cpp
Normal 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
346
utest/unit.test.cpp
Normal 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 */
|
||||
6
utest/unit_utest_main.cpp
Normal file
6
utest/unit_utest_main.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/* file unit_utest_main.cpp */
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
/* end unit_utest_main.cpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue