Add 'xo-unit/' from commit 'b531e382c2'

git-subtree-dir: xo-unit
git-subtree-mainline: e9ee6992ca
git-subtree-split: b531e382c2
This commit is contained in:
Roland Conybeare 2025-05-10 21:29:43 -05:00
commit d1fa15f248
105 changed files with 11790 additions and 0 deletions

51
xo-unit/.github/workflows/nix-main.yml vendored Normal file
View file

@ -0,0 +1,51 @@
# Workflow to build xo-unit using custom docker container;
# container provides nix support
#
# NOTES
# 1. GIT_TOKEN granted automatically by github.
# has read permission on public resources + read/write permission on this repo
#
# 2. container built from [[https:github.com:rconybea/docker-nix-builder]]
# Includes dependencies:
# - nix
# - compiler toolchain: gcc, binutils, bash, etc
# - git
# - cmake
# - catch2
# - pybind11 + python
# - libwebsockets
# - jsoncpp
#
name: xo-unit nix builder
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
BUILD_TYPE: Release
jobs:
build_job:
name: xo-unit nix build on docker-nix-builder
runs-on: ubuntu-latest
container:
# custom docker image. see github.com:rconybea/docker-nix-builder for definition
image: ghcr.io/rconybea/docker-nix-builder:v1
steps:
# not using usual checkout actions: they don't work out-of-the-box from within a container
- name: xo-unit
run: |
echo "::group::clone xo-unit repo"
mkdir -p repo
GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unit.git repo/xo-unit
echo "::endgroup"
echo "::group::build xo-unit with nix"
export NIXPKGS_ALLOW_UNFREE=1
(cd repo/xo-unit && nix build --impure -L --print-build-logs .#xo-unit && tree ./result)
echo "::endgroup"

View file

@ -0,0 +1,252 @@
name: build xo-unit + dependencies
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- name: checkout source
uses: actions/checkout@v3
- name: Install dependencies
run: |
# install catch2, doxygen. see
# [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]]
echo "::group::install catch2"
sudo apt-get install -y catch2
echo "::endgroup"
echo "::group::install doxygen"
sudo apt-get install -y doxygen
echo "::endgroup"
echo "::group::install sphinx"
sudo apt-get install -y python3-sphinx
echo "::endgroup"
echo "::group::install sphinx readthedocs theme"
sudo apt-get install -y python3-sphinx-rtd-theme
echo "::endgroup"
#echo "::group::install pybind11"
#sudo apt-get install -y pybind11-dev
#echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-cmake
uses: actions/checkout@v3
with:
repository: Rconybea/xo-cmake
path: repo/xo-cmake
- name: build xo-cmake
run: |
XONAME=xo-cmake
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-indentlog
uses: actions/checkout@v3
with:
repository: Rconybea/indentlog
path: repo/xo-indentlog
- name: build xo-indentlog
run: |
XONAME=xo-indentlog
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-flatstring
uses: actions/checkout@v3
with:
repository: Rconybea/xo-flatstring
path: repo/xo-flatstring
- name: build xo-flatstring
run: |
XONAME=xo-flatstring
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-randomgen
uses: actions/checkout@v3
with:
repository: Rconybea/randomgen
path: repo/xo-randomgen
- name: build xo-randomgen
run: |
XONAME=xo-randomgen
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: clone xo-ratio
uses: actions/checkout@v3
with:
repository: Rconybea/xo-ratio
path: repo/xo-ratio
- name: build xo-ratio
run: |
XONAME=xo-ratio
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: build self (xo-unit)
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: |
XONAME=xo-unit
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
(cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}})

View file

@ -0,0 +1,248 @@
name: XO unit builder
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
container:
# custom docker image. see github.com:rconybea/docker-xo-builder for definition
image: ghcr.io/rconybea/docker-xo-builder:v1
steps:
- name: xo-cmake
run: |
# treat github.com as known host to prevent shtoopid SSL errors
mkdir -p ~/.ssh
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
cat ~/.ssh/known_hosts
git config --global http.sslVerify false
XO_NAME=xo-cmake
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/xo-cmake.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-indentlog
run: |
XO_NAME=xo-indentlog
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/indentlog.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-flatstring
run: |
XO_NAME=xo-flatstring
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/${XO_NAME}.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-randomgen
run: |
XO_NAME=xo-randomgen
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/randomgen.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-ratio
run: |
XO_NAME=xo-ratio
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/${XO_NAME}.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: xo-unit
run: |
XO_NAME=xo-unit
XO_SRC=repo/${XO_NAME}
XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME}
PREFIX=${{github.workspace}}/local
XO_REPO=https://github.com/rconybea/${XO_NAME}.git
mkdir -p ${XO_SRC}
mkdir -p ${XO_BUILDDIR}
echo "::group::clone ${XO_NAME}"
export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no'
export GIT_SSL_NOVERIFY=true
git clone ${XO_REPO} ${XO_SRC}
echo "::endgroup"
echo "::group::configure ${XO_NAME}"
cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC}
echo "::endgroup"
echo "::group::compile ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -j
echo "::endgroup"
echo "::group::test ${XO_NAME}"
cmake --build ${XO_BUILDDIR} -- test
echo "::endgroup"
echo "::group::local install ${XO_NAME}"
cmake --install ${XO_BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"

8
xo-unit/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# emacs projectile config
.projectile
# clangd working space (see emacs+lsp)
.cache
# typical cmake build directory (source-tree-nephew)
.build*
# symlink to builddir/compile_commands.json; should be set manually in dev sandbox
compile_commands.json

43
xo-unit/CMakeLists.txt Normal file
View file

@ -0,0 +1,43 @@
# xo-unit/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(xo_unit VERSION 1.0)
include(GNUInstallDirs)
include(cmake/xo-bootstrap-macros.cmake)
xo_cxx_toplevel_options3()
# ----------------------------------------------------------------
# c++ settings
# one-time project-specific c++ flags. usually empty
set(PROJECT_CXX_FLAGS "-Wstringop-overread")
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2")
add_definitions(${PROJECT_CXX_FLAGS})
# ----------------------------------------------------------------
add_subdirectory(example)
add_subdirectory(utest)
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)
# ----------------------------------------------------------------
# docs targets depend on all the other library/utest targets
#
add_subdirectory(docs)
# ----------------------------------------------------------------
# dependencies
# for some reason, this doesn't reliably bring in xo_flatstring dependency
xo_headeronly_dependency(${SELF_LIB} xo_ratio)
xo_headeronly_dependency(${SELF_LIB} xo_flatstring)
# etc..
# end CMakeLists.txt

29
xo-unit/LICENSE Normal file
View file

@ -0,0 +1,29 @@
Copyright (c) 2024 Roland Conybeare <git3ub@nym.hush.com>, All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of
external contributions to this project including patches, pull requests, etc.

114
xo-unit/README.md Normal file
View file

@ -0,0 +1,114 @@
# unit library
Provides compile-time dimension checking and scaling.
Similar in spirit to `boost::units`, but:
1. streamlined: assumes modern (c++20) support
2. supports fractional dimensions (rational powers)
## Documentation
- xo-unit documentation [under construction]: [documentation](https://rconybea.github.io/web/xo-unit/html/index.html)
- unit test coverage here: [coverage](https://rconybea.github.io/web/xo-unit/ccov/html/index.html)
## Example
```
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
namespace q = xo::qty::qty;
namespace u = xo::qty::u;
constexpr auto t = q::minutes(2);
constexpr auto d = q::kilometers(2.5);
constexpr auto t2 = t*t; // unit will be min^-2
constexpr auto a = d / t2; // unit will be km.min^-2
// convert to m.s^-2
constexpr quantity<u::meter / (u::second * u::second)> a2 = a;
//constexpr quantity<u::meter> a3 = a; // dimension mismatch, will not compile
// get dimensionless scale value
double x = a2.scale();
```
## Getting Started
See [full install instructions](https://rconybea.github.io/web/xo-unit/html/install.html) for other installation strategies.
### build + install `xo-cmake` dependency
- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake)
Installs a few cmake ingredients, along with build assistant `xo-build` for XO projects such as this one.
### build + install other XO dependencies
```
$ xo-build --clone --configure --build --install xo-flatstring
$ xo-build --clone --configure --build --install xo-ratio
```
Note: can use `-n` to dry-run here
### copy `xo-unit` repository locally
```
$ xo-build --clone xo-unit
```
or equivalently
```
$ git clone https://github.com/rconybea/xo-unit
```
### build + install `xo-unit`
```
$ xo-build --configure --build --install xo-unit
```
or equivalently:
```
$ PREFIX=/usr/local # or wherever you prefer
$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-unit -B xo-unit/.build
$ cmake --build xo-unit/.build -j
$ cmake --install xo-unit/.build
```
### build documentation
```
$ cd xo-unit
$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} --build .build -- sphinx
```
When this completes, can point local browser to `xo-unit/.build/docs/sphinx/index.html`.
### build for unit test coverage
(Note that unit tests involve additional dependencies):
```
$ xo-build --clone --configure --build --install xo-indentlog
$ xo-build --clone --configure --build --install xo-randomgen
```
```
$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX -DENABLE_TESTING=1 xo-unit/.build-ccov
$ cmake --build xo-unit/.build-ccov
```
run coverage-enabled unit tests:
```
$ cmake --build xo-unit/.build-ccov -- test
```
generate html+text coverage report:
```
$ xo-unit/.build-ccov/gen-ccov
```
To see coverage, can point local browser to `xo-unit/.build-ccov/ccov/html/index.html`
### LSP support
```
$ cd xo-unit
$ ln -s .build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree
```

20
xo-unit/cmake/gen-ccov.in Normal file
View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
srcdir=@PROJECT_SOURCE_DIR@
builddir=@PROJECT_BINARY_DIR@
lcov=@LCOV_EXECUTABLE@
genhtml=@GENHTML_EXECUTABLE@
if [[ $lcov == "LCOV_EXECUTABLE-NOTFOUND" ]]; then
echo "gen-ccov: lcov executable not found"
exit 1
fi
if [[ $genhtml == "GENHTML_EXECUTABLE-NOTFOUND" ]]; then
echo "gen-ccov: genhtml executable not found"
exit 1
fi
mkdir $builddir/ccov
$srcdir/cmake/lcov-harness $srcdir $builddir $builddir/ccov/out $lcov $genhtml

114
xo-unit/cmake/lcov-harness Executable file
View file

@ -0,0 +1,114 @@
#!/usr/bin/env bash
srcdir=$1
builddir=$2
outputstem=$3
lcov=$4
genhtml=$5
if [[ -z "${srcdir}" ]]; then
echo "lcov-harness: expected non-empty srcdir"
exit 1
fi
if [[ -z ${builddir} ]]; then
echo "lcov-harness: expected non-empty builddir"
exit 1
fi
if [[ -z ${outputstem} ]]; then
echo "lcov-harness: expected non-empty outputstem"
exit 1
fi
if [[ -z ${lcov} ]]; then
echo "lcov-harness: exepcted non-empty lcov"
exit 1
fi
if [[ -z ${genhtml} ]]; then
echo "lcov-harness: expected non-empty genhtml"
exit 1
fi
# directory stems for location of {.gcda, gcno} coverage information,
#
# if we have source tree:
#
# ${srcdir}
# +- foo
# | \- foo.cpp
# \- bar
# \- quux
# +- quux.cpp
# \- quux_main.cpp
#
# then we expect build tree:
#
# ${builddir}
# +- foo
# | \- CMakeFiles
# | \- foo_target.dir
# | +- foo.cpp.gcda
# | \- foo.cpp.gcno
# +- bar
# \- quux
# \- CMakeFiles
# \- target4quux.dir
# +- quux.cpp.gcda
# +- quux.cpp.gcno
# +- quux_main.cpp.gcda
# \- quux_main.cpp.gcno
#
# in which case will have cmd_body:
#
# ${primarydirs}
# ./foo/CMakeFiles/foo_target.dir
# ./bar/quux/CMakeFiles/target4quux.dir
#
# here foo_target, quux_target are whatever build is using for corresponding cmake target names.
#
# We want to invoke lcov like:
#
# lcov --capture \
# --output ${builddir}/ccov \
# --exclude /utest/ \
# --base-directory ${srcdir}/foo --directory ${builddir}/foo/CMakeFiles/foo_target.dir \
# --base-directory ${srcdir}/bar/quux --directory ${builddir}/bar/quux/CMakeFiles/target4quux.dir
#
primarydirs=$(cd ${builddir} && find -name '*.gcno' \
| xargs --replace=xx dirname xx \
| uniq \
| sed -e 's:^\./::')
#echo "primarydirs=${primarydirs}"
cmd="${lcov} --output ${outputstem}.info --capture --ignore-errors source"
for bdir in ${primarydirs}; do
sdir=$(dirname $(dirname ${bdir}))
cmd="${cmd} --base-directory ${srcdir}/${sdir} --directory ${builddir}/${bdir}"
done
#echo cmd=${cmd}
set -x
# capture
${cmd}
# keep only files with paths under source tree
# (don't want coverage for external libraries such as libstdc++ etc)
${lcov} --extract ${outputstem}.info "${srcdir}/*" --output ${outputstem}2.info
# remove unit test dirs
# (we're interested in coverage of our installed code, not of the unit tests that exercise it)
${lcov} --remove ${outputstem}2.info '*/utest/*' --output ${outputstem}3.info
# generate .html tree
mkdir -p ${builddir}/ccov/html
${genhtml} --ignore-errors source --show-details --prefix ${srcdir} --output-directory ${builddir}/ccov/html ${outputstem}3.info
# also send report to stdout
${lcov} --list ${outputstem}3.info

View file

@ -0,0 +1,35 @@
# ----------------------------------------------------------------
# for example:
# $ PREFIX=/usr/local # for example
# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build
#
# will get
# CMAKE_MODULE_PATH
# from xo-cmake-config --cmake-module-path
#
# and expect .cmake macros in
# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake
# ----------------------------------------------------------------
find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED)
if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND")
message(FATAL "could not find xo-cmake-config executable")
endif()
message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}")
if (NOT XO_SUBMODULE_BUILD)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
# default to typical install location for xo-project-macros
execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
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_cxx)
xo_cxx_bootstrap_message()

View file

@ -0,0 +1,11 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(xo_ratio)
find_dependency(indentlog)
#find_dependency(printjson)
#find_dependency(callback)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

View file

@ -0,0 +1,17 @@
# xo-unit/docs/CMakeLists.txt
xo_doxygen_collect_deps()
xo_docdir_doxygen_config()
xo_docdir_sphinx_config(
index.rst examples.rst glossary.rst install.rst implementation.rst
quantity-reference.rst quantity-class.rst quantity-factoryfunctions.rst quantity-unitvars.rst quantity-source-code.rst
xquantity-reference.rst xquantity-class.rst xquantity-source-code.rst
scaled-unit-reference.rst scaled-unit-class.rst scaled-unit-constants.rst
natural-unit-class.rst
bpu-class.rst
bu-store-class.rst basis-unit-reference.rst
basis-unit-class.rst basis-unit-constants.rst
dimension-enum.rst
development.rst
ubuntu-github-workflow.rst
)

70
xo-unit/docs/README Normal file
View file

@ -0,0 +1,70 @@
build
+-----------------------------------------------+
| cmake |
| CMakeLists.txt |
| $PREFIX/share/cmake/xo_macros/xo_cxx.cmake |
+-----------------------------------------------+
|
| +----------------------+
+------------------------------------------------->| .build/docs/Doxyfile |
| +----------------------+
| |
| /------------/
| |
| v
| +---------------------------------------+ +-----------------+
+---->| doxygen |--->| .build/docs/dox |
| | $PREFIX/share/xo-macros/Doxyfile.in | | +- html/ |
| +---------------------------------------+ | +- xml/ |
| +-----------------+
| |
| /------------/
| |
| v
| +---------------------------------------+ +--------------------+
\---->| sphinx |--->| .build/docs/sphinx |
| +- conf.py | | +- html/ |
| +- _static/ | +--------------------+
| +- *.rst |
+---------------------------------------+
files
README this file
CMakeLists.txt build entry point
conf.py sphinx config
_static static files for sphinx
map
index.rst
+- install.rst
+- examples.rst
+- unit-quantities.rst
+- classes.rst
+- glossary.rst
...
examples
.. doxygenclass:: ${c++ class name}
:project:
:path:
:members:
:protected-members:
:private-members:
:undoc-members:
:member-groups:
:members-only:
:outline:
:no-link:
:allow-dot-graphs:
.. doxygendefine:: ${c preprocessor define}
.. doxygenconcept:: ${c++ concept definition}
.. doxygenenum:: ${c++ enum definition}
.. doxygenfunction:: ${c++ function name}

1
xo-unit/docs/_static/README vendored Normal file
View file

@ -0,0 +1 @@
add any static {.html, .js, ..} files for sphinx to pickup here

BIN
xo-unit/docs/_static/img/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

View file

@ -0,0 +1,76 @@
.. _basis-unit-class:
Basis Unit
==========
A unit representing a fixed multiple of a native dimension.
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
|cBLU basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
Introduction
------------
A :code:`basis_unit` represents a unit belonging to a single native dimension.
For example :code:`bu::meter` representing a distance of 1 meter.
.. code-block:: cpp
#include <xo/unit/basis_unit.hpp>
.. uml::
:scale: 99%
:align: center
:caption: basis unit representing 1 minute
object bu1<<basis_unit>>
bu1 : native_dim = time
bu1 : scalefactor = 60
:code:`basis_unit` is intended as an implementation-level abstraction.
Application code will normally interact with the more-capable :code:`scaled_unit`
instead of :code:`basis_unit`.
Class
-----
.. doxygenclass:: xo::qty::basis_unit
Member Variables
----------------
.. doxygengroup:: basis-unit-instance-vars
Constructors
------------
.. doxygengroup:: basis-unit-constructors
Access Methods
--------------
.. doxygengroup:: basis-unit-access-methods
Comparison
----------
.. doxygengroup:: basis-unit-comparison-support

View file

@ -0,0 +1,60 @@
.. _basis-unit-constants:
Basis Unit Constants
====================
Relative scalefactors for each built-in unit.
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
|cBLU basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
Introduction
------------
Constants in the :code:`xo::qty::detail::bu` namespace represent
low-level building blocks for specifying units.
Relative scalefactors for each unit are chosen here.
Application code will not typically use these values directtly;
instead it's expected to use units from the :code:`xo::qty::u` namespace.
Those units are implemented on top of the basis units described here.
.. code-block:: cpp
#include <xo/unit/basis_unit.hpp>
using xo::qty::detail::bu;
Mass Units
----------
.. doxygengroup:: basis-unit-mass-units
Distance Units
--------------
.. doxygengroup:: basis-unit-distance-units
Time Units
----------
.. doxygengroup:: basis-unit-time-units

View file

@ -0,0 +1,31 @@
.. _basis-unit-reference:
Basis Unit Reference
====================
Built-in named units for each native dimension
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
|cBLU basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
.. toctree::
:maxdepth: 1
Basis Unit Class <basis-unit-class>
basis-unit-constants

View file

@ -0,0 +1,98 @@
.. _bpu-class:
BPU
===
A rational (usually integral) power of a single basis unit
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
|cBLU bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
Introduction
------------
.. code-block:: cpp
#include <xo/unit/bpu.hpp>
A :code:`xo::qty::bpu` (aka "basis power unit") represents a rational (usually integer) power of a :doc:`basis-unit-class`.
For example:
.. uml::
:caption: representation for cubic millimeters
:scale: 99%
:align: center
object mm3<<bpu>>
mm3 : native_dim = dim::distance
mm3 : scalefactor = 1/1000
mm3 : power = 3/1
.. uml::
:caption: representation for annual (365-day) volatility
:scale: 99%
:align: center
object vol<<bpu>>
vol : native_dim = dim::time
vol : scalefactor = 365*24*3600
vol : power = -1/2
:code:`bpu` is intended as an implementation-level abstraction.
Application code will normally interact with the more-general :code:`scaled_unit`
instead of :code:`bpu`.
Class
-----
.. doxygenclass:: xo::qty::bpu
Member Variables
----------------
.. doxygengroup:: bpu-instance-vars
Constructors
------------
.. doxygengroup:: bpu-ctors
Access Methods
--------------
.. doxygengroup:: bpu-access-methods
Other Methods
-------------
.. doxygengroup:: bpu-methods
Comparison
----------
.. doxygengroup:: bpu-comparison
Details
-------
.. doxygengroup:: bpu-abbrev-helpers

View file

@ -0,0 +1,129 @@
.. _bu-store-class:
Basis Unit Store
================
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
|cBLU bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
.. code-block:: cpp
#include <xo/unit/bu_store.hpp>
namespace bu = xo::qty::detail::bu;
A :code:`xo::qty::bu_store` is a small, constexpr, key-value store associating
abbreviations with basis units. To satisfy the constexpr requirement,
all unit abbreviations are irrevocably established from ``bu_store``'s constructor.
The constant ``bu_abbrev_store`` contains a single instance of ``bu_store``,
recording all built-in units along with their associated abbreviations
.. uml::
:caption: basis-unit store
:scale: 99%
:align: center
map mass_table<<bu_dim_store>> {
bu::milligram => "mg"
bu::gram => "g"
bu::kilogram => "kg"
}
map distance_table<<bu_dim_store>> {
bu::millimeter => "mm"
bu::meter => "m"
bu::kilometer => "km"
}
map time_table<<bu_dim_store>> {
bu::millisecond => "ms"
bu::second => "s"
bu::minute => "min"
bu::hour => "hr"
}
object bu_abbrev_store<<bu_store>>
bu_abbrev_store : bu_abbrev_vv[dim::mass] = mass_table
bu_abbrev_store : bu_abbrev_vv[dim::distance] = distance_table
bu_abbrev_store : bu_abbrev_vv[dim::time] = time_table
bu_abbrev_store o-- mass_table
bu_abbrev_store o-- distance_table
bu_abbrev_store o-- time_table
This class exists to support the implementation of ``natural_unit::abbrev()``.
Application code is not expected to interact directly with it.
Class
-----
.. doxygenclass:: xo::qty::detail::bu_store
For example, this would be possible:
.. code-block:: cpp
#include <xo/unit/bu_store.hpp>
namespace bu = using xo::qty::detail::bu;
using xo::qty::detail::bu_store;
using xo::qty::dim;
using xo::flatstring;
constexpr bu_store store;
static_assert(store.bu_abbrev(bu::minute) == flatstring("min"));
static_assert(store.bu_abbrev(bu::microgram) == flatstring("ug"));
.. doxygengroup:: bu-store-constructors
.. doxygengroup:: bu-store-access-methods
.. doxygengroup:: bu-store-implementation-methods
.. doxygenclass:: xo::qty::detail::bu_dim_store
.. doxygengroup:: bu-dim-store-type-traits
.. doxygengroup:: bu-dim-store-instance-vars
Constants
---------
Provides dictionary of unit abbreviations
Application code is not expected to interact directly with ``bu_abbrev_store``.
.. doxygenvariable:: xo::qty::bu_abbrev_store
Functions
---------
.. doxygenfunction:: xo::qty::bu_abbrev
For example:
.. code-block:: cpp
#include <xo/unit/bu_store.hpp>
namespace bu = xo::qty::bu;
using xo::qty::bu_abbrev;
using xo::flatstring;
static_assert(bu_abbrev(bu::kilogram) == xo::flatstring("kg"));

39
xo-unit/docs/conf.py Normal file
View file

@ -0,0 +1,39 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'xo unit documentation'
copyright = '2024, Roland Conybeare'
author = 'Roland Conybeare'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
#extensions = []
extensions = [ "breathe",
"sphinx.ext.mathjax", # inline math
"sphinx.ext.autodoc", # generate info from docstrings
"sphinxcontrib.ditaa", # diagrams-through-ascii-art
"sphinxcontrib.plantuml" # text -> uml diagrams
]
# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in
# match project name in Doxyfile.in
breathe_default_project = "xodoxxml"
templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
pygments_style = 'sphinx'
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
#html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
html_favicon = '_static/img/favicon.ico'

View file

@ -0,0 +1,18 @@
.. _development:
Development
===========
Miscellaneous development notes for *xo-unit*.
Addding a Basis Unit
--------------------
To add a basis unit for an existing dimension:
#. add unit definition to the ``xo::qty::bu`` namespace in ``include/xo/unit/basis_unit.hpp``
#. add call to ``bu_store::bu_establish_abbrev()`` from ``bu_store::bu_store``.
#. add ``natural_unit`` definition to ``xo::qty::nu`` namespace in ``include/xo/unit/natural_unit.hpp``
#. add ``scaled_unit`` definition to ``xo::qty::u`` namespace in ``include/xo/unit/scaled_unit.hpp``.
#. add unit quantity to ``xo::qty::qty`` namespace in ``include/xo/unit/quantity.hpp``
#. add factory function to ``xo::qty::qty`` namespace in ``include/xo/unit/quantity.hpp``

View file

@ -0,0 +1,66 @@
.. _dimension:
Native Dimension
================
An abstract dimension; distinct native dimensions are orthogonal
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
|cBLU dimension |
+--------------------------------+
Introduction
------------
Identifies an abstract dimension, for example *mass* or *time*.
.. code-block:: cpp
#include <xo/unit/dimension.hpp>
For example can use this enum to index basis members of a :doc:`scaled_unit<scaled-unit-class>` instance:
.. code-block:: cpp
:emphasize-lines: 7-8
#include <xo/unit/quantity.hpp>
using namespace xo::qty;
auto x = q::kilometers(100) / q::hours(1);
auto bpu1 = x.lookup_dim(dim::time);
auto bpu2 = x.lookup_dim(dim::distance);
Enum
----
.. doxygenenum:: xo::qty::dimension
Constants
---------
.. doxygenvariable:: xo::qty::n_dim
Functions
---------
.. doxygenfunction:: xo::qty::dim2str

366
xo-unit/docs/examples.rst Normal file
View file

@ -0,0 +1,366 @@
.. _examples:
.. toctree
:maxdepth: 2
Examples
========
Compile-time unit inference
---------------------------
See ``xo-unit/examples/ex1`` for code below.
Units propagate through familiar arithmetic expressions:
.. code-block:: cpp
:linenos:
:emphasize-lines: 14-15
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
namespace q = xo::qty::qty;
namespace su = xo::qty::su;
using namespace std;
constexpr auto t = q::minutes(2);
constexpr auto d = q::kilometers(2.5);
constexpr auto t2 = t*t;
constexpr auto a = d / (t*t);
cerr << "t: " << t << ", d: " << d
<< ", t^2: " << t2
<< ", d.t^-2: " << a
<< endl;
}
with output:
.. code-block::
t: 2min, d: 2.5km, t^2: 4min^2, d.t^-2: 0.625km.min^-2
We can use static asserts to prove that units are being computed at compile-time
.. code-block:: cpp
:linenos:
static_assert(std::same_as<decltype(t)::repr_type, int>);
static_assert(sizeof(t) == sizeof(double));
static_assert(t.scale() == 2);
static_assert(t.abbrev() == flatstring("min"));
static_assert(std::same_as<decltype(d)::repr_type, double>);
static_assert(sizeof(d) == sizeof(double));
static_assert(d.scale() == 2.5);
static_assert(d.abbrev() == flatstring("km"));
static_assert(std::same_as<decltype(t2)::repr_type, int>);
static_assert(sizeof(t2) == sizeof(double));
static_assert(t2.scale() == 4);
static_assert(t2.abbrev() == flatstring("min^2"));
static_assert(std::same_as<decltype(a)::repr_type, double>);
static_assert(sizeof(a) == sizeof(double));
static_assert(a.scale() == 0.625);
static_assert(a.abbrev() == flatstring("km.min^-2"));
Remarks:
- ``xo::qty::quantity`` performs unit consistency checking and propagation at compile time. Runtime space/time overhead is zero.
- Units are sticky: since we expressed ``t`` in minutes, ``(t*t)`` and ``d/(t*t)`` also use minutes.
- Unit ordering is sticky. Distance appears on the left of time in printed value of ``d/(t*t)``
because it was on the left-hand side of ``operator/``
- ``xo-unit`` copies representation from the argument to factory functions ``q::minutes``, ``q::kilometers`` etc.
- Binary operators take representation from the 'most precise' argument, as prescribed by ``std::common_type_t``.
- Unit abbreviations (such as ``kg.min^-2`` above) are computed at compile time.
See ``xo::flatstring`` for constexpr string implementation.
- See ``xo::xquantity`` for parallel implementation that defers unit checking until runtime.
Explicit scale conversion
-------------------------
Can convert between compatible units explictly,
using:
1. ``xo::qty::with_units`` (template function)
2. ``quantity.rescale_ext`` (template method)
3. ``xo::qty::with_units_from`` (template function)
See ``xo-unit/examples/ex2`` for code below.
.. code-block:: cpp
:linenos:
:emphasize-lines: 10,13,16-17
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
namespace q = xo::qty::qty;
namespace u = xo::qty::u;
using xo::qty::with_units_from;
using xo::qty::with_units;
using xo::qty::quantity;
using xo::flatstring;
using namespace std;
constexpr auto t = q::minutes(2);
constexpr auto d = q::kilometers(2.5);
constexpr auto t2 = t*t;
constexpr auto a = d / (t*t);
cerr << "t: " << t << ", d: " << d
<< ", t^2: " << t2
<< ", d.t^-2: " << a
<< endl;
constexpr auto a2 = with_units<u::meter / (u::second * u::second)>(a);
static_assert(a2.abbrev() == flatstring("m.s^-2"));
cerr << "a2: " << a2 << endl;
constexpr auto a3 = a.rescale_ext<u::meter / (u::second * u::second)>();
static_assert(a3.abbrev() == flatstring("m.s^-2"));
cerr << "a3: " << a3 << endl;
constexpr auto au = q::meter / (q::second * q::second);
constexpr auto a4 = with_units_from(a, au);
static_assert(a4.abbrev() == flatstring("m.s^-2"));
cerr << "a4: " << a4 << endl;
}
with output:
.. code-block:: cpp
a2: 0.173611m.s^-2
a3: 0.173611m.s^-2
a4: 0.173611m.s^-2
Implicit Scale conversion
-------------------------
Another way to convert units is to assign to a variable
with desired units -- this works because the units are encoded
as part of the assigned variable's type.
See ``xo-unit/example/ex3`` for code below
.. code-block:: cpp
:linenos:
:emphasize-lines: 12-13
int
main () {
namespace q = xo::qty::qty;
namespace u = xo::qty::u;
using xo::qty::quantity;
constexpr quantity<u::second> t = q::minutes(2);
constexpr quantity<u::meter> d = q::kilometers(2.5);
constexpr auto t2 = t*t;
constexpr auto a = d / (t*t);
std::cerr << "t: " << t << ", d: " << d
<< ", d.t^-2: " << a
<< std::endl;
}
with output:
.. code-block::
t: 120s, d: 2500m, d.t^-2: 0.17e611m.s^-2
Remarks:
- Assignment to ``t`` converted to representation ``double``.
We could have instead used :code:`quantity<u::second, int>` to propagate
right-hand-side representation
Scale conversion and arithmetic
-------------------------------
When representing a particular quantity,
xo-unit uses at most one scale for each :term:`basis dimension` associated with the unit for that quantity.
When an arithmetic operator encounters basis units involving two different scales,
the operator will adopt the scale provided by the left-hand argument:
See ``xo-unit/example/ex4`` for code below
.. code-block:: cpp
:linenos:
:emphasize-lines: 11
#include "xo/unit/quantity.hpp"
#include <iostream>
int main() {
namespace q = xo::qty::qty;
auto t1 = qty::milliseconds(1);
auto t2 = qty::minutes(1);
auto p = t1 * t2;
std::cerr << "t1: " << t1 << ", t2: " << t2 << ", p: " << p << std::endl;
}
with output:
.. code-block::
t1: 1ms, t2: 1min, t1*t2: 60000ms^2
Dimensionless quantities unwrap implicitly
------------------------------------------
Conversely, compiler rejects attempt to implictly unwrap a dimensioned quantity.
See ``xo-unit/examples/ex4`` for code below.
.. code-block:: cpp
:linenos:
:emphasize-lines: 23,26
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
namespace q = xo::qty::qty;
auto t1 = q::milliseconds(1);
auto t2 = q::minutes(1);
auto r1 = t1 / with_repr<double>(t2);
static_assert(r1.is_dimensionless());
static_assert(!t2.is_dimensionless());
static_assert(std::same_as<static_cast<double>(r1), double>);
// r1_value: assignment compiles, since r1 dimensionless
double r1_value = r1;
// r2_value: bad assignment won't compile, 'cannot convert' error
//double r2_value = t2;
std::cerr << "t1: " << t1 << ", t2: " << t2 << ", t1/t2: " << r1_value << std::endl;
}
with output:
.. code-block::
t1: 1ms, t2: 1min, t1/t2: 1.66667e-05
Fractional dimension
--------------------
Fractional dimensions have limited support.
Prior to c++26 we can only support fractional dimensions with denominator 2,
such as powers -3/2, -1/2, +1/2, +3/2 etc.
c++26 will enable support for support fractional dimensions involving other ratios,
by offering constexpr ``::pow()``
See ``xo-unit/examples/ex6`` for code below
.. code-block:: cpp
:linenos:
:emphasize-lines: 15
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
namespace u = xo::unit::units;
namespace q = xo::unit::qty;
using namespace std;
/* 20% volatility over 250 days (approx number of trading days in one year) */
auto q1 = q::volatility_250d(0.2);
/* 10% volatility over 30 days */
auto q2 = q::volatility_30d(0.1);
auto sum = q1 + q2;
auto prod = q1 * q2;
static_assert(sum.abbrev() == flatstring("yr360^(-1/2)"));
static_assert(prod.abbrev() == flatstring("yr360^-1"));
std::cerr << "q1: " << q1 << std::endl;
std::cerr << "q2: " << q2 << std::endl;
std::cerr << "q1+q2: " << sum << std::endl;
std::cerr << "q1*q2: " << prod << std::endl;
}
with output:
.. code-block::
q1: 0.2yr360^(-1/2)
q2: 0.1mo^(-1/2)
q1+q2: 0.54641yr360^(-1/2)
q1*q2: 0.069282yr360^-1
Dynamic dimension
-----------------
If the dimension (or units) associated with a quantity are not known at compile-time,
use ``xo::qty::xquantity`` instead of ``xo::qty::quantity``.
See ``xo-unit/example/ex8`` for code below
.. code-block:: cpp
:linenos:
:emphasize-lines: 10-12
#include "xo/unit/xquantity.hpp"
#include "xo/unit/xquantity_iostream.hpp"
#include <iostream>
int
main () {
using namespace xo::qty;
namespace u = xo::qty::u;
xquantity qty1(7, u::foot);
xquantity qty2(6.0, u::inch);
xquantity qty3 = qty1 + qty2;
std::cerr << "qty1: " << qty1 << std::endl;
std::cerr << "qty2: " << qty2 << std::endl;
std::cerr << "qty3: " << qty3 << std::endl;
/* rescale to mm */
xquantity res = qty3.rescale(xo::qty::nu::millimeter);
/* 2286mm */
std::cerr << "res: " << res << std::endl;
}
Here ``u::foot`` and ``u::inch`` are literals,
but they could have been read from console input or another runtime-only context.

43
xo-unit/docs/glossary.rst Normal file
View file

@ -0,0 +1,43 @@
.. _glossary:
Glossary
--------
.. glossary::
dimension
dim
| Fundamental, orthogonal directions associated available for constructing units.
| For example *mass*, *length*, *time*.
| In *xo-unit* these are represented by the enum :doc:`xo::qty::dimension<dimension-enum>`.
basis unit
bu
| An implementation type representing a quantity (with associated scale) in the direction of a single :term:`dimension`.
| For example *milliseconds*, *seconds*, and *hours* stand for different basis units within the *time* dimension.
| In *xo-unit* these are represented by the template type :doc:`xo::qty::basis_unit<basis-unit-class>`.
basis power unit
bpu
| A rational power of a (single) basis unit.
| For example :math:`s^{-2}` for unit "per second squared" or :math:`yr^{-(1/2)}` for "per square-root of a year".
| In *xo-unit* these are represented by the template type :doc:`xo::qty::bpu<bpu-class>`
natural unit
nu
| A cartesian product of basis power units (bpus);
| For example :math:`kg.m.s^{-2}` or :math:`hr^{-(1/2)}`.
| In *xo-unit* these are represented by template type :doc:`xo::qty::natural_unit<natural-unit-class>`.
scaled unit
su
| A dimensionless multiple of a natural unit.
| Used as intermediate value when coalescing quantities involving different units.
| In *xo-unit* these are represented by template type :doc:`xo::qty::scaled_unit<scaled-unit-class>`.
XO
A set of integrated c++ libraries for complex event processing, with browser and python integration.
`xo documentation here`_
.. _xo documentation here: https://rconybea.github.io/web/sw/xo.html
.. toctree::

View file

@ -0,0 +1,314 @@
.. _implementation:
Components
==========
Library dependency tower for *xo-unit*:
.. ditaa::
+-----------------+
| xo_unit |
+-----------------+
| xo_ratio |
+-----------------+
| xo_flatstring |
+-----------------+
| xo_cmake |
+-----------------+
Install instructions :doc:`here<install>`
Abstraction tower for *xo-unit* components:
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
- :doc:`quantity<quantity-reference>`:
A quantity with unit checking and conversion done at compile-time
.. code-block:: cpp
#include "xo/unit/quantity.hpp"
auto q1 = xo::qty::qty::kilometers(7.5);
- :doc:`xquantity<xquantity-reference>`:
A quantity with unit checking and conversion done at run-time.
This is useful if unit information isn't known at compile time, for example
if reading units from console input.
.. code-block:: cpp
#include "xo/unit/xquantity.hpp"
xquantity qty1(7.5, xo::qty::u::foot)
- :doc:`scaled_unit<scaled-unit-class>`:
.. code-block:: cpp
#include "xo/unit/scaled_unit.hpp"
auto u = xo::qty::u::millimeter;
A unit involving zero or more dimensions, and associated conversion factor.
- can express result of arithmetic involving multiple scales,
by reporting an outer scalefactor
- a scaled unit is 'natural' if its outer scalefactor is 1.
- quantities are represented by associating a natural scaled_unit instance
- scaled_units are closed under multiplication and division.
- multiplication and division commit to a single :code:`basis_unit` for each
dimension.
- :doc:`natural_unit<natural-unit-class>`
.. code-block:: cpp
#include "xo/unit/natural_unit.hpp"
auto u = xo::qty::nu::millimeter;
A unit involving zero or more dimensions, and at most one scale per dimension.
A quantity instance is always represented as a dimensionless multiple
of a natural unit
- natural_units are *not* closed under multiplication and division.
(for example consider :code:`xo::qty::qty::foot * xo::qty::qty::meter`)
- :doc:`bpu<bpu-class>`
A rational (usually integer) power of a basis unit. Has a single dimension.
.. code-block:: cpp
#include "xo/unit/bpu.hpp"
xo::qty::bpu(xo::qty::detail::bu::millimeter,
xo::qty::power_ratio_type(2)); // mm^2
- :doc:`bu_store<bu-store-class>`
Associates basis units with abbreviations.
Abbreviations used to decorate printed quantities.
For example ``bu::kilogram`` => ``"kg"``
.. code-block:: cpp
#include "xo/unit/bu_store.hpp"
xo::qty::bu_abbrev_store.bu_abbrev(xo::qty::detail::bu::picogram); // "pg"
- :doc:`basis_unit<basis-unit-reference>`
A unit with a single dimension and scale.
.. code-block:: cpp
#include "xo/unit/basis_unit.hpp"
auto b = xo::qty::detail::bu::picogram;
- :doc:`dimension<dimension-enum>`
identifies a dimension, such as mass or time.
.. code-block:: cpp
#include "xo/unit/dimension.hpp"
auto d = xo::qty::dimension::mass;
Representation
==============
Worked example using :cpp:class:`xo::qty::quantity`.
.. code-block:: cpp
:linenos:
:emphasize-lines: 6
#include "xo/unit/quantity.hpp"
...
using xo::qty;
namespace q = xo::qty::qty;
// 7.55km.min^-2
quantity qty1 = 7.55 * q::kilometer / (q::minute * q::minute);
Note: in diagrams below, components with pale blue background are discarded before runtime
.. uml::
:caption: representation for quantity 7.55km.min^-2
:scale: 99%
:align: center
object qty1<<quantity>>
qty1 : scale = 7.55
rectangle #e0f0ff {
object km_per_min2<<natural_unit>>
km_per_min2 : n_bpu = 2
km_per_min2 : bpu[0] = km
km_per_min2 : bpu[1] = per_min
object km<<bpu>>
km : native_dim = dim.mass
km : scalefactor = 1000/1
km : power = 1
object per_min2<<bpu>>
per_min2 : native_dim = dim.time
per_min2 : scalefactor = 60/1
per_min2 : power = -2
qty1 o-- km_per_min2 : s_unit (static constexpr)
km_per_min2 *-- km
km_per_min2 *-- per_min2
}
.. code-block:: cpp
:linenos:
// 123ng
quantity qty2 = q::nanograms(123);
.. uml::
:caption: representation for quantity 123 nanograms
:scale: 99%
:align: center
object qty2<<quantity>>
qty2 : scale = 123
rectangle #e0f0ff {
object ng_unit<<natural_unit>>
ng_unit : n_bpu = 1
ng_unit : bpu[0] = ng
object ng<<bpu>>
ng : native_dim = dim::mass
ng : scalefactor = 1/10^9
ng : power = 1
qty2 o-- ng_unit : s_unit (static constexpr)
ng_unit *-- ng
}
.. code-block:: cpp
:linenos:
// (123*7.55) ng.km.min^-2
quantity qty3 = qty2 * qty1;
.. uml::
:caption: quantity 928.65 ng.km.min^-2
:scale: 99%
:align: center
object qty3<<quantity>>
qty3 : scale = 928.65
rectangle #e0f0ff {
object ng_km_min2_unit<<natural_unit>>
ng_km_min2_unit : n_bpu = 3
ng_km_min2_unit : bpu[0] = ng
ng_km_min2_unit : bpu[1] = km
ng_km_min2_unit : bpu[2] = per_min2
object ng<<bpu>>
ng : native_dim = dim::mass
ng : scalefactor = 1/10^9
ng : power = 1
object km<<bpu>>
km : native_dim = dim::distance
km : scalefactor = 1000/1
km : power = 1
object per_min2<<bpu>>
per_min2 : native_dim = dim::time
per_min2 : scalefactor = 60/1
per_min2 : power = -2
qty3 o-- ng_km_min2_unit : s_unit (static constexpr)
ng_km_min2_unit *-- ng
ng_km_min2_unit *-- km
ng_km_min2_unit *-- per_min2
}
.. code-block:: cpp
:linenos:
namespace u = xo::qty::u;
// (123*7.55) ng.km.min^-2 ==> 2.57958e-10kg.m.s^-2
constexpr auto newton = u::kilogram * u::meter / (u::second * u::second);
quantity<newton> qty3b = qty3;
// quantity qty3b = qty3.rescale_ext<newton>();
.. uml::
:caption: quantity 928.65 ng.km.min^-2
:scale: 99%
:align: center
object qty3b<<quantity>>
qty3b : scale = 2.59758e-10
rectangle #e0f0ff {
object kg_m_s2_unit<<natural_unit>>
kg_m_s2_unit : n_bpu = 3
kg_m_s2_unit : bpu[0] = kg
kg_m_s2_unit : bpu[1] = m
kg_m_s2_unit : bpu[2] = per_s2
object kg<<bpu>>
kg : native_dim = dim::mass
kg : scalefactor = 1000/1
kg : power = 1
object m<<bpu>>
m : native_dim = dim::distance
m : scalefactor = 1/1
m : power = 1
object per_s2<<bpu>>
per_s2 : native_dim = dim::time
per_s2 : scalefactor = 1/1
per_s2 : power = -2
qty3b o-- kg_m_s2_unit : s_unit (static constexpr)
kg_m_s2_unit *-- kg
kg_m_s2_unit *-- m
kg_m_s2_unit *-- per_s2
}
.. toctree::
:maxdepth: 2
:caption: Abstractions

48
xo-unit/docs/index.rst Normal file
View file

@ -0,0 +1,48 @@
.. xo-unit-examples documentation master file, created by
sphinx-quickstart on Wed Mar 6 23:32:27 2024.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
xo-unit documentation
=====================
xo-unit is a lightweight header-only library that provides compile-time
dimension checking and unit conversion.
Functionality is similar in spirit to that provided by ``boost::units``;
however there are some important differences:
* streamlined implementation using c++20 features.
* along with no-runtime-overhead compile-time unit inference,
also provides defer-until-runtime representation.
* constexpr string representation for things like unit abbreviations.
* supports fractional dimensions, for concepts like volatility.
* integration with python (see sister project xo-pyunit)
Second, ``xo-unit`` supports fractional dimensions. This allows using it to naturally handle
concepts like volatility (dimension 1/sqrt(time)), for example.
Finally, ``xo-unit`` is written with the expectation of providing
python integration via pybind11. This requires a parallel set of data structures that can work at
runtime (since we can't construct new c++ types at runtime).
.. toctree::
:maxdepth: 2
:caption: xo-unit contents
install
examples
implementation
quantity-reference
xquantity-reference
scaled-unit-reference
natural-unit-class
bpu-class
bu-store-class
basis-unit-reference
dimension-enum
development
ubuntu-github-workflow
glossary
genindex
search

305
xo-unit/docs/install.rst Normal file
View file

@ -0,0 +1,305 @@
.. _install:
.. toctree
:maxdepth: 2
Install
=======
``xo-unit`` uses supporting header-only libraries ``xo-ratio`` and ``xo-flatstring``.
and (optionally) cmake macros ``xo-cmake``. These are on github:
- `xo-unit source`_ (constexpr quantities, units and dimension-checking)
- `xo-ratio source`_ (constexpr exact ratios)
- `xo-flatstring source`_ (constexpr strings)
- `xo-cmake source`_ (shared cmake macros)
.. _xo-unit source: https://github.com/rconybea/xo-unit
.. _xo-ratio source: https://github.com/rconybea/xo-ratio
.. _xo-flatstring source: https://github.com/rconybea/xo-flatstring
.. _xo-cmake source: https://github.com/rconybea/xo-cmake
`xo-cmake` is nccessary to invoke `xo` cmake build for:
- site install
- example programs
- unit tests
Can omit to instead copy `xo_unit`, `xo-ratio` and `xo-flatstring` source trees;
see instructions below for including as git submodule
Implementation relies on some c++20 features (for example class-instances as template arguments).
Tested with gcc 12.3, 13.2
Include as submodule
--------------------
One way to use ``xo-unit`` in a project is to import the source tree directly:
.. code-block:: bash
cd myproject
git submodule add -b main https://github.com/rconybea/xo-flatstring ext/xo-flatstring
git submodule add -b main https://github.com/rconybea/xo-ratio ext/xo-ratio
git submodule add -b main https://github.com/rconybea/xo-unit ext/xo-unit
git submodule update --init
This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``.
You would then add to your compiler's include path the directories ``myproject/ext/xo-flatstring/include``,
``myproject/ext/xo-ratio/include``, ``myproject/ext/xo-unit/include``;
and add
.. code-block:: c++
#include <xo/unit/quantity.hpp>
to c++ source files that rely on xo-unit
Ubuntu Install Pattern
----------------------
:doc:`Example instructions<ubuntu-github-workflow>` (used for github actions CI) for build starting from stock ubuntu
Installing from source
----------------------
Install scripts for `xo-unit`, `xo-ratio` and `xo-flatstring` depend on shared cmake macros
and a bootstrap script `xo-cmake-config` from `xo-cmake`.
Preamble:
.. code-block:: bash
mkdir -p ~/proj/xo
cd ~/proj/xo
git clone https://github.com/rconybea/xo-cmake
git clone https://github.com/rconybea/xo-flatstring
git clone https://github.com/rconybea/xo-ratio
git clone https://github.com/rconybea/xo-unit
PREFIX=~/local # ..or other desired installation prefix
Ordering below is important; cmake support for each subsystem
requires successful installation of its predecessor.
Install `xo-cmake`:
.. code-block:: bash
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -B xo-cmake/.build -S xo-cmake
cmake --build xo-cmake/.build -j # placeholder, no-op for now
cmake --install xo-cmake/.build
Build and Install `xo-flatstring`:
.. code-block:: bash
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DENABLE_TESTING=0 \
-B xo-flatstring/.build -S xo-flatstring
cmake --build xo-flatstring/.build -j
cmake --install xo-flatstring/.build
Build and Install `xo-ratio`:
.. code-block:: bash
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DENABLE_TESTING=0 \
-B xo-ratio/.build -S xo-ratio
cmake --build xo-ratio/.build -j
cmake --install xo-ratio/.build
Build and Install `xo-unit`:
.. code-block:: bash
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DENABLE_TESTING=0 \
-B xo-unit/.build -S xo-unit
cmake --build xo-unit/.build -j
cmake --install xo-unit/.build
Directories under ``PREFIX`` will then contain:
.. code-block::
PREFIX
+- bin
| \- xo-cmake-config
+- include
| \- xo
| +- cxxutil/
| +- flatstring/
| +- ratio/
| +- unit/
+- lib
| \- cmake
| +- indentlog/
| +- randomgen/
| +- xo_flatstring/
| \- xo_unit/
+- share
\- cmake
\- xo_macros/
Use CMake Support
-----------------
To use built-in cmake suport:
Make sure ``PREFIX/lib/cmake`` is searched by cmake (if necessary, include it in ``CMAKE_PREFIX_PATH``)
Add to ``CMakeLists.txt``:
.. code-block:: cmake
FindPackage(xo_unit CONFIG REQUIRED)
target_link_libraries(mytarget INTERFACE xo_unit)
Build and Install with Unit Tests Enabled
-----------------------------------------
Running unit tests require a few additional dependencies:
* `catch2`_ header-only unit-test framework
* `xo-indentlog`_ logging with call-structure indenting
* `xo-randomgen`_ fast random number generator (xoshiro256ss)
.. _catch2: https://github.com/catchorg/Catch2
.. _xo-indentlog: https://github.com/rconybea/indentlog
.. _xo-randomgen: https://github.com/rconybea/randomgen
Preamble:
.. code-block:: bash
mkdir -p ~/proj/xo
cd ~/proj/xo
git clone https://github.com/rconybea/xo-cmake
git clone https://github.com/rconybea/indentlog xo-indentlog
git clone https://github.com/rconybea/randomgen xo-randomgen
git clone https://github.com/rconybea/xo-flatstring
git clone https://github.com/rconybea/xo-ratio
git clone https://github.com/rconybea/xo-unit
PREFIX=~/local # ..or other desired installation prefix
Build and Install `catch2` (assuming ubuntu here):
.. code-block:: bash
sudo apt-get install catch2 # on ubuntu, for example
Build and Install `xo-cmake`:
.. code-block:: bash
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -B xo-cmake/.build -S xo-cmake
cmake --build xo-cmake/.build -j # placeholder, no-op for now
cmake --install xo-cmake/.build
Build, Test and Install `xo-indentlog`:
.. code-block:: bash
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -B xo-indentlog/.build -S xo-indentlog
cmake --build xo-indentlog/.build -j
cmake --build xo-indentlog/.build -- test # run unit tests, cmake invokes ctest
(cd xo-indentlog/.build && ctest) # or invoke ctest directly
cmake --install xo-indentlog/.build
Build and Install `xo-randomgen` (no unit tests yet):
.. code-block:: bash
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -B xo-randomgen/.build -S xo-randomgen
cmake --build xo-randomgen/.build -j
cmake --install xo-randomgen/.build
Build, Test and Install `xo-flatstring`:
.. code-block:: bash
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -B xo-flatstring/.build -S xo-flatstring
cmake --build xo-flatstring/.build -j
cmake --build xo-flatstring/.build -- test # run unit tests, cmake invokes ctest
(cd xo-flatstring/.build && ctest) # or invoke ctest directly
cmake --install xo-flatstring/.build
Build, Test and Install `xo-ratio`:
.. code-block:: bash
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -B xo-ratio/.build -S xo-ratio
cmake --build xo-ratio/.build -j
cmake --build xo-ratio/.build -- test # run unit tests, cmake invokes ctest
(cd xo-ratio/.build && ctest) # or invoke ctest directly
cmake --install xo-ratio/.build
Build, Test and Install `xo-unit`:
.. code-block:: bash
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -B xo-unit/.build -S xo-unit
cmake --build xo-unit/.build -j
cmake --build xo-unit/.build -- test # run unit tests, cmake invokes ctest
(cd xo-unit/.build && ctest) # or invoke ctest directly
cmake --install xo-unit/.build
Build Examples
--------------
To enable building example programs:
.. code-block:: bash
cd ~/proj/xo
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DXO_ENABLE_EXAMPLES=1 -B xo-unit/.build -S xo-unit
Run examples from the build directory:
.. code-block:: bash
~/proj/xo/xo-unit/.build/example/ex1/xo_unit_ex1
~/proj/xo/xo-unit/.build/example/ex2/xo_unit_ex2
# etc
Build and Install Documentation
-------------------------------
xo-unit documentation has these additional dependencies:
* `doxygen`_ annotation-driven inline documentation
* `sphinx`_ documentation based on ReST files
* `sphinx-rtd-theme`_ popular CSS theme for sphinx
* `breathe`_ make doxygen-generated ingredients available from sphinx
.. _doxygen: https://www.doxygen.nl
.. _sphinx: https://www.sphinx-doc.org
.. _sphinx-rtd-theme: https://pypi.org/project/sphinx-rtd-theme
.. _breathe: https://breathe.readthedocs.io/en/latest
Preamble (assuming ubuntu here):
.. code-block:: bash
sudo apt-get install doxygen
sudo apt-get install python3-sphinx
sudo apt-get install python3-sphinx-rtd-theme
sudo apt-get install python3-breathe
Build `xo-unit` docs
.. code-block:: bash
cd ~/proj/xo
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -B xo-unit/.build
cmake --build xo-unit/.build -- docs
cmake --install xo-unit/.build # if docs built, installs to $PREFIX/share/doc/xo_unit/html
Supported compilers
-------------------
* developed with gcc 12.3.0 and gcc 13.2.0; github CI using gcc 11.4.0 (asof March 2024)

View file

@ -0,0 +1,109 @@
.. _natural-unit-class:
Natural Unit
============
A natural unit represents a product of terms, each involving a distinct basis dimension
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
|cBLU natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
Introduction
------------
.. code-block:: cpp
#include <xo/unit/natural_unit.hpp>
Representation for the unit associated with a :doc:`xquantity<xquantity-class>`
- represents a cartesian product of basis units.
- constexpr implementation
- limited support for fractional dimensions such as time^-1/2
.. uml::
:caption: natural unit for a Newton (unit of force)
:scale: 99%
:align: center
object newton<<natural_unit>>
newton : n_bpu = 3
newton : bpu_v[]
object kg<<bpu>>
kg : native_dim = dim::mass
kg : scalefactor = 1000/1
kg : power = 1/1
object m<<bpu>>
m : native_dim = dim::distance
m : scalefactor = 1/1
m : power = 1/1
object s2<<bpu>>
s2 : native_dim = dim::time
s2 : scalefactor = 1/1
s2 : power = -2/1
newton o-- kg
newton o-- m
newton o-- s2
Class
-----
.. doxygenclass:: xo::qty::natural_unit
Member Variables
----------------
.. doxygengroup:: natural-unit-instance-vars
Type Traits
-----------
.. doxygengroup:: natural-unit-type-traits
Constructors
------------
.. doxygengroup:: natural-unit-ctors
Access Methods
--------------
.. doxygengroup:: natural-unit-access-methods
General Methods
---------------
.. doxygengroup:: natural-unit-methods
Conversion
----------
.. doxygengroup:: natural-unit-conversion-methods
Comparison Functions
--------------------
.. doxygengroup:: natural-unit-comparison-functions

View file

@ -0,0 +1,144 @@
.. _quantity-class:
Quantity
========
Dimensioned quantity with compile-time unit checking/conversion
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+---------------+
|cBLU quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
.. code-block:: cpp
#include <xo/unit/quantity.hpp>
.. uml::
:scale: 99%
:align: center
allowmixing
object qty1<<quantity>>
qty1 : scale = 1.23
rectangle constexpr #e0f0ff {
object unit<<scaled_unit>>
unit : is_natural() = true
qty1 o-- unit : s_scaled_unit (static constexpr)
}
- Arithmetic on :doc:`xo::qty::quantity<quantity-reference>` instances
does *not* use ``xo::qty::quantity::s_scaled_unit`` at runtime;
instead gets everything it needs at compile time.
- The :code:`xo::qty::quantity` template takes a :doc:`xo::qty::scaled_unit<scaled-unit-class>` instance,
but only accepts values with :code:`xo::qty::scaled_unit::is_natural() == true`.
This accomodation (instead of requiring a :doc:`xo::qty::natural_unit<natural-unit-class>` instance
is to make possible code like this possible:
.. code-block:: cpp
#include "xo/unit/quantity.hpp"
using namespace xo::qty;
quantity<u::meter / u::second> x;
quantity<u::meter * u::mter> y;
while rejecting attempt to mix multiple scales in the same quantity value:
.. code-block:: cpp
quantity<u::meter * u::millimeter> x; // will not compile
Class
-----
The primary data structure for interacting with xo-unit is the
template class ``xo::qty::quantity``.
A quantity is a compile-time wrapper around a single arithmetic value,
with type taken from the ``Repr`` parameter in ``quantity<Unit, Repr>``.
.. doxygenclass:: xo::qty::quantity
Member Variables
----------------
.. doxygengroup:: quantity-static-vars
.. doxygengroup:: quantity-instance-vars
Type Traits
-----------
.. doxygengroup:: quantity-type-traits
Constructors
------------
.. doxygengroup:: quantity-ctors
The simplest way to create a quantity instance is to use either
* factory functions in ``xo::qty::qty``, see :doc:`quantity-factoryfunctions`
* unit variables in ``xo::qty::qty``, see :doc:`quantity-unitvars`
Assignment
----------
.. doxygengroup:: quantity-assignment
Access Methods
--------------
.. doxygengroup:: quantity-access-methods
Constants
---------
.. doxygengroup:: quantity-constants
Conversion Methods
------------------
Amount-preserving conversion to quantities with different units and/or representation.
.. doxygengroup:: quantity-unit-conversion
Arithmetic
----------
.. doxygengroup:: quantity-operators
Support methods for arithmetic operations
.. doxygengroup:: quantity-arithmetic-support
Comparison
----------
Support methods for comparison operators
.. doxygengroup:: quantity-comparison-support

View file

@ -0,0 +1,94 @@
.. _quantity_factoryfunctions:
Quantity Factory Functions
==========================
Convenience functions for creating quantities with compile-time units
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+---------------+
|cBLU quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
.. toctree::
:maxdepth: 2
Introduction
------------
.. code-block:: cpp
#include <xo/unit/quantity.hpp>
Mass
----
.. doxygenfunction:: xo::qty::qty::picograms
.. doxygenfunction:: xo::qty::qty::nanograms
.. doxygenfunction:: xo::qty::qty::micrograms
.. doxygenfunction:: xo::qty::qty::milligrams
.. doxygenfunction:: xo::qty::qty::grams
.. doxygenfunction:: xo::qty::qty::kilograms
.. doxygenfunction:: xo::qty::qty::tonnes
.. doxygenfunction:: xo::qty::qty::kilotonnes
.. doxygenfunction:: xo::qty::qty::megatonnes
.. doxygenfunction:: xo::qty::qty::gigatonnes
Distance
--------
.. doxygenfunction:: xo::qty::qty::picometers
.. doxygenfunction:: xo::qty::qty::nanometers
.. doxygenfunction:: xo::qty::qty::micrometers
.. doxygenfunction:: xo::qty::qty::millimeters
.. doxygenfunction:: xo::qty::qty::meters
.. doxygenfunction:: xo::qty::qty::kilometers
.. doxygenfunction:: xo::qty::qty::megameters
.. doxygenfunction:: xo::qty::qty::gigameters
.. doxygenfunction:: xo::qty::qty::lightseconds
.. doxygenfunction:: xo::qty::qty::astronomicalunits
.. doxygenfunction:: xo::qty::qty::inches
.. doxygenfunction:: xo::qty::qty::feet
.. doxygenfunction:: xo::qty::qty::yards
.. doxygenfunction:: xo::qty::qty::miles
Time
----
.. doxygenfunction:: xo::qty::qty::picoseconds
.. doxygenfunction:: xo::qty::qty::nanoseconds
.. doxygenfunction:: xo::qty::qty::microseconds
.. doxygenfunction:: xo::qty::qty::milliseconds
.. doxygenfunction:: xo::qty::qty::seconds
.. doxygenfunction:: xo::qty::qty::minutes
.. doxygenfunction:: xo::qty::qty::hours
.. doxygenfunction:: xo::qty::qty::days
.. doxygenfunction:: xo::qty::qty::weeks
.. doxygenfunction:: xo::qty::qty::months
.. doxygenfunction:: xo::qty::qty::years
.. doxygenfunction:: xo::qty::qty::year250s
.. doxygenfunction:: xo::qty::qty::year360s
.. doxygenfunction:: xo::qty::qty::year365s
Volatility
----------
.. doxygenfunction:: xo::qty::qty::volatility_30d
.. doxygenfunction:: xo::qty::qty::volatility_250d
.. doxygenfunction:: xo::qty::qty::volatility_360d
.. doxygenfunction:: xo::qty::qty::volatility_365d

View file

@ -0,0 +1,31 @@
.. _quantity-reference:
Quantity Reference
==================
.. ditaa::
:--scale: 0.85
+----------------+---------------+
|cBLU quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
.. toctree::
:maxdepth: 2
quantity-class
quantity-factoryfunctions
quantity-unitvars
quantity-source-code

View file

@ -0,0 +1,11 @@
.. _quantity-source-code:
Quantity Source Code
====================
github: `quantity.hpp @github`_
.. _`quantity.hpp @github`: https://github.com/rconybea/xo-unit/blob/main/include/xo/unit/quantity.hpp
.. literalinclude:: ../include/xo/unit/quantity.hpp
:language: c++

View file

@ -0,0 +1,98 @@
.. _quantity-unitvars:
Quantity Unit Variables
=======================
Built-in unit quantities
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+---------------+
|cBLU quantity | xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
Introduction
------------
.. code-block:: cpp
#include <xo/unit/quantity.hpp>
The ``xo::qty::qty`` namespace contains unit quantities in each dimension.
Can use these to assemble unit quantities with compound dimensions
.. code-block:: cpp
:linenos:
:emphasize-lines: 6
#include "xo/unit/quantity.hpp"
namespace q = xo::qty::qty;
auto q1 = (q::kilometers(150.0) / q::hours(0.5));
constexpr auto u_mps = q:meter / q:second;
auto q2 = with_units_from(q1, u_mps);
Mass
----
.. doxygenvariable:: xo::qty::qty::picogram
.. doxygenvariable:: xo::qty::qty::nanogram
.. doxygenvariable:: xo::qty::qty::microgram
.. doxygenvariable:: xo::qty::qty::milligram
.. doxygenvariable:: xo::qty::qty::gram
.. doxygenvariable:: xo::qty::qty::kilogram
.. doxygenvariable:: xo::qty::qty::tonne
.. doxygenvariable:: xo::qty::qty::kilotonne
.. doxygenvariable:: xo::qty::qty::megatonne
.. doxygenvariable:: xo::qty::qty::gigatonne
Distance
--------
.. doxygenvariable:: xo::qty::qty::picometer
.. doxygenvariable:: xo::qty::qty::nanometer
.. doxygenvariable:: xo::qty::qty::micrometer
.. doxygenvariable:: xo::qty::qty::millimeter
.. doxygenvariable:: xo::qty::qty::meter
.. doxygenvariable:: xo::qty::qty::kilometer
.. doxygenvariable:: xo::qty::qty::megameter
.. doxygenvariable:: xo::qty::qty::gigameter
.. doxygenvariable:: xo::qty::qty::lightsecond
.. doxygenvariable:: xo::qty::qty::astronomicalunit
.. doxygenvariable:: xo::qty::qty::inch
.. doxygenvariable:: xo::qty::qty::foot
.. doxygenvariable:: xo::qty::qty::yard
.. doxygenvariable:: xo::qty::qty::mile
Time
----
.. doxygenvariable:: xo::qty::qty::picosecond
.. doxygenvariable:: xo::qty::qty::nanosecond
.. doxygenvariable:: xo::qty::qty::microsecond
.. doxygenvariable:: xo::qty::qty::millisecond
.. doxygenvariable:: xo::qty::qty::second
.. doxygenvariable:: xo::qty::qty::minute
.. doxygenvariable:: xo::qty::qty::hour
.. doxygenvariable:: xo::qty::qty::day
.. doxygenvariable:: xo::qty::qty::week
.. doxygenvariable:: xo::qty::qty::month
.. doxygenvariable:: xo::qty::qty::year
.. doxygenvariable:: xo::qty::qty::year250
.. doxygenvariable:: xo::qty::qty::year360
.. doxygenvariable:: xo::qty::qty::year365

View file

@ -0,0 +1,165 @@
.. _scaled-unit-class:
Scaled Unit
===========
A dimensionless multiple of a :doc:`natural_unit<natural-unit-class>`
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+----------------+
| quantity | xquantity |
+----------------+----------------+
|cBLU scaled_unit |
+---------------------------------+
| natural_unit |
+---------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+----------------+
| basis_unit |
+---------------------------------+
| dimension |
+---------------------------------+
Introduction
------------
.. code-block::cpp
#include <xo/unit/scaled_unit.hpp>
Extension of :doc:`natural_unit<natural-unit-class>` to enable representing the intermediate
result of multiplication (or division) of natural units.
- represents a (dimensionless) multiple of a cartesian product of basis units.
- constexpr implementation
- limited support for fractional dimensions such as time^-1/2
.. uml::
:caption: scaled unit after (u::meter * u::foot / u::minute)
:scale: 99%
:align: center
object area_per_time<<scaled_unit>>
area_per_time : outer_scale_factor = 3048/10000
area_per_time : outer_scale_sq = 1.0
area_per_time : natural_unit = m2_per_min
object m2_per_min<<natural_unit>>
m2_per_min : n_bpu = 2
m2_per_min : bpu_v[]
object m2<<bpu>>
m2 : native_dim = dim::distance
m2 : scalefactor = 1/1
m2 : power = 2/1
object min<<bpu>>
min : native_dim = dim::time
min : scalefactor = 60/1
min : power = -1/1
area_per_time o-- m2_per_min
m2_per_min o-- m2
m2_per_min o-- min
Scaled units with non-unity outer scalefactors arise as intermediate results
of quantity arithmetic
Motivation
^^^^^^^^^^
Consider multiplying two units:
.. code-block:: cpp
using namespace xo::qty;
constexpr auto u_prod = u::meter * u::kilometer;
How should we represent the product?
We don't want to mix units. Instead we consolidate on a common unit;
to do this we accumulate a product of conversion factors from such consolidation.
For example:
.. code-block:: cpp
:emphasize-lines: 3
static_assert(u_prod.n_bpu() == 1);
static_assert(u_prod[0].bu() == detail::bu::meter);
static_assert(u_prod[0].power() == power_ratio_type(2));
static_assert(u_prod.outer_scale_factor_ == xo::ratio::ratio<int64_t>(1000));
static_assert(u_prod.outer_scale_sq_ == 1.0); // used if fractional dimension
Here we accumulate :code:`1000`, from converting kilometers to meters.
Division works similarly. In this example dimension cancel, but we still have a non-unity conversion factor.
.. code-block:: cpp
:emphasize-lines: 7
namespace u = xo::qty::u;
constexpr auto u_div = u::meter / u::kilometer;
// dimensionlesss result
static_assert(u_prod.n_bpu() == 0);
static_assert(u_prod.outer_scale_factor_ == xo::ratio::ratio<int64_t>(1,1000));
static_assert(u_prod.outer_scale_sq_ == 1.0);
When multiple dimensions needing conversion are involved, scalefactors accumulate:
.. code-block:: cpp
:emphasize-lines: 8
namespace u = xo::qty::u;
constexpr auto u2_prod = u::meter * u::hour * u::kilometer * u::minute;
static_assert(u2_prod.n_bpu() == 2);
static_assert(u2_prod[0].bu() == detail::bu::meter);
static_assert(u2_prod[1].bu() == detail::bu::hour);
static_assert(u2_prod.outer_scale_factor_ == xo::ratio::ratio<int64_t>(50,3));
static_assert(u2_prod.outer_scale_sq_ == 1.0); // used if fractional dimension
Here the :code:`50/3` result comes from multiplying :code:`1000/1` (converting kilometers -> meters)
by :code:`1/60` (converting minutes -> hours)
Class
-----
.. doxygenclass:: xo::qty::scaled_unit
Member Variables
----------------
.. doxygengroup:: scaled-unit-instance-vars
Type Traits
-----------
.. doxygengroup:: scaled-unit-type-traits
Access Methods
--------------
.. doxygengroup:: scaled-unit-access-methods
General Methods
---------------
.. doxygengroup:: scaled-unit-general-methods
Operators
---------
.. doxygengroup:: scaled-unit-operators

View file

@ -0,0 +1,75 @@
.. _scaled-unit-constants
Scaled Unit Constants
=====================
Built-in unit constants, for use with conversions
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity | xquantity |
+----------------+---------------+
|cBLU scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
Introduction
------------
Built-in units. Application code may use these to trigger conversion
.. code-block:: cpp
:emphasize-lines: 5
#include <xo/unit/quantity.hpp>
using namespace xo::qty;
constexpr quantity<u::meter / u::second> q1 = q::miles(60) / q::hour;
Note that it's often easiest to use :doc:`unit quantity constants<quantity-unitvars>`,
like :code:`q::hour` in the example above
Dimensionless Constant
----------------------
.. doxygengroup:: scaled-unit-dimensionless
Mass Units
----------
.. doxygengroup:: scaled-unit-mass
Distance Units
--------------
.. doxygengroup:: scaled-unit-distance
Time Units
----------
.. doxygengroup:: scaled-unit-time
Volatility Units
----------------
.. doxygengroup:: scaled-unit-volatility
Miscellaneous Units
-------------------
.. doxygengroup:: scaled-unit-misc

View file

@ -0,0 +1,31 @@
.. _scaled-unit-reference:
Scaled Unit Reference
=====================
A dimensionless multiple of a :doc:`natural_unit<natural-unit-class>`
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity | xquantity |
+----------------+---------------+
|cBLU scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
.. toctree::
:maxdepth: 1
Scaled Unit Class <scaled-unit-class>
scaled-unit-constants

View file

@ -0,0 +1,9 @@
.. ubuntu-github-workflow:
Ubuntu Github Workflow
======================
Instructions for build starting on a stock ubuntu platform (see `xo-unit/.github/workflows/ubuntu_main.yml`)
.. literalinclude:: ../.github/workflows/ubuntu-main.yml
:language: yaml

View file

@ -0,0 +1,96 @@
.. _xquantity-class:
Xquantity
=========
Polymorphic dimensioned quantity with runtime unit checking/conversion
Context
-------
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity |cBLU xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
.. code-block:: cpp
#include <xo/unit/xquantity.hpp>
.. uml::
:scale: 99%
:align: center
allowmixing
object qty1<<xquantity>>
qty1 : scale = 1.23
qty1 : unit = unit
object unit<<scaled_unit>>
unit : is_natural() = true
qty1 o-- unit
Class
-----
Class with run-time unit representation.
.. doxygenclass:: xo::qty::xquantity
Member Variables
----------------
.. doxygengroup:: xquantity-instance-vars
Constructors
------------
.. doxygengroup:: xquantity-ctors
Access Methods
--------------
.. doxygengroup:: xquantity-access-methods
Constants
---------
.. doxygengroup:: xquantity-constants
Conversion Methods
------------------
.. doxygengroup:: xquantity-unit-conversion
Arithmetic
----------
.. doxygengroup:: xquantity-operators
Support methods for arithmetic operations
.. doxygengroup:: xquantity-arithmetic-support
Comparison
----------
Support methods for comparison operators
.. doxygengroup:: xquantity-comparison-support

View file

@ -0,0 +1,29 @@
.. _xquantity-reference:
Xquantity Reference
===================
.. ditaa::
:--scale: 0.85
+----------------+---------------+
| quantity |cBLU xquantity |
+----------------+---------------+
| scaled_unit |
+--------------------------------+
| natural_unit |
+--------------------------------+
| bpu |
+----------------+ |
| bu_store | |
+----------------+---------------+
| basis_unit |
+--------------------------------+
| dimension |
+--------------------------------+
.. toctree::
:maxdepth: 2
xquantity-class
xquantity-source-code

View file

@ -0,0 +1,11 @@
.. _xquantity-source-code:
Xquantity Source Code
=====================
github: `xquantity.hpp @github`_
.. _`xquantity.hpp @github`: https://github.com/rconybea/xo-unit/blob/main/include/xo/unit/xquantity.hpp
.. literalinclude:: ../include/xo/unit/xquantity.hpp
:language: c++

View file

@ -0,0 +1,10 @@
add_subdirectory(ex1)
add_subdirectory(ex2)
add_subdirectory(ex3)
add_subdirectory(ex4)
add_subdirectory(ex5)
add_subdirectory(ex6)
add_subdirectory(ex7)
add_subdirectory(ex8)
add_subdirectory(ex_su)
add_subdirectory(ex_qty)

View file

@ -0,0 +1,12 @@
# xo-unit/example/ex1/CMakeLists.txt
set(SELF_EXE xo_unit_ex1)
set(SELF_SRCS ex1.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_unit)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,53 @@
/** @file ex1.cpp **/
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
namespace q = xo::qty::qty;
namespace u = xo::qty::u;
using xo::qty::quantity;
using xo::flatstring;
using namespace std;
constexpr auto t = q::minutes(2);
constexpr auto d = q::kilometers(2.5);
constexpr auto t2 = t*t;
constexpr auto a = d / (t*t);
cerr << "t: " << t << ", d: " << d
<< ", t^2: " << t2
<< ", d.t^-2: " << a
<< endl;
static_assert(std::same_as<decltype(t)::repr_type, int>);
static_assert(sizeof(t) == sizeof(int));
static_assert(t.scale() == 2);
static_assert(t.abbrev() == flatstring("min"));
static_assert(std::same_as<decltype(d)::repr_type, double>);
static_assert(sizeof(d) == sizeof(double));
static_assert(d.scale() == 2.5);
static_assert(d.abbrev() == flatstring("km"));
static_assert(std::same_as<decltype(t2)::repr_type, int>);
static_assert(sizeof(t2) == sizeof(int));
static_assert(t2.scale() == 4);
static_assert(t2.abbrev() == flatstring("min^2"));
static_assert(std::same_as<decltype(a)::repr_type, double>);
static_assert(sizeof(a) == sizeof(double));
static_assert(a.scale() == 0.625);
static_assert(a.abbrev() == flatstring("km.min^-2"));
constexpr auto a2 = a.rescale_ext<(u::meter / (u::second * u::second))>();
cerr << "d.t^-2: " << a2 << endl;
static_assert(a2.abbrev() == flatstring("m.s^-2"));
}
/** end ex1.cpp **/

View file

@ -0,0 +1,12 @@
# xo-unit/example/ex2/CMakeLists.txt
set(SELF_EXE xo_unit_ex2)
set(SELF_SRCS ex2.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_unit)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,49 @@
/** @file ex2.cpp **/
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
namespace q = xo::qty::qty;
namespace u = xo::qty::u;
using xo::qty::with_units_from;
using xo::qty::with_units;
using xo::qty::quantity;
using xo::flatstring;
using namespace std;
constexpr auto t = q::minutes(2);
constexpr auto d = q::kilometers(2.5);
constexpr auto t2 = t*t;
constexpr auto a = d / (t*t);
cerr << "t: " << t << ", d: " << d
<< ", t^2: " << t2
<< ", d.t^-2: " << a
<< endl;
constexpr auto a2 = a.rescale_ext<u::meter / (u::second * u::second)>();
static_assert(a2.abbrev() == flatstring("m.s^-2"));
cerr << "a2: " << a2 << endl;
constexpr auto a3 = with_units<u::meter / (u::second * u::second)>(a);
static_assert(a3.abbrev() == flatstring("m.s^-2"));
cerr << "a3: " << a3 << endl;
constexpr auto au = q::meter / (q::second * q::second);
constexpr auto a4 = with_units_from(a, au);
static_assert(a4.abbrev() == flatstring("m.s^-2"));
cerr << "a4: " << a4 << endl;
}
/** end ex2.cpp **/

View file

@ -0,0 +1,12 @@
# xo-unit/example/ex3/CMakeLists.txt
set(SELF_EXE xo_unit_ex3)
set(SELF_SRCS ex3.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_unit)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,25 @@
/** @file ex3.cpp **/
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
namespace q = xo::qty::qty;
namespace u = xo::qty::u;
using xo::qty::quantity;
using namespace std;
constexpr quantity<u::second> t = q::minutes(2);
constexpr quantity<u::meter> d = q::kilometers(2.5);
constexpr auto t2 = t*t;
constexpr auto a = d / (t*t);
cerr << "t: " << t << ", d: " << d
<< ", d.t^-2: " << a
<< endl;
}
/** end ex3.cpp **/

View file

@ -0,0 +1,12 @@
# xo-unit/example/ex4/CMakeLists.txt
set(SELF_EXE xo_unit_ex4)
set(SELF_SRCS ex4.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_unit)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,30 @@
/** @file ex4.cpp **/
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
namespace q = xo::qty::qty;
auto t1 = q::milliseconds(1);
auto t2 = q::minutes(1);
auto r1 = t1 / with_repr<double>(t2);
static_assert(r1.is_dimensionless());
static_assert(!t2.is_dimensionless());
static_assert(std::same_as<decltype(static_cast<double>(r1)), double>);
/* r1_value: assignment compiles, since r1 dimensionless */
double r1_value = r1;
/* r2_value: assignment won't compile, 'cannot convert' error */
//double r2_value = t2;
std::cerr << "t1: " << t1 << ", t2: " << t2 << ", t1/t2: " << r1 << std::endl;
}
/** end ex4.cpp */

View file

@ -0,0 +1,12 @@
# xo-unit/example/ex5/CMakeLists.txt
set(SELF_EXE xo_unit_ex5)
set(SELF_SRCS ex5.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_unit)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,24 @@
/** @file ex5.cpp **/
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
//namespace u = xo::unit::units;
namespace q = xo::qty::qty;
using namespace std;
/* 20% volatility over 250 days (approx number of trading days in one year) */
auto q1 = q::volatility_250d(0.2);
/* 10% volatility over 30 days */
auto q2 = q::volatility_30d(0.1);
auto sum = q1 + q2;
auto prod = q1 * q2;
cerr << "q1: " << q1 << ", q2: " << q2 << ", q1+q2: " << sum << ", q1*q2: " << prod << endl;
}
/** end ex5.cpp */

View file

@ -0,0 +1,12 @@
# xo-unit/example/ex6/CMakeLists.txt
set(SELF_EXE xo_unit_ex6)
set(SELF_SRCS ex6.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_unit)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,36 @@
/** @file ex6.cpp **/
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
using xo::qty::quantity;
namespace q = xo::qty::qty;
using xo::flatstring;
/* 20% volatility over 360 days */
auto q1 = q::volatility_360d(0.2);
/* 10% volatility over 30 days */
auto q2 = q::volatility_30d(0.1);
/* 10% volatility per 30 days
* ~ (10% * sqrt(360/30)) volatility over 360 days
* ~ (10% * 3.4641)
* ~ 0.34641yr360^(-1/2)
*/
auto sum = q1 + q2;
auto prod = q1 * q2;
static_assert(sum.abbrev() == flatstring("yr360^(-1/2)"));
static_assert(prod.abbrev() == flatstring("yr360^-1"));
std::cerr << "q1: " << q1 << std::endl;
std::cerr << "q2: " << q2 << std::endl;
std::cerr << "q1+q2: " << sum << std::endl;
std::cerr << "q1*q2: " << prod << std::endl;
}
/** end ex6.cpp */

View file

@ -0,0 +1,12 @@
# xo-unit/example/ex7/CMakeLists.txt
set(SELF_EXE xo_unit_ex7)
set(SELF_SRCS ex7.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_unit)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,31 @@
/** @file ex7.cpp **/
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
#include <iostream>
int
main () {
using namespace xo::qty;
namespace u = xo::qty::u;
namespace q = xo::qty::qty;
using namespace std;
quantity qty1 = 7.55 * q::kilometer / q::minute / q::minute;
quantity qty2 = q::nanograms(123);
quantity qty3 = qty2 * qty1;
cerr << "qty1: " << qty1 << endl;
cerr << "qty2: " << qty2 << endl;
cerr << "qty3: " << qty3 << endl;
/* rescale to not-so-absurd units */
/* kg.m.s^-2 */
quantity res = qty3.rescale_ext<u::kilogram * u::meter / (u::second * u::second)>();
/* 2.57958e-10kg.m.s^-2 */
cerr << "res: " << res << endl;
}
/** end ex7.cpp */

View file

@ -0,0 +1,12 @@
# xo-unit/example/ex8/CMakeLists.txt
set(SELF_EXE xo_unit_ex8)
set(SELF_SRCS ex8.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_unit)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,37 @@
/** @file ex8.cpp **/
#include "xo/unit/xquantity.hpp"
#include "xo/unit/xquantity_iostream.hpp"
#include <iostream>
int
main () {
using namespace xo::qty;
namespace u = xo::qty::u;
using namespace std;
constexpr xquantity qty1(7, u::foot);
constexpr xquantity qty2(6.0, u::inch);
// constexpr not supported for xquantity addition
xquantity qty3 = qty1 + qty2;
cerr << "qty1: " << qty1 << endl;
cerr << "qty2: " << qty2 << endl;
cerr << "qty3: " << qty3 << endl;
/* rescale to mm */
xquantity res = qty3.rescale_ext(xo::qty::u::millimeter);
/* 2286mm */
cerr << "res: " << res << endl;
/* 12 */
xquantity qty4 = qty1 / qty2;
auto res2 = qty4 + 4;
cerr << "res2: " << res << endl;
}
/** end ex8.cpp */

View file

@ -0,0 +1,12 @@
# xo-unit/example/ex_qty/CMakeLists.txt
set(SELF_EXE xo_unit_ex_qty)
set(SELF_SRCS ex_qty.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_unit)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,18 @@
/* @file ex_qty.cpp */
#include "xo/unit/quantity.hpp"
#include "xo/unit/quantity_iostream.hpp"
using namespace std;
int
main() {
using namespace xo::qty;
namespace u = xo::qty::u;
static_assert(u::meter.n_bpu() == 1);
//constexpr auto q = qty::meters(2) + u::meter;
}
/* end ex_qty.cpp */

View file

@ -0,0 +1,12 @@
# xo-unit/example/su/CMakeLists.txt
set(SELF_EXE xo_unit_ex_su)
set(SELF_SRCS ex_su.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_unit)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,38 @@
/** @file ex_su.cpp **/
#include "xo/unit/scaled_unit.hpp"
#include "xo/unit/scaled_unit_iostream.hpp"
#include <iostream>
using namespace std;
int
main() {
using namespace xo::qty;
constexpr auto u_prod = u::meter * u::kilometer;
static_assert(u_prod[0].bu() == detail::bu::meter);
static_assert(u_prod[0].power() == power_ratio_type(2));
static_assert(u_prod.outer_scale_factor_ == xo::ratio::ratio<int64_t>(1000));
static_assert(u_prod.outer_scale_sq_ == 1.0); // used if fractional dimension
constexpr auto u_div = u::meter / u::kilometer;
static_assert(u_div.n_bpu() == 0);
static_assert(u_div.outer_scale_factor_ == xo::ratio::ratio<int64_t>(1,1000));
static_assert(u_prod.outer_scale_sq_ == 1.0); // used if fractional dimension
constexpr auto u2_prod = u::meter * u::hour * u::kilometer * u::minute;
static_assert(u2_prod.n_bpu() == 2);
static_assert(u2_prod[0].bu() == detail::bu::meter);
static_assert(u2_prod[1].bu() == detail::bu::hour);
// *1000 from converting kilometers -> meters
// /60 from converting minutes -> hours
// 1000/60 = 50/3 in lowest terms
static_assert(u2_prod.outer_scale_factor_ == xo::ratio::ratio<int64_t>(50,3)); // used if fractional dimension
static_assert(u2_prod.outer_scale_sq_ == 1.0); // used if fractional dimension
}
/** end ex_su.cpp **/

299
xo-unit/flake.nix Normal file
View file

@ -0,0 +1,299 @@
{
description = "xo-unit: c++ compile-time dimension checking and unit conversion";
# Adapted from xo-nix2/flake.nix
# MANIFESTO
# No build instructions in flake.nix
# - Following Jade Lovelace's advice
# - Build instructions are in pkgs/*.nix
# - Each pkgs/*.nix is intended to work 'like a .nix file in nixpkgs'
# I'm being lazy about source hashes, since flake.nix supplies them.
#
# Motivation (per JL) versus doing everything in flake.nix:
# - nixpkgs-ready
# - parameterized
# - overridable
# - still works if cross-compiling
#
# Instead: using flake.nix as entry point:
# - pin nixpkgs to a specific revision, for reproducibility
# - pin our candidate packages (pkgs/*.nix), for the same reason.
# to determine specific hash for nixpkgs:
# 1. $ cd ~/proj/nixpkgs
# 2. $ git checkout release-23.05
# 3. $ git fetch
# 4. $ git pull
# 5. $ git log -1
# take this hash, then substitue for ${hash} in:
# inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/${hash}.tar.gz";
# below
#inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/9a333eaa80901efe01df07eade2c16d183761fa3.tar.gz";
# as sbove but instead of {release-23.05} use {release-23.11}
# gcc -> 12.3.0
# python -> 3.11.6
#
inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz"; # asof 10mar2024
#inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/4dd376f7943c64b522224a548d9cab5627b4d9d6.tar.gz";
# inputs.nixpkgs.url
# = "https://github.com/NixOS/nixpkgs/archive/fac3684647cc9d6dfb2a39f3f4b7cf5fc89c96b6.tar.gz"; # asof 8feb2024
# fac3684647.. asof 17oct2023
# instead of
# inputs.nixpkgs.url = "github:nixos/nixpkgs/23.05";
inputs.flake-utils.url = "github:numtide/flake-utils";
# To add a new package, visit placeholder-A .. placeholder-E
inputs.xo-cmake-path = { type = "github"; owner = "Rconybea"; repo = "xo-cmake"; flake = false; };
inputs.xo-indentlog-path = { type = "github"; owner = "Rconybea"; repo = "indentlog"; flake = false; };
inputs.xo-flatstring-path = { type = "github"; owner = "Rconybea"; repo = "xo-flatstring"; flake = false; };
inputs.xo-refcnt-path = { type = "github"; owner = "Rconybea"; repo = "refcnt"; flake = false; };
inputs.xo-subsys-path = { type = "github"; owner = "Rconybea"; repo = "subsys"; flake = false; };
inputs.xo-randomgen-path = { type = "github"; owner = "Rconybea"; repo = "randomgen"; flake = false; };
inputs.xo-ratio-path = { type = "github"; owner = "Rconybea"; repo = "xo-ratio"; flake = false; };
#inputs.xo-pyutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyutil"; flake = false; };
inputs.xo-reflect-path = { type = "github"; owner = "Rconybea"; repo = "reflect"; flake = false; };
#inputs.xo-pyreflect-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreflect"; flake = false; };
# placeholder-A
outputs
= { self,
nixpkgs,
flake-utils,
xo-cmake-path,
xo-indentlog-path,
xo-flatstring-path,
xo-refcnt-path,
xo-subsys-path,
xo-randomgen-path,
xo-ratio-path,
xo-reflect-path,
# placeholder-B
} :
# out :: system -> {packages, devShells}
let
out
= system :
let
pkgs = nixpkgs.legacyPackages.${system};
# could try using
# appliedOverlay = (pkgs.extend self.overlays.default)
# but it doesn't seem to work the way I expect,
# For example, wants to pickup 2.7.11 python for xo-pyutil !
#
appliedOverlay = self.overlays.default pkgs pkgs;
in
{
#xo-cmake-dir = "${self.packages.${system}.xo-cmake}/share/cmake";
# reminder:
# 'packages' comprises the output of this flake;
# each defn invokes a build
# ./pkgs/$example.nix
# using
# cmake-examples-$example-path
# above for source code
packages.xo-cmake = appliedOverlay.xo-cmake;
packages.xo-indentlog = appliedOverlay.xo-indentlog;
packages.xo-flatstring = appliedOverlay.xo-flatstring;
packages.xo-refcnt = appliedOverlay.xo-refcnt;
packages.xo-subsys = appliedOverlay.xo-subsys;
packages.xo-randomgen = appliedOverlay.xo-randomgen;
packages.xo-ratio = appliedOverlay.xo-ratio;
packages.xo-reflect = appliedOverlay.xo-reflect;
packages.xo-unit = appliedOverlay.xo-unit;
# placeholder-C
packages.xo-userenv = appliedOverlay.xo-userenv;
devShells = appliedOverlay.devShells;
};
in
flake-utils.lib.eachDefaultSystem
out
//
{
# introduce overlay to extend nixpkgs with our local packages,
# (which ofc are not present in nixpkgs, though same form would work if they were present)
#
overlays.default = final: prev:
(
let
# can use
# $ nix-env -qaP | grep \.boost # show known boost versions
# $ nix-env -qaP | grep \.python.*Packages # show known python versions
stdenv = prev.stdenv;
boost = prev.boost182;
python = prev.python311Full;
pythonPackages = prev.python311Packages;
#doxygen = prev.doxygen;
pybind11 = pythonPackages.pybind11;
#breathe = pythonPackages.breathe;
#sphinx = pythonPackages.sphinx;
#sphinx-rtd-theme = pythonPackages.sphinx-rtd-theme;
#extras1 = { boost = boost; };
#extras2 = { boost = boost; python3Packages = python3Packages; pybind11 = pybind11; };
#extras3 = { boost = boost; python3Packages = python3Packages; pybind11 = pybind11; doxygen = doxygen; };
#extras4 = extras3 // { breathe = breathe; };
xo-cmake =
(prev.callPackage ./pkgs/xo-cmake.nix {}).overrideAttrs
(old: { src = xo-cmake-path; });
xo-indentlog =
(prev.callPackage ./pkgs/xo-indentlog.nix { xo-cmake = xo-cmake; }).overrideAttrs
(old: { src = xo-indentlog-path; });
xo-flatstring =
(prev.callPackage ./pkgs/xo-flatstring.nix { xo-cmake = xo-cmake;
xo-indentlog = xo-indentlog;
python = python;
pythonPackages = pythonPackages; }).overrideAttrs
(old: { src = xo-flatstring-path; });
xo-subsys =
(prev.callPackage ./pkgs/xo-subsys.nix { xo-cmake = xo-cmake; }).overrideAttrs
(old: { src = xo-subsys-path; });
xo-randomgen =
(prev.callPackage ./pkgs/xo-randomgen.nix { xo-cmake = xo-cmake;
xo-indentlog = xo-indentlog;
}).overrideAttrs
(old: { src = xo-randomgen-path; });
xo-ratio =
(prev.callPackage ./pkgs/xo-ratio.nix { xo-cmake = xo-cmake;
xo-flatstring = xo-flatstring;
xo-randomgen = xo-randomgen;
python = python;
pythonPackages = pythonPackages;
}).overrideAttrs
(old: { src = xo-ratio-path; });
xo-refcnt =
(prev.callPackage ./pkgs/xo-refcnt.nix { xo-cmake = xo-cmake;
xo-indentlog = xo-indentlog; }).overrideAttrs
(old: { src = xo-refcnt-path; });
xo-reflect =
(prev.callPackage ./pkgs/xo-reflect.nix { xo-cmake = xo-cmake;
xo-subsys = xo-subsys;
xo-refcnt = xo-refcnt; }).overrideAttrs
(old: { src = xo-reflect-path; });
xo-unit =
(prev.callPackage ./pkgs/xo-unit.nix { xo-cmake = xo-cmake;
xo-flatstring = xo-flatstring;
xo-randomgen = xo-randomgen;
xo-ratio = xo-ratio;
python = python;
pythonPackages = pythonPackages;
}).overrideAttrs
(old: { src = ./.; });
# placeholder-D
# user environment with all xo libraries present
xo-userenv =
(prev.callPackage ./pkgs/xo-userenv.nix { xo-cmake = xo-cmake;
xo-indentlog = xo-indentlog;
xo-flatstring = xo-flatstring;
xo-subsys = xo-subsys;
xo-refcnt = xo-refcnt;
xo-randomgen = xo-randomgen;
xo-ratio = xo-ratio;
xo-reflect = xo-reflect;
xo-unit = xo-unit;
}).overrideAttrs(old: {});
in
# attrs in this set provide derivations with all overlay changes applied.
#
# REMINDER: need expression like
# packages.xo-foo = appliedOverlay.xo-foo;
# above to export
{
xo-cmake = xo-cmake;
xo-indentlog = xo-indentlog;
xo-flatstring = xo-flatstring;
xo-subsys = xo-subsys;
xo-refcnt = xo-refcnt;
xo-randomgen = xo-randomgen;
xo-ratio = xo-ratio;
xo-reflect = xo-reflect;
xo-unit = xo-unit;
# placeholder-E
xo-userenv = xo-userenv;
devShells = {
default = prev.mkShell.override
# but may need prev.clang16Stdenv instead of prev.stdenv here on macos
{ stdenv = prev.stdenv; }
{ packages
= [ python
pybind11
pythonPackages.coverage
pythonPackages.sphinx
pythonPackages.sphinx-rtd-theme
pythonPackages.breathe
# pythonPackages.pyarrow
boost # really for filemerge
prev.llvmPackages_16.clang-unwrapped
prev.anki
prev.mesa
prev.egl-wayland
prev.emacs29
prev.notmuch
prev.emacsPackages.notmuch
prev.inconsolata-lgc
prev.doxygen
prev.graphviz
prev.ditaa
prev.semgrep
prev.ripgrep
prev.git
prev.openssh
prev.cmake
prev.gdb
prev.which
prev.man
prev.man-pages
prev.less
prev.tree
prev.nix-tree
prev.lcov
prev.arrow-cpp
prev.libwebsockets
prev.jsoncpp
prev.eigen
prev.catch2
prev.pkg-config
prev.zlib
];
};
};
});
};
}

View file

@ -0,0 +1,240 @@
/** @file basis_unit.hpp **/
#pragma once
#include "native_unit.hpp"
#include "dimension.hpp"
//#include "basis_unit_abbrev.hpp"
#include "xo/ratio/ratio.hpp"
namespace xo {
namespace qty {
using bu_abbrev_type = flatstring<16>;
using scalefactor_ratio_type = xo::ratio::ratio<std::int64_t>;
using scalefactor2x_ratio_type = xo::ratio::ratio<__int128>;
/** @class basis_unit
* @brief A dimensionless multiple of a single natively-specified basis dimension
*
* For example "3600 minutes" or "1e-6 grams".
*
* Members are public so that a @c basis_unit instance qualifies as a 'structural type',
* and therefore may be used as a non-type template parameter.
**/
struct basis_unit {
public:
/** @defgroup basis-unit-constructors basis_unit constructors **/
///@{
/** sentinel basis unit: invalid dimension and zero scalefactor **/
constexpr basis_unit() = default;
/** basis unit representing multiple @p scalefactor of native dimension @p **/
constexpr basis_unit(dimension native_dim,
const scalefactor_ratio_type & scalefactor)
: native_dim_{native_dim},
scalefactor_{scalefactor}
{}
///@}
/** @defgroup basis-unit-access-methods basis_unit access methods **/
///@{
/** get @c native_dim member **/
constexpr dimension native_dim() const { return native_dim_; }
/** get @c scalefactor member **/
constexpr const scalefactor_ratio_type & scalefactor() const { return scalefactor_; }
///@}
public: /* public so instance can be a non-type template parameter (a structural type) */
/** @defgroup basis-unit-instance-vars basis_unit instance variables **/
///@{
/** @brief identifies a native unit, e.g. time **/
dimension native_dim_ = dimension::invalid;
/** @brief this unit defined as multiple scalefactor times native unit **/
scalefactor_ratio_type scalefactor_ = {};
///@}
};
/** @defgroup basis-unit-comparison-support basis_unit comparisons **/
///@{
/** @c true iff basis units are equal;
* both native dimension and scalefactor must be equal
**/
inline constexpr bool
operator==(const basis_unit & x, const basis_unit & y)
{
return ((x.native_dim_ == y.native_dim_)
&& (x.scalefactor_ == y.scalefactor_));
}
/** @c true iff bass units are not equal **/
inline constexpr bool
operator!=(const basis_unit & x, const basis_unit & y)
{
return ((x.native_dim_ != y.native_dim_)
|| (x.scalefactor_ != y.scalefactor_));
}
///@}
namespace detail {
/** @brief namespace for basis-unit constants and helpers **/
namespace bu {
// ----- mass -----
constexpr basis_unit mass_unit(std::int64_t num, std::int64_t den) {
return basis_unit(dimension::mass, scalefactor_ratio_type(num, den));
}
/** @defgroup basis-unit-mass-units basis_unit mass units **/
///@{
/** basis unit of 10^-12 grams **/
constexpr basis_unit picogram = mass_unit( 1, 1000000000000);
/** basis unit of 10^-9 grams **/
constexpr basis_unit nanogram = mass_unit( 1, 1000000000);
/** basis unit of 10^-6 grams **/
constexpr basis_unit microgram = mass_unit( 1, 1000000);
/** basis unit of 10^-3 grams **/
constexpr basis_unit milligram = mass_unit( 1, 1000);
/** basis unit of 1 gram **/
constexpr basis_unit gram = mass_unit( 1, 1);
/** basis unit of 10^3 grams **/
constexpr basis_unit kilogram = mass_unit( 1000, 1);
/** basis unit of 10^6 grams = 10^3 kilograms **/
constexpr basis_unit tonne = mass_unit( 1000000, 1);
/** basis unit of 10^9 grams = 10^6 kilograms = 10^3 tonnes **/
constexpr basis_unit kilotonne = mass_unit( 1000000000, 1);
/** basis unit of 10^12 grams = 10^9 kilograms = 10^6 tonnes **/
constexpr basis_unit megatonne = mass_unit( 1000000000000, 1);
/** basis unit of 10^15 grams = 10^12 kilograms = 10^9 tonnes **/
constexpr basis_unit gigatonne = mass_unit(1000000000000000, 1);
///@}
// ----- distance -----
constexpr basis_unit distance_unit(std::int64_t num, std::int64_t den) {
return basis_unit(dimension::distance, scalefactor_ratio_type(num, den));
}
/** @defgroup basis-unit-distance-units basis_unit distance units **/
///@{
/* US spelling */
/** basis unit of 10^-12 meters **/
constexpr basis_unit picometer = distance_unit( 1, 1000000000000);
/** basis unit of 10^-9 meters **/
constexpr basis_unit nanometer = distance_unit( 1, 1000000000);
/** basis unit of 10^-6 meters **/
constexpr basis_unit micrometer = distance_unit( 1, 1000000);
/** basis unit of 10^-3 meters **/
constexpr basis_unit millimeter = distance_unit( 1, 1000);
/** basis unit of 1 meter **/
constexpr basis_unit meter = distance_unit( 1, 1);
/** basis unit of 10^3 meters **/
constexpr basis_unit kilometer = distance_unit( 1000, 1);
/** basis unit of 10^6 meters (for form's sake -- not commonly used) **/
constexpr basis_unit megameter = distance_unit( 1000000, 1);
/** basis unit of 10^9 meters (for form's sake -- not commonly used) **/
constexpr basis_unit gigameter = distance_unit( 1000000000, 1);
/** basis unit of 1 light-second = distance light travels in a vacuum in 1 second **/
constexpr basis_unit lightsecond = distance_unit( 299792458, 1);
/** basis unit of 1 astronomical unit, representing approximate radius of earth orbit. **/
constexpr basis_unit astronomicalunit = distance_unit( 149597870700, 1);
/* Int'l spelling */
/** international spelling for picometer **/
constexpr basis_unit picometre = picometer;
/** international spelling for nanometer **/
constexpr basis_unit nanometre = nanometer;
/** international spelling for micrometer **/
constexpr basis_unit micrometre = micrometer;
/** international spelling for millimeter **/
constexpr basis_unit millimetre = millimeter;
/** international spelling for meter **/
constexpr basis_unit metre = meter;
/** international spelling for kilometer **/
constexpr basis_unit kilometre = kilometer;
/** international spelling for megameter **/
constexpr basis_unit megametre = megameter;
/** international spelling for gigameter **/
constexpr basis_unit gigametre = gigameter;
/** @brief basis-unit representing 1 inch; defined as exactly 1/12 feet **/
constexpr basis_unit inch = distance_unit( 3048, 120000);
/** @brief basis-unit representing 1 foot; defined as exactly 0.3048 meters **/
constexpr basis_unit foot = distance_unit( 3048, 10000);
/** @brief basis-unit representing 1 yard; defined as exactly 3 feet **/
constexpr basis_unit yard = distance_unit( 3*3048, 10000);
/** @brief basis-unit representing 1 mile; defined as exactly 1760 yards = 5280 feet **/
constexpr basis_unit mile = distance_unit( 5280*3048, 10000);
///@}
// ----- time -----
constexpr basis_unit time_unit(std::int64_t num, std::int64_t den) {
return basis_unit(dimension::time, scalefactor_ratio_type(num, den));
}
/** @defgroup basis-unit-time-units basis_unit time units **/
///@{
/** basis unit of 10^-12 seconds **/
constexpr basis_unit picosecond = time_unit( 1, 1000000000000);
/** basis unit of 10^-9 seconds **/
constexpr basis_unit nanosecond = time_unit( 1, 1000000000);
/** basis unit of 10^-6 seconds **/
constexpr basis_unit microsecond = time_unit( 1, 1000000);
/** basis unit of 10^-3 seconds **/
constexpr basis_unit millisecond = time_unit( 1, 1000);
/** basis unit of 1 second **/
constexpr basis_unit second = time_unit( 1, 1);
/** basis unit of 1 minute = 60 seconds **/
constexpr basis_unit minute = time_unit( 60, 1);
/** basis unit of 1 hour = 3600 seconds **/
constexpr basis_unit hour = time_unit( 3600, 1);
/** basis unit of 1 day = exactly 24 hours **/
constexpr basis_unit day = time_unit( 24*3600, 1);
/** basis unit of 1 week = exactly 7 days **/
constexpr basis_unit week = time_unit( 7*24*3600, 1);
/** basis unit of 1 month = exactly 30 days **/
constexpr basis_unit month = time_unit( 30*24*3600, 1);
/** basis unit of 1 year, defined as 365.25 days **/
constexpr basis_unit year = time_unit( (365*24+6)*3600, 1);
/* alt conventions used in finance */
/** basis unit of 1 year365 = exactly 365 days **/
constexpr basis_unit year365 = time_unit( 365*24*3600, 1);
/** basis unit of 1 year360 = exactly 360 days **/
constexpr basis_unit year360 = time_unit( 360*24*3600, 1);
/** basis unit of 1 year250 = exactly 250 days.
* Approximate number of business days in one year
**/
constexpr basis_unit year250 = time_unit( 250*24*3600, 1);
//constexpr basis_unit century = time_unit( 100L*(365*24+6)*3600, 1);
//constexpr basis_unit millenium = time_unit(1000L*(365*24+6)*3600, 1);
///@}
// ----- currency -----
/** @defgroup basis-unit-misc-units basis_unit miscellaneous units**/
///@{
constexpr basis_unit currency_unit(std::int64_t num, std::int64_t den) {
return basis_unit(dimension::currency, scalefactor_ratio_type(num, den));
}
/** pseudounit -- placeholder for any actual currency amount **/
constexpr basis_unit currency = currency_unit(1, 1);
// ----- price -----
constexpr basis_unit price_unit(std::int64_t num, std::int64_t den) {
return basis_unit(dimension::price, scalefactor_ratio_type(num, den));
}
/** psuedounit -- context-dependent interpretation for a screen price **/
constexpr basis_unit price = price_unit(1, 1);
///@}
} /*namespace bu*/
} /*namespace detail*/
} /*namespace qty*/
} /*namespace xo*/
/** end basis_unit.hpp **/

View file

@ -0,0 +1,188 @@
/** @file bpu.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "basis_unit.hpp"
#include "bu_store.hpp"
namespace xo {
namespace qty {
namespace abbrev {
/** fixed-size string representation for exponent of a basis-power-unit **/
using power_abbrev_type = flatstring<16>;
/** @defgroup bpu-abbrev-helpers bpu abbrev helpers **/
///@{
/** @brief construct prefix string for unit exponent
*
* Auxiliary function for @ref bpu_abbrev
**/
constexpr power_abbrev_type
flatstring_from_exponent(const power_ratio_type & power)
{
if (power.den() == 1) {
if (power.num() == 1) {
/* for no exponent annotation for power ^1 */
return power_abbrev_type::from_chars("");
} else {
/* e.g. "^-1", "^2" */
return (power_abbrev_type::from_flatstring
(flatstring_concat(flatstring("^"),
power_abbrev_type::from_int(power.num()))));
}
} else {
/* e.g. "^1/2", "^-1/2" */
return (power_abbrev_type::from_flatstring
(flatstring_concat(flatstring("^"),
power.to_str<power_abbrev_type::fixed_capacity>())));
}
}
/** construct suffix abbreviation for a basis-power-unit **/
static constexpr bpu_abbrev_type
bpu_abbrev(dim native_dim,
const scalefactor_ratio_type & scalefactor,
const power_ratio_type & power)
{
return (bpu_abbrev_type::from_flatstring
(flatstring_concat
(bu_abbrev(basis_unit(native_dim, scalefactor)),
flatstring_from_exponent(power))));
}
///@}
}
/** @class bpu
*
* @brief represent product of a compile-time scale-factor with a rational power of a native unit
**/
template<typename Int>
struct bpu {
public:
using ratio_int_type = Int;
public:
/** @defgroup bpu-ctors bpu constructors **/
///@{
/** default constructor. creates dimensionless bpu,
* representing zero'th power of sentinel basis unit
**/
constexpr bpu() = default;
/** construct @c bpu representing exponent @p power of basis unit @p bu **/
constexpr bpu(const basis_unit & bu,
const power_ratio_type & power)
: bu_{bu},
power_{power}
{}
/** construct @c bpu representing exponent @p power of @c basis_unit(native_dim,scalefactor) **/
constexpr bpu(dim native_dim,
const scalefactor_ratio_type & scalefactor,
const power_ratio_type & power)
: bu_(native_dim, scalefactor),
power_{power}
{}
/** construct bpu representing basis unit @p bu, i.e. with unit exponent **/
static constexpr bpu<Int> unit_power(const basis_unit & bu) {
return bpu<Int>(bu, power_ratio_type(1,1));
}
///@}
/** @defgroup bpu-access-methods bpu access methods **/
///@{
/** @brief report this bpu's @ref basis_unit, e.g. @c detail::bu::minute **/
constexpr const basis_unit & bu() const { return bu_; }
/** @brief report this bpu's @ref dimension, e.g. @c dimension::time **/
constexpr dimension native_dim() const { return bu_.native_dim(); }
/** @brief report this bpu's scale factor, e.g. @c 60/1 for @c detail::bu::minute **/
constexpr const scalefactor_ratio_type & scalefactor() const { return bu_.scalefactor(); }
/** @brief report this bpu's exponent, e.g. @c 3/1 for bpu representing cubic meters **/
constexpr const power_ratio_type & power() const { return power_; }
///@}
/** @defgroup bpu-methods **/
///@{
/** abbreviation for this dimension
*
* @code
* bpu<int64_t>(dim::time,
* scalefactor_ratio_type(60,1),
* power_ratio_type(-2,1)).abbrev() => "min^-2"
* @endcode
**/
constexpr bpu_abbrev_type abbrev() const
{
return abbrev::bpu_abbrev(bu_.native_dim_,
bu_.scalefactor_,
power_);
}
/** for bpu @c x, @c x.reciprocal() represents dimension of @c 1/x
*
* Example:
* @code
* constexpr auto x = bpu<int64_t>(dim::time,
* scalefactor_ratio_type(60,1),
* power_ratio_type(1));
* x.abbrev() => "min"
* x.reciprocal().abbrev() => "min^-1"
* @endcode
**/
constexpr bpu<Int> reciprocal() const {
return bpu<Int>(bu_.native_dim(), bu_.scalefactor(), power_.negate());
}
/** construct bpu representing the same unit, but using @c Int2 to represent exponenct **/
template <typename Int2>
constexpr bpu<Int2> to_repr() const {
return bpu<Int2>(this->native_dim(),
this->scalefactor(),
ratio::ratio<Int2>(power_.num(), power_.den()));
}
///@}
public: /* need public members so that a basis_unit instance can be a non-type template parameter (a structural type) */
/** @defgroup bpu-instance-vars **/
///@{
/** this @c bpu represent a power of basis unit @c bu.
*
* Public to avoid disqualifying @c bpu as a 'structural type'.
**/
struct basis_unit bu_;
/** this unit represents basis dimension (bu) taken to this power
*
* Public to avoid disqualifying @c bpu as a 'structural type'.
**/
power_ratio_type power_ = {};
///@}
};
/** @defgroup bpu-comparison **/
///@{
/** @brief compare bpus @p x and @p y for equality
*
* Equality requires that both basis unit and power are equal
**/
template <typename Int>
inline constexpr bool
operator==(const bpu<Int> & x, const bpu<Int> & y) {
return ((x.bu() == y.bu())
&& (x.power_ == y.power_));
}
/** @brief compare bpus @p x and @p y for inequality **/
template <typename Int>
inline constexpr bool
operator!=(const bpu<Int> & x, const bpu<Int> & y) {
return ((x.bu() != y.bu())
|| (x.power_ != y.power_));
}
///@}
} /*namespace qty*/
} /*namespace xo*/
/** end bpu.hpp **/

View file

@ -0,0 +1,30 @@
/** @file bpu_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "bpu.hpp"
#include "dim_iostream.hpp"
#include "xo/ratio/ratio_iostream.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <iostream>
namespace xo {
namespace qty {
template <typename Int>
inline std::ostream &
operator<<(std::ostream & os, const bpu<Int> & x) {
os << "<bpu"
<< xtag("dim", x.native_dim())
<< xtag("mult", x.scalefactor())
<< xtag("pwr", x.power())
<< ">";
return os;
}
} /*namespace qty*/
} /*namespace xo*/
/** end bpu_iostream.hpp **/

View file

@ -0,0 +1,279 @@
/** @file bu_store.hpp **/
#pragma once
//#include "bpu.hpp"
#include "basis_unit.hpp"
#include "xo/ratio/ratio.hpp"
#include <array>
#include <cstdint>
namespace xo {
namespace qty {
using bpu_abbrev_type = flatstring<24>;
using power_ratio_type = xo::ratio::ratio<std::int64_t>;
namespace detail {
/** @class bu_dim_store
* @brief store basis-unit abbreviations for a particular dimension
**/
struct bu_dim_store {
/** max number of basis-units per dimension **/
static constexpr std::size_t max_bu_per_dim = 25;
/** @defgroup bu-dim-store-type-traits bu-dim-store type traits **/
///@{
using entry_type = std::pair<scalefactor2x_ratio_type, bu_abbrev_type>;
/* e.g.
* [(1/1000000000, "nm"), (1/1000000, "um"), (1/1000, "mm"), (1/1, "m"), (1000/1, "km")]
*/
using native_scale_v = std::array<entry_type, max_bu_per_dim>;
///@}
public:
constexpr bu_dim_store() = default;
constexpr bool empty() const { return n_bu_ == 0; }
constexpr std::size_t size() const { return n_bu_; }
constexpr const entry_type & operator[](std::size_t i) const { return bu_abbrev_v_[i]; }
/** @brief get least-upper-bound index position in bu_abbrev_v[]
*
* return value in [0, n] where n = .size()
**/
constexpr std::size_t abbrev_lub_ix(const scalefactor_ratio_type & scalefactor) const
{
if (n_bu_ == 0)
return 0;
std::size_t lo = 0;
std::size_t hi = n_bu_-1;
if (scalefactor <= bu_abbrev_v_[lo].first)
return 0;
auto cmp = (scalefactor <=> bu_abbrev_v_[hi].first);
if (cmp > 0)
return n_bu_;
if (cmp == 0)
return hi;
while (hi-lo > 1) {
/* inv:
* bu_abbrev_v[lo].first < scalefactor <= bu_abbrev_v[hi].first
*/
std::size_t mid = lo + (hi - lo)/2;
if (scalefactor > bu_abbrev_v_[mid].first)
lo = mid;
else
hi = mid;
}
return hi;
}
constexpr void insert_aux(std::size_t ix,
const entry_type & entry)
{
if (n_bu_ >= max_bu_per_dim)
return;
++n_bu_;
for (std::size_t dest_ix = n_bu_; dest_ix > ix; --dest_ix)
bu_abbrev_v_[dest_ix] = bu_abbrev_v_[dest_ix - 1];
bu_abbrev_v_[ix] = entry;
}
/** @brief establish abbreviation @p abbrev for basis unit @p bu
**/
constexpr void bu_establish_abbrev(const scalefactor_ratio_type & scalefactor,
const bu_abbrev_type & abbrev)
{
std::int32_t i_abbrev = this->abbrev_lub_ix(scalefactor);
auto entry = std::make_pair(scalefactor, abbrev);
if ((i_abbrev < bu_abbrev_v_.size())
&& (bu_abbrev_v_[i_abbrev].first == scalefactor))
{
bu_abbrev_v_[i_abbrev] = entry;
} else {
this->insert_aux(i_abbrev, entry);
}
}
public:
/** @defgroup bu-dim-store-instance-vars bu-dim-store instance vars **/
///@{
std::size_t n_bu_ = 0;
std::array<entry_type, max_bu_per_dim> bu_abbrev_v_;
///@}
}; /*bu_dim_store*/
/** @class bu_store
* @brief associate basis units with abbreviations
**/
struct bu_store {
/** @defgroup bu-store-constructors bu-store constructors **/
///@{
/** construct canonical instance containing all known basis units **/
constexpr bu_store() {
// ----- mass -----
this->bu_establish_abbrev(detail::bu::picogram, bu_abbrev_type::from_chars("pg"));
this->bu_establish_abbrev(detail::bu::nanogram, bu_abbrev_type::from_chars("ng"));
this->bu_establish_abbrev(detail::bu::microgram, bu_abbrev_type::from_chars("ug"));
this->bu_establish_abbrev(detail::bu::milligram, bu_abbrev_type::from_chars("mg"));
this->bu_establish_abbrev(detail::bu::gram, bu_abbrev_type::from_chars("g"));
this->bu_establish_abbrev(detail::bu::kilogram, bu_abbrev_type::from_chars("kg"));
this->bu_establish_abbrev(detail::bu::tonne, bu_abbrev_type::from_chars("t"));
this->bu_establish_abbrev(detail::bu::kilotonne, bu_abbrev_type::from_chars("kt"));
this->bu_establish_abbrev(detail::bu::megatonne, bu_abbrev_type::from_chars("Mt"));
this->bu_establish_abbrev(detail::bu::gigatonne, bu_abbrev_type::from_chars("Gt"));
// ----- distance -----
this->bu_establish_abbrev(detail::bu::picometer, bu_abbrev_type::from_chars("pm"));
this->bu_establish_abbrev(detail::bu::nanometer, bu_abbrev_type::from_chars("nm"));
this->bu_establish_abbrev(detail::bu::micrometer, bu_abbrev_type::from_chars("um"));
this->bu_establish_abbrev(detail::bu::millimeter, bu_abbrev_type::from_chars("mm"));
this->bu_establish_abbrev(detail::bu::meter, bu_abbrev_type::from_chars("m"));
this->bu_establish_abbrev(detail::bu::kilometer, bu_abbrev_type::from_chars("km"));
this->bu_establish_abbrev(detail::bu::megameter, bu_abbrev_type::from_chars("Mm"));
this->bu_establish_abbrev(detail::bu::gigameter, bu_abbrev_type::from_chars("Gm"));
this->bu_establish_abbrev(detail::bu::lightsecond, bu_abbrev_type::from_chars("lsec"));
this->bu_establish_abbrev(detail::bu::astronomicalunit, bu_abbrev_type::from_chars("AU"));
this->bu_establish_abbrev(detail::bu::inch, bu_abbrev_type::from_chars("in"));
this->bu_establish_abbrev(detail::bu::foot, bu_abbrev_type::from_chars("ft"));
this->bu_establish_abbrev(detail::bu::yard, bu_abbrev_type::from_chars("yd"));
this->bu_establish_abbrev(detail::bu::mile, bu_abbrev_type::from_chars("mi"));
// ----- time -----
this->bu_establish_abbrev(detail::bu::picosecond, bu_abbrev_type::from_chars("ps"));
this->bu_establish_abbrev(detail::bu::nanosecond, bu_abbrev_type::from_chars("ns"));
this->bu_establish_abbrev(detail::bu::microsecond, bu_abbrev_type::from_chars("us"));
this->bu_establish_abbrev(detail::bu::millisecond, bu_abbrev_type::from_chars("ms"));
this->bu_establish_abbrev(detail::bu::second, bu_abbrev_type::from_chars("s"));
this->bu_establish_abbrev(detail::bu::minute, bu_abbrev_type::from_chars("min"));
this->bu_establish_abbrev(detail::bu::hour, bu_abbrev_type::from_chars("hr"));
this->bu_establish_abbrev(detail::bu::day, bu_abbrev_type::from_chars("dy"));
this->bu_establish_abbrev(detail::bu::week, bu_abbrev_type::from_chars("wk"));
this->bu_establish_abbrev(detail::bu::month, bu_abbrev_type::from_chars("mo"));
this->bu_establish_abbrev(detail::bu::year250, bu_abbrev_type::from_chars("yr250"));
this->bu_establish_abbrev(detail::bu::year, bu_abbrev_type::from_chars("yr"));
this->bu_establish_abbrev(detail::bu::year360, bu_abbrev_type::from_chars("yr360"));
this->bu_establish_abbrev(detail::bu::year365, bu_abbrev_type::from_chars("yr365"));
// ----- misc (currency, price) -----
this->bu_establish_abbrev(detail::bu::currency, bu_abbrev_type::from_chars("ccy"));
this->bu_establish_abbrev(detail::bu::price, bu_abbrev_type::from_chars("px"));
}
///@}
/** @defgroup bu-store-implementation-methods **/
///@{
/** report fallback abbreviation for a basis unit.
*
* Typically unused. Will be invoked only if a basis unit exists for which
* @ref bu_store::bu_establish_abbrev was not called by bu_store's constructor.
*
* Tries to produce something unambiguous,
* while still supplying enough information
* to indicate what's needed to resolve the problem.
*
* Example
* @code
* bu_store.bu_fallback_abbrev(dim::mass, scalefactor_ratio_type(1234,1))
* => "1234g"
* @endcode
**/
static constexpr bu_abbrev_type
bu_fallback_abbrev(dim basis_dim,
const scalefactor_ratio_type & scalefactor)
{
return (bu_abbrev_type::from_flatstring
(flatstring_concat
(scalefactor.to_str<bu_abbrev_type::fixed_capacity>(),
native_unit2_v[static_cast<std::uint32_t>(basis_dim)].abbrev_str())));
}
///@}
/** @defgroup bu-store-access-methods **/
///@{
/** @brief get basis-unit abbreviation at runtime **/
constexpr bu_abbrev_type bu_abbrev(const basis_unit & bu) const
{
const auto & bu_abbrev_v = bu_abbrev_vv_[static_cast<std::size_t>(bu.native_dim())];
std::size_t i_abbrev = bu_abbrev_v.abbrev_lub_ix(bu.scalefactor());
if ((i_abbrev < bu_abbrev_v.size())
&& (bu_abbrev_v[i_abbrev].first == bu.scalefactor()))
{
return bu_abbrev_v[i_abbrev].second;
} else {
return bu_fallback_abbrev(bu.native_dim(), bu.scalefactor());
}
}
///@}
/** @addtogroup bu-store-implementation-methods **/
///@{
/** associate abbreviation @p abbrev with basis unit @p bu
*
* Example:
* @code
* // femtograms
* bu_store.bu_establish_abbrev(detail::bu::mass_unit(1, 1000000000000000), "fg")
* @endcode
**/
constexpr void bu_establish_abbrev(const basis_unit & bu,
const bu_abbrev_type & abbrev) {
auto & dim_store = bu_abbrev_vv_[static_cast<std::size_t>(bu.native_dim_)];
dim_store.bu_establish_abbrev(bu.scalefactor_, abbrev);
}
///@}
public: /* ntoe: public members required so bu_dim_store can be a structural type */
/** @defgroup bu-store-instance-vars **/
///@{
/** bu-store contents, indexed by native dimension **/
std::array<bu_dim_store, n_dim> bu_abbrev_vv_;
///@}
};
} /*namespace detail*/
/** @brief global abbreviation store.
*
* @note
* Extending the contents of this store at runtime is not supported,
* in favor of preserving constexpr abbreviations.
**/
static constexpr detail::bu_store bu_abbrev_store;
/** @brief get abbreviation for basis-unit @p bu **/
constexpr bu_abbrev_type
bu_abbrev(const basis_unit & bu)
{
return bu_abbrev_store.bu_abbrev(bu);
}
} /*namespace qty*/
} /*namespace xo*/
/** end bu_store.hpp **/

View file

@ -0,0 +1,21 @@
/** @file dim_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "dimension.hpp"
#include <iostream>
namespace xo {
namespace qty {
inline std::ostream &
operator<<(std::ostream & os, dim x) {
os << dim2str(x);
return os;
}
} /*namespace qty*/
} /*namespace xo*/
/** end dim_iostream.hpp **/

View file

@ -0,0 +1,65 @@
/* @file dimension.hpp */
#pragma once
#include <cstdint>
namespace xo {
namespace qty {
/** @enum dimension
* @brief represent an abstract dimension.
*
* *xo-unit* units are expressed as a cartesian product
* of powers of these dimensions.
**/
enum class dimension {
/** sentinel value. not a dimension **/
invalid = -1,
/** weight. native unit = 1 gram **/
mass,
/** distance. native unit = 1 meter **/
distance,
/** time. native unit = 1 second **/
time,
/** a currency amount. native unit depends on actual currency.
* For USD: one US dollar.
*
* NOTE: multicurrency work not supported by *xo-unit*.
* - (1usd + 1eur) is well-defined.
* - (1sec + 1m) is not.
**/
currency,
/** A screen price.
* The interpretation of prices is highly context dependent;
* expect useful to bucket separately from currenty amounts.
**/
price,
/** not a dimension. comes last, counts entries **/
n_dim
};
using dim = dimension;
/** @brief string value for a dimension enum **/
inline const char *
dim2str(dimension x)
{
switch(x) {
case dimension::mass: return "mass";
case dimension::distance: return "distance";
case dimension::time: return "time";
case dimension::currency: return "currency";
case dimension::price: return "price";
default: break;
}
return "?dim";
}
/** @brief number of built-in dimensions, convenient for array sizing **/
static constexpr std::size_t n_dim = static_cast<std::size_t>(dimension::n_dim);
} /*namespace qty*/
} /*namespace xo*/
/* end dimension.hpp */

View file

@ -0,0 +1,49 @@
/** @file native_unit.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "dimension.hpp"
#include "xo/flatstring/flatstring.hpp"
namespace xo {
namespace qty {
using native_unit2_abbrev_type = flatstring<8>;
/** @class native_unit
*
* @brief Represent a native (built-in) unit.
*
* A basis_unit is expressed as a multiple of a native_unit
*
**/
struct native_unit {
public:
constexpr native_unit(dimension native_dim,
const native_unit2_abbrev_type & abbrev_str)
: native_dim_{native_dim},
abbrev_str_{abbrev_str}
{}
constexpr dimension native_dim() const { return native_dim_; }
constexpr const native_unit2_abbrev_type & abbrev_str() const { return abbrev_str_; }
private:
dimension native_dim_;
native_unit2_abbrev_type abbrev_str_;
};
static constexpr native_unit native_unit2_v[n_dim] = {
native_unit(dimension::mass, native_unit2_abbrev_type::from_chars("g")),
native_unit(dimension::distance, native_unit2_abbrev_type::from_chars("m")),
native_unit(dimension::time, native_unit2_abbrev_type::from_chars("s")),
native_unit(dimension::currency, native_unit2_abbrev_type::from_chars("ccy")),
native_unit(dimension::price, native_unit2_abbrev_type::from_chars("px")),
};
} /*namespace qty*/
} /*namespace xo*/
/** end native_unit.hpp **/

View file

@ -0,0 +1,527 @@
/** @file natural_unit.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "bpu.hpp"
#include <cmath>
#include <cassert>
namespace xo {
namespace qty {
using nu_abbrev_type = flatstring<32>;
template <typename Int>
class natural_unit;
namespace detail {
template <typename Int, typename... Ts>
constexpr void
push_bpu_array(natural_unit<Int> * p_target, Ts... args);
template <typename Int>
constexpr void
push_bpu_array(natural_unit<Int> * p_target) {}
template <typename Int, typename T0, typename... Ts>
constexpr void
push_bpu_array(natural_unit<Int> * p_target, T0 && bpu0, Ts... args) {
p_target->push_back(bpu0);
push_bpu_array(p_target, args...);
}
template <typename Int>
struct nu_maker {
template <typename... Ts>
static constexpr natural_unit<Int>
make_nu(Ts... args) {
natural_unit<Int> bpu_array;
detail::push_bpu_array(&bpu_array, args...);
return bpu_array;
}
};
}
/** @class natural_unit
* @brief an array representing the cartesian product of distinct basis-power-units
*
* 1. Quantities are represented as a multiple of a natural unit
* 2. Each bpu in the array represents a power of a basis dimension, e.g. "meter" or "second^2".
* 3. Each bpu in an array has a different dimension id.
* For example @c dim::time, if present, appears once.
* 4. Basis dimensions can appear in any order.
* Order used for constructing abbreviations: will get @c "kg.m" or @c "m.kg"
* depending on the ordering of @c dim::distance and @c dim::mass in @c bpu_v_
*
* @c Int supplies representation for numerator and denominator in basis-unit scale factors.
**/
template <typename Int>
class natural_unit {
public:
/** @defgroup natural-unit-type-traits natural unit type traits **/
///@{
/** @brief representation for numerator and denominator of scalefactor ratios **/
using ratio_int_type = Int;
///@}
public:
/** @addtogroup natural-unit-ctors **/
///@{
/** construct dimensionless unit **/
constexpr natural_unit() : n_bpu_{0} {}
/** construct unit representing basis unit @p bu with exponent @p power **/
static constexpr natural_unit from_bu(basis_unit bu,
power_ratio_type power = power_ratio_type(1)) {
return detail::nu_maker<Int>::make_nu(bpu<Int>(bu, power));
}
///@}
/** @addtogroup natural-unit-access-methods **/
///@{
/** always true. Provided for symmetry with @c xo::qty::scaled_unit::is_natural **/
constexpr bool is_natural() const { return true; }
/** get member @c n_bpu **/
constexpr std::size_t n_bpu() const { return n_bpu_; }
/** true if this unit has no dimension **/
constexpr bool is_dimensionless() const { return n_bpu_ == 0; }
/** get address of member @c bpu_v **/
constexpr bpu<Int> * bpu_v() const { return bpu_v_; }
///@}
/** @defgroup natural-unit-methods **/
///@{
/** construct reciprocal of this unit.
*
* For example reciprocal of a newton (abbreviation @c "kg.m.s^-2") is
* a unit with abbreviation @c "kg^-1.m^-1.s^2"
**/
constexpr natural_unit reciprocal() const {
natural_unit retval;
for (std::size_t i = 0; i < this->n_bpu(); ++i)
retval.push_back((*this)[i].reciprocal());
return retval;
}
/** abbreviation for this unit.
*
* Apply as suffix when printing quantities involving this unit.
*
* For example @c "mm" for millimeters, or @c "ns" for nanoseconds
**/
constexpr nu_abbrev_type abbrev() const {
nu_abbrev_type retval;
for (std::size_t i = 0; i < n_bpu_; ++i) {
if (i > 0)
retval.append(".");
retval.append(bpu_v_[i].abbrev(), 0, -1);
}
return retval;
}
/** remove bpu at position @p p **/
constexpr void remove_bpu(size_t p) {
for (std::size_t i = p; i+1 < n_bpu_; ++i)
bpu_v_[i] = bpu_v_[i+1];
--n_bpu_;
}
/** append @p bpu to this unit in-place
*
* Require @c bpu.native_dim does not match any existing member of @ref bpu_v_
**/
constexpr void push_back(const bpu<Int> & bpu) {
if (n_bpu_ < n_dim)
bpu_v_[n_bpu_++] = bpu;
}
///@}
/** @addtogroup natural-unit-access-methods **/
///@{
/** get bpu for dimension @p d. if d isn't present, construct bpu with 0 power **/
constexpr bpu<Int> lookup_dim(dimension d) const {
for (std::size_t i = 0, n = n_bpu(); i<n; ++i) {
if (d == bpu_v_[i].native_dim())
return bpu_v_[i];
}
/** not found, return sentinel **/
return bpu<Int>(d, scalefactor_ratio_type(0), power_ratio_type(0));
}
/** get element @p i of @ref bpu_v_ **/
constexpr bpu<Int> & operator[](std::size_t i) { return bpu_v_[i]; }
/** get element @p i of @ref bpu_v_ (const version) **/
constexpr const bpu<Int> & operator[](std::size_t i) const { return bpu_v_[i]; }
///@}
/** @defgroup natural-unit-conversion-methods **/
///@{
/** convert to equivalent unit using scalefactor representation @p Int2 instead of
* @ref ratio_int_type
**/
template <typename Int2>
constexpr natural_unit<Int2> to_repr() const {
natural_unit<Int2> retval;
std::size_t i = 0;
for (; i < n_bpu_; ++i)
retval.push_back(bpu_v_[i].template to_repr<Int2>());
return retval;
}
///@}
public: /* public members so instance can be non-type template parameter (is a structural type) */
/** @defgroup natural-unit-instance-vars **/
///@{
/** the number of occupied slots in @c bpu_v_ **/
std::size_t n_bpu_;
/** storage for basis power units **/
bpu<Int> bpu_v_[n_dim];
///@}
};
/** @defgroup natural-unit-comparison-functions natural-unit comparison functions **/
///@{
/** compare natural units @p x, @p y for equality. **/
template <typename Int>
constexpr bool
operator==(const natural_unit<Int> & x,
const natural_unit<Int> & y)
{
if (x.n_bpu() != y.n_bpu())
return false;
/* does x contain any dimension that isn't present in y? */
for (std::size_t i = 0, n = x.n_bpu(); i<n; ++i) {
const bpu<Int> & xi = x[i];
if (xi != y.lookup_dim(xi.native_dim()))
return false;
}
/* if all bpu's x[i] match something from y, then x,y must be equal
* since they each have the same number of bpu's
*/
return true;
}
/** compare natural units @p x, @p y for inequality **/
template <typename Int>
constexpr bool
operator!=(const natural_unit<Int> & x,
const natural_unit<Int> & y) {
return !(x == y);
}
///@}
namespace detail {
/**
* Given bpu ~ (b.u)^p:
* - b = bpu.scalefactor
* - u = bpu.native_dim
* - p = bpu.power
*
* want to rewrite in the form a'.(b'.u)^p
*
* Can compute a' exactly iff p is integral.
* In that case:
* (b.u)^p = ((b/b').b'.u)^p
* = (b/b')^p.(b'.u)^p
* = a'.(b'.u)^p with a' = (b/b')^p
*
* Can write p = p0 + q, with p0 = floor(p) integral, q = frac(p) in [0,1)
*
* Then
* (b/b')^p = (b/b')^p0 * (b/b')^q
*
* we'll compute:
* - (b/b')^p0 exactly (as a ratio)
* - (b/b')^q inexactly (as a double)
**/
template <typename Int,
typename OuterScale = ratio::ratio<Int> >
struct outer_scalefactor_result {
constexpr outer_scalefactor_result(const OuterScale & outer_scale_factor,
double outer_scale_sq)
: outer_scale_factor_{outer_scale_factor},
outer_scale_sq_{outer_scale_sq} {}
/* (b/b')^p0 */
OuterScale outer_scale_factor_;
/* (b/b')^q -- until c++26 only allow q=0 or q=1/2 */
double outer_scale_sq_;
};
template <typename Int,
typename OuterScale = ratio::ratio<Int> >
struct bpu2_rescale_result {
constexpr bpu2_rescale_result(const bpu<Int> & bpu_rescaled,
const OuterScale & outer_scale_factor,
double outer_scale_sq)
: bpu_rescaled_{bpu_rescaled},
outer_scale_factor_{outer_scale_factor},
outer_scale_sq_{outer_scale_sq}
{}
/* (b'.u)^p */
bpu<Int> bpu_rescaled_;
/* (b/b')^p0 */
OuterScale outer_scale_factor_;
/* [(b/b')^q]^2 -- until c++26 only allow q=0 or q=1/2 */
double outer_scale_sq_;
};
template < typename Int,
typename OuterScale = ratio::ratio<Int> >
constexpr
bpu2_rescale_result<Int, OuterScale>
bpu2_rescale(const bpu<Int> & orig,
const scalefactor_ratio_type & new_scalefactor)
{
/* we have orig, representing qty (b.u)^p,
* with b=orig.scalefactor, u=native dimension, p=orig.power
*/
ratio::ratio<Int> mult = (orig.scalefactor() / new_scalefactor);
/* inv: p_frac in (-1, 1) */
auto p_frac = orig.power().frac();
/* asof c++26: replace mult_sq with ::pow(mult, p_frac) */
double mult_sq = std::numeric_limits<double>::quiet_NaN();
/* pre-c++26 workaround */
{
if (p_frac.den() == 1) {
mult_sq = 1.0;
} else if(p_frac.num() == 1 && p_frac.den() == 2) {
mult_sq = mult.template convert_to<double>();
} else if(p_frac.num() == -1 && p_frac.den() == 2) {
mult_sq = 1.0 / mult.template convert_to<double>();
} else {
// remaining possibilities not supported until c++26
}
}
ratio::ratio<Int> mult_p = mult.power(orig.power().floor());
return bpu2_rescale_result<Int, OuterScale>(bpu<Int>(orig.native_dim(),
new_scalefactor,
orig.power()),
mult_p.template convert_to<OuterScale>(),
mult_sq);
}
template < typename Int,
typename OuterScale >
constexpr
outer_scalefactor_result<Int, OuterScale>
bpu_product_inplace(bpu<Int> * p_target_bpu,
const bpu<Int> & rhs_bpu_orig)
{
assert(rhs_bpu_orig.native_dim() == p_target_bpu->native_dim());
bpu2_rescale_result<Int, OuterScale> rhs_bpu_rr
= bpu2_rescale<Int, OuterScale>(rhs_bpu_orig,
p_target_bpu->scalefactor().template convert_to<OuterScale>());
*p_target_bpu = bpu<Int>(p_target_bpu->native_dim(),
p_target_bpu->scalefactor(),
p_target_bpu->power() + rhs_bpu_orig.power());
return outer_scalefactor_result<Int>(rhs_bpu_rr.outer_scale_factor_,
rhs_bpu_rr.outer_scale_sq_);
}
template < typename Int,
typename OuterScale >
constexpr
outer_scalefactor_result<Int, OuterScale>
bpu_ratio_inplace(bpu<Int> * p_target_bpu,
const bpu<Int> & rhs_bpu_orig)
{
assert(rhs_bpu_orig.native_dim() == p_target_bpu->native_dim());
bpu2_rescale_result<Int, OuterScale> rhs_bpu_rr
= bpu2_rescale<Int, OuterScale>(rhs_bpu_orig,
p_target_bpu->scalefactor());
*p_target_bpu = bpu<Int>(p_target_bpu->native_dim(),
p_target_bpu->scalefactor(),
p_target_bpu->power() - rhs_bpu_orig.power());
return outer_scalefactor_result<Int, OuterScale>
(OuterScale(1) / rhs_bpu_rr.outer_scale_factor_,
1.0 / rhs_bpu_rr.outer_scale_sq_);
}
template < typename Int,
typename OuterScale >
constexpr
outer_scalefactor_result<Int, OuterScale>
nu_product_inplace(natural_unit<Int> * p_target,
const bpu<Int> & bpu)
{
std::size_t i = 0;
for (; i < p_target->n_bpu(); ++i) {
auto * p_target_bpu = &((*p_target)[i]);
if (p_target_bpu->native_dim() == bpu.native_dim()) {
outer_scalefactor_result<Int, OuterScale> retval
= bpu_product_inplace<Int, OuterScale>(p_target_bpu, bpu);
if (p_target_bpu->power().is_zero()) {
/* dimension assoc'd with *p_target_bpu has been cancelled */
p_target->remove_bpu(i);
}
return retval;
}
}
/* control here: i=p_target->n_bpu()
* Dimension represented by bpu does not already appear in *p_target.
* Adopt bpu's scalefactor
*/
p_target->push_back(bpu);
return outer_scalefactor_result<Int, OuterScale>
(OuterScale(1) /*outer_scale_factor*/,
1.0 /*outer_scale_sq*/);
}
template < typename Int,
typename OuterScale = ratio::ratio<Int> >
constexpr
outer_scalefactor_result<Int, OuterScale>
nu_ratio_inplace(natural_unit<Int> * p_target,
const bpu<Int> & bpu)
{
std::size_t i = 0;
for (; i < p_target->n_bpu(); ++i) {
auto * p_target_bpu = &((*p_target)[i]);
if (p_target_bpu->native_dim() == bpu.native_dim()) {
outer_scalefactor_result<Int, OuterScale> retval
= bpu_ratio_inplace<Int, OuterScale>(p_target_bpu, bpu);
if (p_target_bpu->power().is_zero()) {
/* dimension assoc'd with *p_target_bpu has been cancelled */
p_target->remove_bpu(i);
}
return retval;
}
}
/* here: i=p_target->n_bpu()
* Dimension represented by bpu does not already appear in *p_target.
* Adopt bpu's scalefactor
*/
p_target->push_back(bpu.reciprocal());
return outer_scalefactor_result<Int, OuterScale>
(OuterScale(1) /*outer_scale_factor*/,
1.0 /*outer_scale_sq*/);
}
} /*namespace detail*/
/** @brief namespace for constants representing basis natural units
*
* Application code will typically use instead parallel scaled-unit constants
* (see the 'u' namespace in 'scaled_unit.hpp')
**/
namespace nu {
constexpr auto dimensionless = natural_unit<std::int64_t>();
// ----- mass -----
constexpr auto picogram = natural_unit<std::int64_t>::from_bu(detail::bu::picogram);
constexpr auto nanogram = natural_unit<std::int64_t>::from_bu(detail::bu::nanogram);
constexpr auto microgram = natural_unit<std::int64_t>::from_bu(detail::bu::microgram);
constexpr auto milligram = natural_unit<std::int64_t>::from_bu(detail::bu::milligram);
constexpr auto gram = natural_unit<std::int64_t>::from_bu(detail::bu::gram);
constexpr auto kilogram = natural_unit<std::int64_t>::from_bu(detail::bu::kilogram);
constexpr auto tonne = natural_unit<std::int64_t>::from_bu(detail::bu::tonne);
constexpr auto kilotonne = natural_unit<std::int64_t>::from_bu(detail::bu::kilotonne);
constexpr auto megatonne = natural_unit<std::int64_t>::from_bu(detail::bu::megatonne);
constexpr auto gigatonne = natural_unit<std::int64_t>::from_bu(detail::bu::gigatonne);
// ----- distance -----
constexpr auto picometer = natural_unit<std::int64_t>::from_bu(detail::bu::picometer);
constexpr auto nanometer = natural_unit<std::int64_t>::from_bu(detail::bu::nanometer);
constexpr auto micrometer = natural_unit<std::int64_t>::from_bu(detail::bu::micrometer);
constexpr auto millimeter = natural_unit<std::int64_t>::from_bu(detail::bu::millimeter);
constexpr auto meter = natural_unit<std::int64_t>::from_bu(detail::bu::meter);
constexpr auto kilometer = natural_unit<std::int64_t>::from_bu(detail::bu::kilometer);
constexpr auto megameter = natural_unit<std::int64_t>::from_bu(detail::bu::megameter);
constexpr auto gigameter = natural_unit<std::int64_t>::from_bu(detail::bu::gigameter);
constexpr auto lightsecond = natural_unit<std::int64_t>::from_bu(detail::bu::lightsecond);
constexpr auto astronomicalunit = natural_unit<std::int64_t>::from_bu(detail::bu::astronomicalunit);
constexpr auto inch = natural_unit<std::int64_t>::from_bu(detail::bu::inch);
constexpr auto foot = natural_unit<std::int64_t>::from_bu(detail::bu::foot);
constexpr auto yard = natural_unit<std::int64_t>::from_bu(detail::bu::yard);
constexpr auto mile = natural_unit<std::int64_t>::from_bu(detail::bu::mile);
// ----- time -----
constexpr auto picosecond = natural_unit<std::int64_t>::from_bu(detail::bu::picosecond);
constexpr auto nanosecond = natural_unit<std::int64_t>::from_bu(detail::bu::nanosecond);
constexpr auto microsecond = natural_unit<std::int64_t>::from_bu(detail::bu::microsecond);
constexpr auto millisecond = natural_unit<std::int64_t>::from_bu(detail::bu::millisecond);
constexpr auto second = natural_unit<std::int64_t>::from_bu(detail::bu::second);
constexpr auto minute = natural_unit<std::int64_t>::from_bu(detail::bu::minute);
constexpr auto hour = natural_unit<std::int64_t>::from_bu(detail::bu::hour);
constexpr auto day = natural_unit<std::int64_t>::from_bu(detail::bu::day);
constexpr auto week = natural_unit<std::int64_t>::from_bu(detail::bu::week);
constexpr auto month = natural_unit<std::int64_t>::from_bu(detail::bu::month);
constexpr auto year = natural_unit<std::int64_t>::from_bu(detail::bu::year);
constexpr auto year250 = natural_unit<std::int64_t>::from_bu(detail::bu::year250);
constexpr auto year360 = natural_unit<std::int64_t>::from_bu(detail::bu::year360);
constexpr auto year365 = natural_unit<std::int64_t>::from_bu(detail::bu::year365);
constexpr auto currency = natural_unit<std::int64_t>::from_bu(detail::bu::currency);
constexpr auto price = natural_unit<std::int64_t>::from_bu(detail::bu::price);
constexpr auto volatility_30d = natural_unit<std::int64_t>::from_bu(detail::bu::month, power_ratio_type(-1,2));
constexpr auto volatility_250d = natural_unit<std::int64_t>::from_bu(detail::bu::year250, power_ratio_type(-1,2));
constexpr auto volatility_360d = natural_unit<std::int64_t>::from_bu(detail::bu::year360, power_ratio_type(-1,2));
constexpr auto volatility_365d = natural_unit<std::int64_t>::from_bu(detail::bu::year365, power_ratio_type(-1,2));
} /*namespace nu*/
} /*namespace qty*/
} /*namespace xo*/
/** end natural_unit.hpp **/

View file

@ -0,0 +1,29 @@
/** @file natural_unit_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "natural_unit.hpp"
#include "bpu_iostream.hpp"
#include <iostream>
namespace xo {
namespace qty {
template <typename Int>
inline std::ostream &
operator<<(std::ostream & os, const natural_unit<Int> & x) {
os << "<natural-unit [";
for (std::size_t i=0; i<x.n_bpu(); ++i) {
if (i > 0)
os << ", ";
os << x[i];
}
os << "]>";
return os;
}
} /*namespace qty*/
} /*namespace xo*/
/** end natural_unit_iostream.hpp **/

View file

@ -0,0 +1,34 @@
/* @file numeric_concept.hpp */
#pragma once
#include <concepts>
namespace xo {
namespace qty {
/** @concept numeric_concept
* @brief Concept for values that participate in arithmetic operations (+,-,*,/) and comparisons
*
* Intended to include at least:
* - built-in integral and floating-point types
* - xo::raio<U>
* - xo::unit::quantity<U,R>
*
* Intend numeric_concept to apply to types suitable for
* xo::unit::quantity::repr_type.
**/
template <typename T, typename U = T>
concept numeric_concept = requires(T x, U y)
{
{ -x };
{ x - y };
{ x + y };
{ x * y };
{ x / y };
{ x == y };
{ x != y };
};
} /*namespace qty*/
} /*namespace xo*/
/* end numeric_concept.hpp */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
/** @file quantity_concept.hpp **/
#pragma once
//#include "unit_concept.hpp"
#include "numeric_concept.hpp"
namespace xo {
namespace qty {
template <typename Quantity>
concept quantity_concept = requires(Quantity qty, typename Quantity::repr_type repr)
{
typename Quantity::unit_type;
typename Quantity::repr_type;
//{ Quantity::multiply(qty, qty) };
{ qty.scale() } -> std::same_as<const typename Quantity::repr_type &>;
{ qty.unit() } -> std::same_as<const typename Quantity::unit_type &>;
//{ Quantity::unit_cstr() } -> std::same_as<char const *>;
//{ Quantity::unit_quantity() } -> std::same_as<Quantity>;
//{ Quantity::promote(repr) } -> std::same_as<Quantity>;
} && (true //unit_concept<typename Quantity::unit_type>
&& numeric_concept<typename Quantity::repr_type>);
} /*namespace qty*/
} /*namespace xo*/
/* end quantity_concept.hpp */

View file

@ -0,0 +1,26 @@
/** @file quantity_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "quantity.hpp"
#include "natural_unit_iostream.hpp"
namespace xo {
namespace qty {
template < auto NaturalUnit, typename Repr >
inline std::ostream &
operator<< (std::ostream & os,
const quantity<NaturalUnit, Repr> & x)
{
os << x.scale() << x.abbrev();
return os;
}
} /*namespace qty*/
} /*namespace xo*/
/** end quantity_iostream.hpp **/

View file

@ -0,0 +1,37 @@
/** @file quantity_ops.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "quantity_concept.hpp"
namespace xo {
namespace qty {
/** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr)
**/
template <typename Quantity, typename Quantity2>
requires quantity_concept<Quantity> && quantity_concept<Quantity2>
constexpr auto
operator== (const Quantity & x, const Quantity2 & y)
{
return (Quantity::compare(x, y) == 0);
}
/** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr)
**/
template <typename Quantity, typename Quantity2>
requires quantity_concept<Quantity> && quantity_concept<Quantity2>
constexpr auto
operator<=> (const Quantity & x, const Quantity2 & y)
{
return Quantity::compare(x, y);
}
} /*namespace qty*/
} /*namespace xo*/
/** end quantity_ops.hpp **/

View file

@ -0,0 +1,401 @@
/** @file scaled_unit.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "width2x.hpp"
namespace xo {
namespace qty {
/** @class scaled_unit
* @brief Represents the product sqrt(outer_scale_sq) * outer_scale_exact * nat_unit
**/
template < typename Int,
typename OuterScale = ratio::ratio<Int> >
struct scaled_unit {
/** @defgroup scaled-unit-type-traits scaled-unit type traits **/
///@{
/** type for representing individual basis-unit scalefactors **/
using ratio_int_type = typename natural_unit<Int>::ratio_int_type;
///@}
public:
/** @defgroup scaled-unit-ctors scaled-unit constructors **/
///@{
/** create scaled unit representing a multiple
* @p outer_scale_factor * @p sqrt(outer_scale_sq)
* of natural unit @p nat_unit
**/
constexpr scaled_unit(const natural_unit<Int> & nat_unit,
OuterScale outer_scale_factor,
double outer_scale_sq)
: natural_unit_{nat_unit},
outer_scale_factor_{outer_scale_factor},
outer_scale_sq_{outer_scale_sq}
{}
///@}
/** @defgroup scaled-unit-access-methods scaled-unit access methods **/
///@{
/** always true for scaled_unit **/
static constexpr bool is_scaled_unit_type_v = true;
/** always true for scaled_unit **/
constexpr bool is_scaled_unit_type() const { return true; }
/** true iff scaled unit can be faithfully represented by a @ref natural_unit **/
constexpr bool is_natural() const {
return (outer_scale_factor_ == OuterScale(1) && (outer_scale_sq_ == 1.0));
}
/** true if this scaled unit has no dimension **/
constexpr bool is_dimensionless() const { return natural_unit_.is_dimensionless(); }
/** get number of distinct native dimensions present.
* e.g. for unit Newton = 1 kg.m.s^-2, n_bpu would be 3,
* with {mass, distance, time} present.
* Note that this value does not count exponents
**/
constexpr std::size_t n_bpu() const { return natural_unit_.n_bpu(); }
///@}
/** @defgroup scaled-unit-general-methods scaled-unit access methods **/
///@{
/** return reciprocal of this unit. **/
constexpr scaled_unit reciprocal() const {
return scaled_unit(natural_unit_.reciprocal(),
1 / outer_scale_factor_,
1.0 / outer_scale_sq_);
}
/** get bpu for dimension @p d. if d isn't present, construct bpu with 0 power **/
constexpr bpu<Int> lookup_dim(dimension d) const {
return natural_unit_.lookup_dim(d);
}
/** return @p i'th bpu associated with this unit **/
constexpr bpu<Int> & operator[](std::size_t i) { return natural_unit_[i]; }
/** return @p i'th bpu associated with this unit (const version) **/
constexpr const bpu<Int> & operator[](std::size_t i) const { return natural_unit_[i]; }
///@}
public: /* public members so scaled_unit instance can be a non-type template parameter (a structural type) */
/** @defgroup scaled-unit-instance-vars **/
///@{
/** scale factor multiplying @ref natural_unit_ **/
OuterScale outer_scale_factor_;
/** squared scale factor multiplying @ref natural_unit_ **/
double outer_scale_sq_;
/** natural unit term in this scaled unit **/
natural_unit<Int> natural_unit_;
///@}
};
// TODO: comparison operators
namespace detail {
/** promote natural unit to scaled unit (with unit outer scalefactors) **/
template <typename Int>
constexpr auto su_promote(const natural_unit<Int> & bpuv) {
return scaled_unit<Int>(bpuv,
ratio::ratio<Int>(1, 1),
1.0);
}
}
namespace u {
/* values here can be used as template arguments to quantity:
* e.g.
* quantity<u:picogram> qty1;
* quantity<u:meter/u:second> velocity;
*/
constexpr auto
su_from_bu(const basis_unit & bu,
const power_ratio_type & power = power_ratio_type(1))
{
return detail::su_promote(natural_unit<std::int64_t>::from_bu(bu, power));
}
/** @defgroup scaled-unit-dimensionless scaled-unit dimensionless constant **/
///@{
/** dimensionless unit; equivalent to 1 **/
constexpr auto dimensionless = detail::su_promote(natural_unit<std::int64_t>());
///@}
// ----- mass units -----
/** @defgroup scaled-unit-mass scaled-unit mass units **/
///@{
/** unit of 10^-12 grams **/
constexpr auto picogram = su_from_bu(detail::bu::picogram);
/** unit of 10^-9 grams **/
constexpr auto nanogram = su_from_bu(detail::bu::nanogram);
/** unit of 10^-6 grams **/
constexpr auto microgram = su_from_bu(detail::bu::microgram);
/** unit of 10^-3 grams **/
constexpr auto milligram = su_from_bu(detail::bu::milligram);
/** unit of 1 gram **/
constexpr auto gram = su_from_bu(detail::bu::gram);
/** unit of 10^3 grams **/
constexpr auto kilogram = su_from_bu(detail::bu::kilogram);
/** unit of 1 metric tonne = 10^3 kg **/
constexpr auto tonne = su_from_bu(detail::bu::tonne);
/** unit of 10^3 tonnes = 10^6 kg **/
constexpr auto kilotonne = su_from_bu(detail::bu::kilotonne);
/** unit of 10^6 tonnes = 10^9 kg **/
constexpr auto megatonne = su_from_bu(detail::bu::megatonne);
/** unit of 10^9 tonnes = 10^12 kg **/
constexpr auto gigatonne = su_from_bu(detail::bu::gigatonne);
///@}
// ----- distance units -----
/** @defgroup scaled-unit-distance scaled-unit distance units **/
///@{
/** unit of 10^-12 meters **/
constexpr auto picometer = su_from_bu(detail::bu::picometer);
/** unit of 10^-9 meters **/
constexpr auto nanometer = su_from_bu(detail::bu::nanometer);
/** unit of 10^-6 meters **/
constexpr auto micrometer = su_from_bu(detail::bu::micrometer);
/** unit of 10^-3 meters **/
constexpr auto millimeter = su_from_bu(detail::bu::millimeter);
/** unit of 1 meter **/
constexpr auto meter = su_from_bu(detail::bu::meter);
/** unit of 10^3 meters **/
constexpr auto kilometer = su_from_bu(detail::bu::kilometer);
/** unit of 10^6 meters (not commonly used) **/
constexpr auto megameter = su_from_bu(detail::bu::megameter);
/** unit of 10^9 meters (not commonly used) **/
constexpr auto gigameter = su_from_bu(detail::bu::gigameter);
/** unit of 1 light-second = distance light travels in a vacuum in 1 second **/
constexpr auto lightsecond = su_from_bu(detail::bu::lightsecond);
/** unit of 1 astronomical unit, for approximate radius of earth orbit **/
constexpr auto astronomicalunit = su_from_bu(detail::bu::astronomicalunit);
/** unit of 1 inch = 1/12 feet **/
constexpr auto inch = su_from_bu(detail::bu::inch);
/** unit of 1 foot = 0.3048 meters **/
constexpr auto foot = su_from_bu(detail::bu::foot);
/** unit of 1 yard = 3 feet **/
constexpr auto yard = su_from_bu(detail::bu::yard);
/** unit of 1 mile = 1760 yards **/
constexpr auto mile = su_from_bu(detail::bu::mile);
///@}
// ----- time units -----
/** @defgroup scaled-unit-time scaled-unit time units **/
///@{
/** unit of 1 picosecond = 10^-12 seconds **/
constexpr auto picosecond = su_from_bu(detail::bu::picosecond);
/** unit of 1 nanosecond = 10^-9 seconds **/
constexpr auto nanosecond = su_from_bu(detail::bu::nanosecond);
/** unit of 1 microseccond = 10^-6 seconds **/
constexpr auto microsecond = su_from_bu(detail::bu::microsecond);
/** unit of 1 millisecond = 10^-3 seconds **/
constexpr auto millisecond = su_from_bu(detail::bu::millisecond);
/** unit of 1 second **/
constexpr auto second = su_from_bu(detail::bu::second);
/** unit of 1 minute **/
constexpr auto minute = su_from_bu(detail::bu::minute);
/** unit of 1 hour **/
constexpr auto hour = su_from_bu(detail::bu::hour);
/** unit for a 24-hour day **/
constexpr auto day = su_from_bu(detail::bu::day);
/** unit for a week comprising exactly 7 24-hour days **/
constexpr auto week = su_from_bu(detail::bu::week);
/** unit for a 30-day month **/
constexpr auto month = su_from_bu(detail::bu::month);
/** unit for a year containing exactly 365.25 24-hour days **/
constexpr auto year = su_from_bu(detail::bu::year);
/** unit for a 'year' containing exactly 250 24-hour days.
* (approximates the number of business days in a year)
**/
constexpr auto year250 = su_from_bu(detail::bu::year250);
/** unit for a 'year' containing exactly 360 24-hour days **/
constexpr auto year360 = su_from_bu(detail::bu::year360);
/** unit for a 'year' containing exactly 365 24-hour days **/
constexpr auto year365 = su_from_bu(detail::bu::year365);
///@}
/** @defgroup scaled-unit-misc scaled-unit miscellaneous units **/
///@{
// ----- currency -----
/** generic currency unit **/
constexpr auto currency = su_from_bu(detail::bu::currency);
// ----- price - ---
/** generic price unit **/
constexpr auto price = su_from_bu(detail::bu::price);
///@}
// ----- volatility units -----
/** @defgroup scaled-unit-volatility scaled-unit volatility units **/
///@{
/** volatility, in 30-day units **/
constexpr auto volatility_30d = su_from_bu(detail::bu::month,
power_ratio_type(-1,2));
/** volatility, in 250-day 'annual' units **/
constexpr auto volatility_250d = su_from_bu(detail::bu::year250,
power_ratio_type(-1,2));
/** volatility, in 360-day 'annual' units **/
constexpr auto volatility_360d = su_from_bu(detail::bu::year360,
power_ratio_type(-1,2));
/** volatility, in 365-day 'annual' units **/
constexpr auto volatility_365d = su_from_bu(detail::bu::year365,
power_ratio_type(-1,2));
///@}
}
namespace detail {
template <typename Int,
typename Int2x = width2x<Int>,
typename OuterScale = ratio::ratio<Int2x>>
constexpr
scaled_unit<Int, OuterScale>
su_product(const natural_unit<Int> & lhs_bpu_array,
const natural_unit<Int> & rhs_bpu_array)
{
natural_unit<Int2x> prod = lhs_bpu_array.template to_repr<Int2x>();
/* accumulate product of scalefactors spun off by rescaling
* any basis-units in rhs_bpu_array that conflict with the same dimension
* in lh_bpu_array
*/
auto sfr = (detail::outer_scalefactor_result<Int2x>
(OuterScale(1) /*outer_scale_factor*/,
1.0 /*outer_scale_sq*/));
for (std::size_t i = 0; i < rhs_bpu_array.n_bpu(); ++i) {
auto sfr2 = nu_product_inplace<Int2x, OuterScale>(&prod,
rhs_bpu_array[i].template to_repr<Int2x>());
sfr.outer_scale_factor_ = sfr.outer_scale_factor_ * sfr2.outer_scale_factor_;
sfr.outer_scale_sq_ *= sfr2.outer_scale_sq_;
}
return scaled_unit<Int, OuterScale>(prod.template to_repr<Int>(),
sfr.outer_scale_factor_,
sfr.outer_scale_sq_);
}
/* use Int2x to accumulate scalefactor
*/
template < typename Int,
typename Int2x = width2x<Int>,
typename OuterScale = ratio::ratio<Int2x> >
constexpr
scaled_unit<Int, OuterScale>
su_ratio(const natural_unit<Int> & nu_lhs,
const natural_unit<Int> & nu_rhs)
{
natural_unit<Int2x> ratio = nu_lhs.template to_repr<Int2x>();
/* accumulate product of scalefactors spun off by rescaling
* any basis-units in rhs_bpu_array that conflict with the same dimension
* in lh_bpu_array
*/
auto sfr = (detail::outer_scalefactor_result<Int2x, OuterScale>
(OuterScale(1) /*outer_scale_factor*/,
1.0 /*outer_scale_sq*/));
for (std::size_t i = 0; i < nu_rhs.n_bpu(); ++i) {
auto sfr2 = nu_ratio_inplace<Int2x, OuterScale>(&ratio,
nu_rhs[i].template to_repr<Int2x>());
/* note: nu_ratio_inplace() reports multiplicative outer scaling factors,
* so multiply is correct here
*/
sfr.outer_scale_factor_ = (sfr.outer_scale_factor_
* sfr2.outer_scale_factor_);
sfr.outer_scale_sq_ *= sfr2.outer_scale_sq_;
}
return scaled_unit<Int, OuterScale>(ratio.template to_repr<Int>(),
sfr.outer_scale_factor_,
sfr.outer_scale_sq_);
}
}
/** @defgroup scaled-unit-operators **/
///@{
/** Multiply scaled_unit instances @p x_unit and @p y_unit.
* Result is a scaled_unit for the product dimension.
* For each basis dimension, result will prioritize scale from @p x_unit ahead of @p y_unit.
**/
template <typename Int,
typename Int2x = detail::width2x_t<Int>>
inline constexpr scaled_unit<Int>
operator* (const scaled_unit<Int> & x_unit,
const scaled_unit<Int> & y_unit)
{
auto rr = detail::su_product<Int, Int2x>(x_unit.natural_unit_,
y_unit.natural_unit_);
return (scaled_unit<Int>
(rr.natural_unit_,
(ratio::ratio<Int2x>(rr.outer_scale_factor_)
* ratio::ratio<Int2x>(x_unit.outer_scale_factor_)
* ratio::ratio<Int2x>(y_unit.outer_scale_factor_)),
rr.outer_scale_sq_ * x_unit.outer_scale_sq_ * y_unit.outer_scale_sq_));
}
/** Divide scaled_unit instances @p x_unit by @p y_unit.
* Result is a scaled_unit for the quotient dimension.
* For each basis dimension, result will prioritize scale from @p x_unit ahead of @p y_unit.
**/
template <typename Int,
typename Int2x = detail::width2x_t<Int>>
inline constexpr scaled_unit<Int>
operator/ (const scaled_unit<Int> & x_unit,
const scaled_unit<Int> & y_unit)
{
auto rr = detail::su_ratio<Int, Int2x>(x_unit.natural_unit_,
y_unit.natural_unit_);
return (scaled_unit<Int>
(rr.natural_unit_,
(ratio::ratio<Int2x>(rr.outer_scale_factor_)
* ratio::ratio<Int2x>(x_unit.outer_scale_factor_)
* ratio::ratio<Int2x>(y_unit.outer_scale_factor_)),
rr.outer_scale_sq_ * x_unit.outer_scale_sq_ * y_unit.outer_scale_sq_));
}
///@}
} /*namespace qty*/
} /*namespace xo*/
/** end scaled_unit.hpp **/

View file

@ -0,0 +1,23 @@
/** @file scaled_unit_concept.hpp **/
#pragma once
#include <concepts>
namespace xo {
namespace qty {
template <typename ScaledUnit>
concept scaled_unit_concept = requires(ScaledUnit su)
{
typename ScaledUnit::ratio_int_type;
{ su.is_scaled_unit_type() } -> std::same_as<bool>;
{ su.is_natural() } -> std::same_as<bool>;
{ su.is_dimensionless() } -> std::same_as<bool>;
} && ScaledUnit::is_scaled_unit_type_v;
} /*namespace qty*/
} /*namespace xo*/
/** end scaled_unit_concept.hpp **/

View file

@ -0,0 +1,31 @@
/** @file scaled_unit_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "scaled_unit.hpp"
#include "natural_unit_iostream.hpp"
#include "xo/flatstring/int128_iostream.hpp"
#include <iostream>
namespace xo {
namespace qty {
template <typename Int, typename OuterScale>
inline std::ostream &
operator<<(std::ostream & os,
const scaled_unit<Int, OuterScale> & x)
{
os << "<scaled-unit"
<< xtag("outer_scale_factor", x.outer_scale_factor_)
<< xtag("outer_scale_sq", x.outer_scale_sq_)
<< xtag("bpuv", x.natural_unit_)
<< ">";
return os;
};
} /*namespace qty*/
} /*namespace xo*/
/** end scaled_unit_iostream.hpp **/

View file

@ -0,0 +1,24 @@
/** @file unit2.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "natural_unit.hpp"
namespace xo {
namespace qty {
/** @class unit2
* @brief represent an arbitrary unit along with dimension details
*
* For example,
* kg.m.s^-2 or
* (kilogram * meter) / (second * second)
**/
template <typename Int>
using unit2 = natural_unit<Int>;
} /*namespace qty*/
} /*namespace xo*/
/** end unit2.hpp **/

View file

@ -0,0 +1,39 @@
/** @file width2x.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "natural_unit.hpp"
#include <cstdint>
namespace xo {
namespace qty {
namespace detail {
template <typename Int>
struct width2x;
template <>
struct width2x<std::int16_t> {
using type = std::int32_t;
};
template <>
struct width2x<std::int32_t> {
using type = std::int64_t;
};
template <>
struct width2x<std::int64_t> {
using type = __int128_t;
};
template <typename Int>
using width2x_t = width2x<Int>::type;
}
} /*namespace qty*/
} /*namespace xo*/
/** end width2x.hpp **/

View file

@ -0,0 +1,562 @@
/** @file xquantity.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "quantity_ops.hpp"
#include "scaled_unit.hpp"
#include "natural_unit.hpp"
namespace xo {
namespace qty {
/** @class xquantity
* @brief represent a scalar quantity with polymorphic units.
*
* - @p Repr type used represent a dimensionless multiple of a natural unit.
*
* Constexpr implementation, but units are explicitly represented:
* @code
* sizeof(xquantity) > sizeof(xquantity::repr_type)
* @endcode
*
* Explicit unit representation allows introducing units at runtime,
* for example in python bindings.
* See for example <a href="https://github.com/rconybea/xo-pyutil">xo-pyutil</a>
*
* See @ref quantity for implementation with units established at compile time
*
* Require:
* - Repr supports numeric operations (+, -, *, /)
* - Repr supports conversion from double.
**/
template <typename Repr = double,
typename Int = std::int64_t>
class xquantity {
public:
/** @defgroup xquantity-type-traits xquantity type traits **/
///@{
/** @brief runtime representation for this value's scale **/
using repr_type = Repr;
/** @brief runtime representation for this value's unit **/
using unit_type = natural_unit<Int>;
/** @brief type used for numerator and denominator in basis-unit scalefactor ratios **/
using ratio_int_type = Int;
/** @brief double-width type for numerator and denominator of intermediate
* scalefactor ratios. Use to mitigate loss of precision during computation
* of conversion factors between units with widely-differing magnitude
**/
using ratio_int2x_type = detail::width2x_t<typename unit_type::ratio_int_type>;
///@}
public:
/** @defgroup xquantity-ctors xquantity constructors **/
///@{
/** create dimensionless, zero quantity **/
constexpr xquantity()
: scale_{0}, unit_{natural_unit<Int>()} {}
/** create quantity representing multiple of @p scale times @p unit **/
constexpr xquantity(Repr scale,
const natural_unit<Int> & unit)
: scale_{scale}, unit_{unit} {}
/** create quantity representing multiple of @p scale times @p unit.
*
* Collects outer scalefactors (if any) from @p unit,
* so for example:
*
* @code
* using namespace xo::qty;
* xquantity q(123, u::meter * u::millimeter);
*
* q.scale() --> 0.123
* @endcode
**/
constexpr xquantity(Repr scale,
const scaled_unit<Int> & unit)
:
scale_(scale
* unit.outer_scale_factor_.template convert_to<double>()
* ((unit.outer_scale_sq_ == 1.0)
? 1.0
: ::sqrt(unit.outer_scale_sq_))
),
unit_{unit.natural_unit_} {}
///@}
/** @defgroup xquantity-constants static xquantity constants **/
///@{
/** false since unit information may be unknown at compile time.
* Coordinates with @c quantity::always_constexpr_unit
**/
static constexpr bool always_constexpr_unit = false;
///@}
/** @defgroup xquantity-access-methods xquantity access methods **/
///@{
/** get member @ref scale_ **/
constexpr const repr_type & scale() const { return scale_; }
/** get member @ref unit_ **/
constexpr const unit_type & unit() const { return unit_; }
/** true iff this quantity has no dimension **/
constexpr bool is_dimensionless() const { return unit_.is_dimensionless(); }
/** return abbreviation for quantities with this unit **/
constexpr nu_abbrev_type abbrev() const { return unit_.abbrev(); }
///@}
/** @defgroup xquantity-arithmetic-support**/
///@{
/** create unit quantity with same unit as @c this **/
constexpr xquantity unit_qty() const { return xquantity(1, unit_); }
/** create zero quantity with same unit as @c this **/
constexpr xquantity zero_qty() const { return xquantity(0, unit_); }
/** create quantity representing reciprocal of @c this **/
constexpr xquantity reciprocal() const { return xquantity(1.0 / scale_, unit_.reciprocal()); }
/** create quantity representing this value scaled by dimensionless mutliplier @p x **/
template <typename Dimensionless>
requires std::is_arithmetic_v<Dimensionless>
constexpr auto scale_by(Dimensionless x) const {
return xquantity(x * this->scale_, this->unit_);
}
/** create quantity representing this value scaled by dimensionless multiplier @p 1/x **/
template <typename Dimensionless>
requires std::is_arithmetic_v<Dimensionless>
constexpr auto divide_by(Dimensionless x) const {
return xquantity(this->scale_ / x, this->unit_);
}
/** create quantity representing dimensionless numerator @p x divided by this value **/
template <typename Dimensionless>
requires std::is_arithmetic_v<Dimensionless>
constexpr auto divide_into(Dimensionless x) const {
return xquantity(x / this->scale_, this->unit_.reciprocal());
}
/** multiply two @c xquantity values, or a mixed (@c xquantity, @c quantity) pair **/
template <typename Quantity2>
static constexpr
auto multiply(const xquantity & x, const Quantity2 & y) {
using r_repr_type = std::common_type_t<typename xquantity::repr_type,
typename Quantity2::repr_type>;
using r_int_type = std::common_type_t<typename xquantity::ratio_int_type,
typename Quantity2::ratio_int_type>;
using r_int2x_type = std::common_type_t<typename xquantity::ratio_int2x_type,
typename Quantity2::ratio_int2x_type>;
auto rr = detail::su_product<r_int_type, r_int2x_type>(x.unit(), y.unit());
r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_factor_.template convert_to<r_repr_type>()
* static_cast<r_repr_type>(x.scale())
* static_cast<r_repr_type>(y.scale()));
return xquantity<r_repr_type, r_int_type>(r_scale,
rr.natural_unit_);
}
/** compute quotient @p x / @p y, where @p x and @p y are xquantities **/
template <typename Quantity2>
static constexpr
auto divide(const xquantity & x, const Quantity2 & y) {
using r_repr_type = std::common_type_t<typename xquantity::repr_type,
typename Quantity2::repr_type>;
using r_int_type = std::common_type_t<typename xquantity::ratio_int_type,
typename Quantity2::ratio_int_type>;
using r_int2x_type = std::common_type_t<typename xquantity::ratio_int2x_type,
typename Quantity2::ratio_int2x_type>;
auto rr = detail::su_ratio<r_int_type, r_int2x_type>(x.unit(), y.unit());
/* note: su_ratio() reports multiplicative outer scaling factors,
* so multiply is correct here
*/
r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_factor_.template convert_to<r_repr_type>()
* static_cast<r_repr_type>(x.scale())
/ static_cast<r_repr_type>(y.scale()));
return xquantity<r_repr_type, r_int_type>(r_scale,
rr.natural_unit_);
}
/** compute sum @p x + @p y, where @p x and @p y are xquantities **/
template <typename Quantity2>
static constexpr
auto add(const xquantity & x, const Quantity2 & y) {
using r_repr_type = std::common_type_t<typename xquantity::repr_type,
typename Quantity2::repr_type>;
using r_int_type = std::common_type_t<typename xquantity::ratio_int_type,
typename Quantity2::ratio_int_type>;
using r_int2x_type = std::common_type_t<typename xquantity::ratio_int2x_type,
typename Quantity2::ratio_int2x_type>;
/* conversion to get y in same units as x: multiply by y/x */
auto rr = detail::su_ratio<r_int_type, r_int2x_type>(y.unit(), x.unit());
if (rr.natural_unit_.is_dimensionless()) {
r_repr_type r_scale = (static_cast<r_repr_type>(x.scale())
+ (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_factor_.template convert_to<r_repr_type>()
* static_cast<r_repr_type>(y.scale())));
return xquantity<r_repr_type, r_int_type>(r_scale, x.unit_.template to_repr<r_int_type>());
} else {
/* units don't match! */
return xquantity<r_repr_type, r_int_type>(std::numeric_limits<r_repr_type>::quiet_NaN(),
x.unit_.template to_repr<r_int_type>());
}
}
/** compute difference @p x - @p y, where @p x and @p y are xquantities **/
template <typename Quantity2>
static constexpr
auto subtract(const xquantity & x, const Quantity2 & y) {
using r_repr_type = std::common_type_t<typename xquantity::repr_type,
typename Quantity2::repr_type>;
using r_int_type = std::common_type_t<typename xquantity::ratio_int_type,
typename Quantity2::ratio_int_type>;
using r_int2x_type = std::common_type_t<typename xquantity::ratio_int2x_type,
typename Quantity2::ratio_int2x_type>;
/* conversion to get y in same units as x: multiply by y/x */
auto rr = detail::su_ratio<r_int_type, r_int2x_type>(y.unit(), x.unit());
if (rr.natural_unit_.is_dimensionless()) {
r_repr_type r_scale = (static_cast<r_repr_type>(x.scale())
- (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_factor_.template convert_to<r_repr_type>()
* static_cast<r_repr_type>(y.scale())));
return xquantity<r_repr_type, r_int_type>(r_scale, x.unit_.template to_repr<r_int_type>());
} else {
/* units don't match! */
return xquantity<r_repr_type, r_int_type>(std::numeric_limits<r_repr_type>::quiet_NaN(),
x.unit_.template to_repr<r_int_type>());
}
}
///@}
/** @defgroup xquantity-unit-conversion **/
///@{
/** create quantity representing the same value, but in units of @p unit2 **/
constexpr
auto rescale(const natural_unit<Int> & unit2) const {
/* conversion factor from .unit -> unit2*/
auto rr = detail::su_ratio<ratio_int_type,
ratio_int2x_type>(this->unit_, unit2);
if (rr.natural_unit_.is_dimensionless()) {
repr_type r_scale = (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_factor_.template convert_to<repr_type>()
* this->scale_);
return xquantity(r_scale, unit2);
} else {
return xquantity(std::numeric_limits<repr_type>::quiet_NaN(), unit2);
}
}
constexpr
auto rescale_ext(const scaled_unit<Int> & unit2) const {
/* conversion factor from .unit -> unit2*/
auto rr = detail::su_ratio<ratio_int_type,
ratio_int2x_type>(unit_,
unit2.natural_unit_);
if (rr.natural_unit_.is_dimensionless()) {
/* NOTE: test for unit .outer_scale_sq values to get constexpr result with c++23
* and integer dimension powers.
*
* NOTE: we don't intend to support mixed-unit quantities.
* If we change intention, will need to take into account
* (s_scaled_unit.outer_scale_factor_, s_scaled_unit.outer_scale_sq_)
*/
repr_type r_scale
= ((((rr.outer_scale_sq_ == 1.0)
&& (unit2.outer_scale_sq_ == 1.0))
? 1.0
: ::sqrt(rr.outer_scale_sq_ / unit2.outer_scale_sq_))
* rr.outer_scale_factor_.template convert_to<repr_type>()
* this->scale_
/ unit2.outer_scale_factor_.template convert_to<repr_type>());
return xquantity(r_scale, unit2);
} else {
return xquantity(std::numeric_limits<repr_type>::quiet_NaN(), unit2);
}
}
///@}
/** @defgroup xquantity-comparison-support xquantity comparison support methods **/
///@{
/** perform 3-way comparison between @c xquantity values @p x and @p y **/
template <typename Quantity2>
static constexpr
auto compare(const xquantity & x, const Quantity2 & y) {
xquantity y2 = y.rescale(x.unit_);
return x.scale() <=> y2.scale();
}
///@}
/** @defgroup xquantity-operators xquantity operators **/
///@{
/** add @p x in-place, converting units if necessary **/
template <typename Quantity2>
xquantity & operator+= (const Quantity2 & x) {
*this = *this + x;
return *this;
}
/** unary negation; preserves unit information **/
xquantity operator-() const {
return xquantity(-scale_, unit_);
}
/** subtract @p x in-place, converting units if necessary **/
template <typename Quantity2>
xquantity & operator-= (const Quantity2 & x) {
*this = *this - x;
return *this;
}
/** multiply @p x in-place, converting units if necessary
*
* @note: unlike @c quantity::operator*=, may change dimension of lhs
**/
template <typename Quantity2>
xquantity & operator*= (const Quantity2 & x) {
*this = *this * x;
return *this;
}
/** divide @p x in-place, converting units if necessary
*
* @note: unlike @c quantity::operator/=, may change dimension of lhs
**/
template <typename Quantity2>
xquantity & operator/= (const Quantity2 & x) {
*this = *this / x;
return *this;
}
///@}
private:
/** @defgroup xquantity-instance-vars **/
///@{
/** quantity represents this multiple of a unit amount **/
Repr scale_ = Repr{};
/** unit for this quantity **/
natural_unit<Int> unit_;
///@}
}; /*xquantity*/
/** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr)
**/
template <typename Repr = double,
typename Int = std::int64_t>
inline constexpr xquantity<Repr, Int>
unit_qty(const scaled_unit<Int> & u)
{
return xquantity<Repr, Int>
(u.outer_scale_factor_.template convert_to<double>() * ::sqrt(u.outer_scale_sq_),
u.natural_unit_);
}
/** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr)
**/
template <typename Repr = double,
typename Int = std::int64_t>
inline constexpr xquantity<Repr, Int>
natural_unit_qty(const natural_unit<Int> & nu) {
return xquantity<Repr, Int>(1.0, nu);
}
/** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr)
**/
template <typename Q1, typename Q2>
requires (quantity_concept<Q1>
&& quantity_concept<Q2>
&& (!Q1::always_constexpr_unit || !Q2::always_constexpr_unit))
constexpr auto
operator* (const Q1 & x, const Q2 & y)
{
return Q1::multiply(x, y);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator* (double x, const Quantity & y)
{
return y.scale_by(x);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator* (const Quantity & x, double y)
{
return x.scale_by(y);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, typename Quantity2>
requires quantity_concept<Quantity> && quantity_concept<Quantity2>
constexpr auto
operator/ (const Quantity & x, const Quantity2 & y)
{
return Quantity::divide(x, y);
}
/** note: doesn not require unit scaling, so constexpr with c++23 **/
template <typename Quantity, typename Dimensionless>
requires quantity_concept<Quantity> && std::is_arithmetic_v<Dimensionless>
constexpr auto
operator/ (const Quantity & x, Dimensionless y)
{
return x.divide_by(y);
}
/** note: doesn not require unit scaling, so constexpr with c++23 **/
template <typename Dimensionless, typename Quantity>
requires std::is_arithmetic_v<Dimensionless> && quantity_concept<Quantity>
constexpr auto
operator/ (Dimensionless x, const Quantity & y)
{
return y.divide_into(x);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, typename Quantity2>
requires quantity_concept<Quantity> && quantity_concept<Quantity2>
constexpr auto
operator+ (const Quantity & x, const Quantity2 & y)
{
return Quantity::add(x, y);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator+ (const Quantity & x, double y)
{
return x + Quantity(y, u::dimensionless);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator+ (double x, const Quantity & y)
{
return Quantity(x, u::dimensionless) + y;
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, typename Quantity2>
requires (quantity_concept<Quantity>
&& quantity_concept<Quantity2>)
constexpr auto
operator- (const Quantity & x, const Quantity2 & y)
{
return Quantity::subtract(x, y);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator- (const Quantity & x, double y)
{
return x - Quantity(y, u::dimensionless);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator- (double x, const Quantity & y)
{
return Quantity(x, u::dimensionless) - y;
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator== (const Quantity & x, double y)
{
return (x == Quantity(y, u::dimensionless));
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator== (double x, const Quantity & y)
{
return (Quantity(x, u::dimensionless) == y);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, double>
requires quantity_concept<Quantity>
constexpr auto
operator<=> (const Quantity & x, double y)
{
return Quantity::compare(x, Quantity(y, u::dimensionless));
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, double>
requires quantity_concept<Quantity>
constexpr auto
operator<=> (double x, const Quantity & y)
{
return Quantity::compare(Quantity(x, u::dimensionless), y);
}
namespace xu {
constexpr auto nanogram = xquantity(1.0, u::nanogram);
}
} /*namespace qty*/
} /*namespace xo*/
/** end xquantity.hpp **/

View file

@ -0,0 +1,27 @@
/** @file xquantity_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "xquantity.hpp"
#include "natural_unit_iostream.hpp"
//#include <iostream>
namespace xo {
namespace qty {
template <typename Repr = double,
typename Int = std::int64_t>
inline std::ostream &
operator<< (std::ostream & os,
const xquantity<Repr, Int> & x)
{
os << x.scale() << x.abbrev();
return os;
}
} /*namespace qty*/
} /*namespace xo*/
/** end xquantity_iostream.hpp **/

38
xo-unit/pkgs/xo-cmake.nix Normal file
View file

@ -0,0 +1,38 @@
{
# dependencies
stdenv, cmake, # ... other deps here
# args
# attrset for fetching source code.
# { type, owner, repo, ref }
#
# e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b"
#
# see [[../flake.nix]]
#
#cmake-examples-ex1-path
# someconfigurationoption ? false
} :
stdenv.mkDerivation (finalattrs:
{
name = "xo-cmake";
# src = cmake-examples-ex1-path;
src = (fetchGit {
url = "https://github.com/rconybea/xo-cmake";
version = "1.0";
#ref = "ex1";
#rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645";
});
nativeBuildInputs = [ cmake ];
# installPhase = ''
# mkdir -p $out
# echo 'This project intentionally has no install phase'
# '';
})

View file

@ -0,0 +1,75 @@
{
# 1. nixpkgs dependencies
# 1.1. python
python, pythonPackages,
# 1.2. particular python packages
sphinx ? pythonPackages.sphinx,
sphinx-rtd-theme ? pythonPackages.sphinx-rtd-theme,
breathe ? pythonPackages.breathe,
# 1.3. document-generation packages
# use of makeFontsConf adapted from nixpkgs/development/libraries/gtkmm/4.x.nix
#
doxygen, graphviz,
##fontconfig,
##makeFontsConf,
# 1.4. c++ build/utest chain
stdenv, cmake, catch2, # ... other deps here
# 2. xo dependencies
xo-cmake, xo-indentlog
# args
# attrset for fetching source code.
# { type, owner, repo, ref }
#
# e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b"
#
# see [[../flake.nix]]
#
#cmake-examples-ex1-path
# someconfigurationoption ? false
} :
stdenv.mkDerivation (finalattrs:
{
name = "xo-flatstring";
src = (fetchGit {
url = "https://github.com/rconybea/xo-flatstring";
version = "1.0";
#ref = "ex1";
#rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645";
});
cmakeFlags = ["-DXO_CMAKE_CONFIG_EXECUTABLE=${xo-cmake}/bin/xo-cmake-config"];
# move HOME so fontconfig can do sensible things
buildPhase = ''
#set -x
echo "FONTCONFIG_FILE=$FONTCONFIG_FILE"
#export FONTCONFIG_FILE=$fontconfig.out/etc/fonts/fonts.conf
export HOME=$TMPDIR
export XDG_CONFIG_HOME=$TMPDIR
mkdir $XDG_CONFIG_HOME/fontconfig
#grep xdg $FONTCONFIG_FILE
#$fontconfig.out/bin/fc-cache -v
make && make docs
#make
'';
doCheck = true;
nativeBuildInputs = [ cmake catch2
doxygen graphviz sphinx sphinx-rtd-theme breathe ];
propagatedBuildInputs = [ xo-indentlog ];
})

View file

@ -0,0 +1,36 @@
{
# nixpkgs dependencies
stdenv, cmake, catch2, # ... other deps here
# xo dependencies
xo-cmake,
# args
# attrset for fetching source code.
# { type, owner, repo, ref }
#
# e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b"
#
# see [[../flake.nix]]
#
#cmake-examples-ex1-path
# someconfigurationoption ? false
} :
stdenv.mkDerivation (finalattrs:
{
name = "xo-indentlog";
src = (fetchGit {
url = "https://github.com/rconybea/indentlog";
version = "1.0";
#ref = "ex1";
#rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645";
});
cmakeFlags = ["-DXO_CMAKE_CONFIG_EXECUTABLE=${xo-cmake}/bin/xo-cmake-config"];
doCheck = true;
nativeBuildInputs = [ cmake catch2 ];
})

View file

@ -0,0 +1,37 @@
{
# nixpkgs dependencies
stdenv, cmake, catch2, # ... other deps here
# xo dependencies
xo-cmake, xo-indentlog
# args
# attrset for fetching source code.
# { type, owner, repo, ref }
#
# e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b"
#
# see [[../flake.nix]]
#
#cmake-examples-ex1-path
# someconfigurationoption ? false
} :
stdenv.mkDerivation (finalattrs:
{
name = "xo-randomgen";
src = (fetchGit {
url = "https://github.com/rconybea/randomgen";
version = "1.0";
#ref = "ex1";
#rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645";
});
cmakeFlags = ["-DXO_CMAKE_CONFIG_EXECUTABLE=${xo-cmake}/bin/xo-cmake-config"];
doCheck = true;
nativeBuildInputs = [ cmake catch2 ];
propagatedBuildInputs = [ xo-indentlog ];
})

83
xo-unit/pkgs/xo-ratio.nix Normal file
View file

@ -0,0 +1,83 @@
{
# 1. nixpkgs dependencies
# 1.1. python
python, pythonPackages,
# 1.2. particular python packages
sphinx ? pythonPackages.sphinx,
sphinx-rtd-theme ? pythonPackages.sphinx-rtd-theme,
breathe ? pythonPackages.breathe,
# 1.3. document-generation packages
# use of makeFontsConf adapted from nixpkgs/development/libraries/gtkmm/4.x.nix
#
doxygen, graphviz,
##fontconfig,
##makeFontsConf,
# used for graphviz (dot) see docs/Doxyfile.in
#helvetica-neue-lt-std,
# 1.4. c++ build/utest chain
stdenv, cmake, catch2, # ... other deps here
# 2. xo dependencies
xo-cmake, xo-flatstring, xo-randomgen,
# args
# attrset for fetching source code.
# { type, owner, repo, ref }
#
# e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b"
#
# see [[../flake.nix]]
#
#cmake-examples-ex1-path
# someconfigurationoption ? false
} :
stdenv.mkDerivation (finalattrs:
{
name = "xo-ratio";
src = (fetchGit {
url = "https://github.com/rconybea/xo-ratio";
version = "1.0";
#ref = "ex1";
#rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645";
});
cmakeFlags = ["-DXO_CMAKE_CONFIG_EXECUTABLE=${xo-cmake}/bin/xo-cmake-config"];
# NOTE: helvetic-neue-lt-std has unfree license
#FONTCONFIG_FILE = makeFontsConf { fontDirectories = [ helvetica-neue-lt-std ]; };
# move HOME so fontconfig can do sensible things
buildPhase = ''
#set -x
echo "FONTCONFIG_FILE=$FONTCONFIG_FILE"
#export FONTCONFIG_FILE=$fontconfig.out/etc/fonts/fonts.conf
export HOME=$TMPDIR
export XDG_CONFIG_HOME=$TMPDIR
mkdir $XDG_CONFIG_HOME/fontconfig
#grep xdg $FONTCONFIG_FILE
#$fontconfig.out/bin/fc-cache -v
make && make docs
#make
'';
doCheck = true;
nativeBuildInputs = [ cmake catch2
doxygen graphviz sphinx sphinx-rtd-theme breathe
xo-randomgen
];
propagatedBuildInputs = [ xo-flatstring ];
})

View file

@ -0,0 +1,37 @@
{
# nixpkgs dependencies
stdenv, cmake, catch2, # ... other deps here
# xo dependencies
xo-cmake, xo-indentlog
# args
# attrset for fetching source code.
# { type, owner, repo, ref }
#
# e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b"
#
# see [[../flake.nix]]
#
#cmake-examples-ex1-path
# someconfigurationoption ? false
} :
stdenv.mkDerivation (finalattrs:
{
name = "xo-refcnt";
src = (fetchGit {
url = "https://github.com/rconybea/refcnt";
version = "1.0";
#ref = "ex1";
#rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645";
});
cmakeFlags = ["-DXO_CMAKE_CONFIG_EXECUTABLE=${xo-cmake}/bin/xo-cmake-config"];
doCheck = true;
nativeBuildInputs = [ cmake catch2 ];
propagatedBuildInputs = [ xo-indentlog ];
})

View file

@ -0,0 +1,37 @@
{
# nixpkgs dependencies
stdenv, cmake, catch2, # ... other deps here
# xo dependencies
xo-cmake, xo-refcnt, xo-subsys,
# args
# attrset for fetching source code.
# { type, owner, repo, ref }
#
# e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b"
#
# see [[../flake.nix]]
#
#cmake-examples-ex1-path
# someconfigurationoption ? false
} :
stdenv.mkDerivation (finalattrs:
{
name = "xo-reflect";
src = (fetchGit {
url = "https://github.com/rconybea/reflect";
version = "1.0";
#ref = "ex1";
#rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645";
});
cmakeFlags = ["-DXO_CMAKE_CONFIG_EXECUTABLE=${xo-cmake}/bin/xo-cmake-config"];
doCheck = true;
nativeBuildInputs = [ cmake catch2 ];
propagatedBuildInputs = [ xo-subsys xo-refcnt ];
})

View file

@ -0,0 +1,36 @@
{
# nixpkgs dependencies
stdenv, cmake, catch2, # ... other deps here
# xo dependencies
xo-cmake,
# args
# attrset for fetching source code.
# { type, owner, repo, ref }
#
# e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b"
#
# see [[../flake.nix]]
#
#cmake-examples-ex1-path
# someconfigurationoption ? false
} :
stdenv.mkDerivation (finalattrs:
{
name = "xo-subsys";
src = (fetchGit {
url = "https://github.com/rconybea/subsys";
version = "1.0";
#ref = "ex1";
#rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645";
});
cmakeFlags = ["-DXO_CMAKE_CONFIG_EXECUTABLE=${xo-cmake}/bin/xo-cmake-config"];
#doCheck = true;
nativeBuildInputs = [ cmake catch2 ];
})

79
xo-unit/pkgs/xo-unit.nix Normal file
View file

@ -0,0 +1,79 @@
{
# 1. nixpkgs dependencies
# 1.1. python
python, pythonPackages,
# 1.2. particular python packages
sphinx ? pythonPackages.sphinx,
sphinx-rtd-theme ? pythonPackages.sphinx-rtd-theme,
breathe ? pythonPackages.breathe,
# 1.3. document-generation packages
# use of makeFontsConf adapted from nixpkgs/development/libraries/gtkmm/4.x.nix
#
doxygen, graphviz,
#fontconfig,
makeFontsConf,
# used for graphviz (dot) see docs/Doxyfile.in
#helvetica-neue-lt-std,
# 1.4. c++ build/utest chain
stdenv, cmake, catch2, # ... other deps here
# 2. xo dependencies
xo-cmake, xo-flatstring, xo-ratio, xo-randomgen,
# args
# attrset for fetching source code.
# { type, owner, repo, ref }
#
# e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b"
#
# see [[../flake.nix]]
#
#cmake-examples-ex1-path
# someconfigurationoption ? false
} :
stdenv.mkDerivation (finalattrs:
{
name = "xo-unit";
src = (fetchGit {
url = "https://github.com/rconybea/xo-unit";
version = "1.0";
#ref = "ex1";
#rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645";
});
cmakeFlags = ["-DXO_CMAKE_CONFIG_EXECUTABLE=${xo-cmake}/bin/xo-cmake-config"];
# NOTE: helvetic-neue-lt-std has unfree license
#FONTCONFIG_FILE = makeFontsConf { fontDirectories = [ helvetica-neue-lt-std ]; };
# move HOME so fontconfig can do sensible things
buildPhase = ''
set -x
echo "FONTCONFIG_FILE=$FONTCONFIG_FILE"
#export FONTCONFIG_FILE=$fontconfig.out/etc/fonts/fonts.conf
export HOME=$TMPDIR
export XDG_CONFIG_HOME=$TMPDIR
mkdir $XDG_CONFIG_HOME/fontconfig
#grep xdg $FONTCONFIG_FILE
#$fontconfig.out/bin/fc-cache -v
make && make docs
'';
doCheck = true;
nativeBuildInputs = [ cmake catch2 doxygen graphviz sphinx sphinx-rtd-theme breathe xo-randomgen ];
propagatedBuildInputs = [ xo-flatstring xo-ratio ];
})

View file

@ -0,0 +1,27 @@
{
# nixpkgs dependencies
buildFHSUserEnv, # ... other deps here
# xo dependencies
xo-cmake, xo-indentlog, xo-flatstring, xo-subsys, xo-refcnt, xo-ratio, xo-reflect, xo-randomgen, xo-unit,
# other args
# someconfigurationoption ? false
} :
buildFHSUserEnv {
name = "xo-userenv";
targetPkgs = pkgs: [ xo-cmake
xo-indentlog
xo-flatstring
xo-subsys
xo-refcnt
xo-ratio
xo-reflect
xo-randomgen
xo-unit
];
# runScript = ...;
# profile = ...;
}

View file

@ -0,0 +1,26 @@
# xo-unit/utest/CMakeLists.txt
set(SELF_EXE utest.unit)
set(SELF_SRCS
unit_utest_main.cpp #mpl_unit.test.cpp
xquantity.test.cpp
quantity.test.cpp
bpu.test.cpp
basis_unit.test.cpp
scaled_unit.test.cpp
natural_unit.test.cpp
unit.test.cpp #quantity.test.cpp
)
if (ENABLE_TESTING)
xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS})
# ----------------------------------------------------------------
# dependencies..
xo_self_dependency(${SELF_EXE} xo_unit)
xo_headeronly_dependency(${SELF_EXE} xo_ratio)
xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,153 @@
/* @file bu.test.cpp */
#include "xo/unit/basis_unit.hpp"
#include "xo/unit/bu_store.hpp"
#include "xo/indentlog/scope.hpp"
//#include "xo/indentlog/print/tag.hpp"
#include <catch2/catch.hpp>
namespace xo {
using xo::qty::scalefactor_ratio_type;
using xo::qty::bu_abbrev;
using xo::qty::basis_unit;
using xo::qty::bu_abbrev_type;
using xo::qty::native_unit2_v;
using xo::qty::dim;
namespace bu = xo::qty::detail::bu;
namespace ut {
/* compile-time test:
* verify we can use a basis_unit as a non-type template parameter
* we will need this for quantity<Repr, Int, natural_unit<Int>>
*/
template <basis_unit bu>
constexpr bu_abbrev_type bu_mpl_abbrev = bu_abbrev(bu);
TEST_CASE("basis_unit", "[basis_unit]") {
static_assert(bu_mpl_abbrev<bu::gram> == bu_abbrev(bu::gram));
REQUIRE(bu_mpl_abbrev<bu::gram> == bu_abbrev(bu::gram));
} /*TEST_CASE(basis_unit)*/
TEST_CASE("basis_unit1", "[basis_unit]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.basis_unit1"));
static_assert(native_unit2_v[static_cast<int>(dim::mass)].native_dim() == dim::mass);
static_assert(native_unit2_v[static_cast<int>(dim::distance)].native_dim() == dim::distance);
static_assert(native_unit2_v[static_cast<int>(dim::time)].native_dim() == dim::time);
static_assert(native_unit2_v[static_cast<int>(dim::time)].native_dim() == dim::time);
static_assert(native_unit2_v[static_cast<int>(dim::currency)].native_dim() == dim::currency);
static_assert(native_unit2_v[static_cast<int>(dim::price)].native_dim() == dim::price);
log && log(xtag("mass*10^3", bu_abbrev(bu::kilogram)));
static_assert(bu_abbrev(bu::kilogram)
== bu_abbrev_type::from_chars("kg"));
log && log("---------------------");
/* note: using CHECK to make test show up in coverage */
# define REQUIRE_x2(x) static_assert(x); REQUIRE(x)
log && log(xtag("mass", bu_abbrev(bu::meter)));
REQUIRE_x2(bu_abbrev(bu::picogram) == bu_abbrev_type::from_chars("pg"));
REQUIRE_x2(bu_abbrev(bu::nanogram)
== bu_abbrev_type::from_chars("ng"));
REQUIRE_x2(bu_abbrev(bu::microgram)
== bu_abbrev_type::from_chars("ug"));
REQUIRE_x2(bu_abbrev(bu::milligram)
== bu_abbrev_type::from_chars("mg"));
REQUIRE_x2(bu_abbrev(bu::gram)
== bu_abbrev_type::from_chars("g"));
REQUIRE_x2(bu_abbrev(bu::kilogram)
== bu_abbrev_type::from_chars("kg"));
REQUIRE_x2(bu_abbrev(bu::tonne)
== bu_abbrev_type::from_chars("t"));
REQUIRE_x2(bu_abbrev(bu::kilotonne)
== bu_abbrev_type::from_chars("kt"));
REQUIRE_x2(bu_abbrev(bu::megatonne)
== bu_abbrev_type::from_chars("Mt"));
REQUIRE_x2(bu_abbrev(bu::gigatonne)
== bu_abbrev_type::from_chars("Gt"));
log && log(xtag("distance", bu_abbrev(bu::meter)));
REQUIRE_x2(bu_abbrev(bu::picometre)
== bu_abbrev_type::from_chars("pm"));
REQUIRE_x2(bu_abbrev(bu::nanometre)
== bu_abbrev_type::from_chars("nm"));
REQUIRE_x2(bu_abbrev(bu::micrometre)
== bu_abbrev_type::from_chars("um"));
REQUIRE_x2(bu_abbrev(bu::millimetre)
== bu_abbrev_type::from_chars("mm"));
REQUIRE_x2(bu_abbrev(bu::metre)
== bu_abbrev_type::from_chars("m"));
REQUIRE_x2(bu_abbrev(bu::kilometre)
== bu_abbrev_type::from_chars("km"));
REQUIRE_x2(bu_abbrev(bu::megametre)
== bu_abbrev_type::from_chars("Mm"));
REQUIRE_x2(bu_abbrev(bu::gigametre)
== bu_abbrev_type::from_chars("Gm"));
REQUIRE_x2(bu_abbrev(bu::picometer)
== bu_abbrev_type::from_chars("pm"));
REQUIRE_x2(bu_abbrev(bu::nanometer)
== bu_abbrev_type::from_chars("nm"));
REQUIRE_x2(bu_abbrev(bu::micrometer)
== bu_abbrev_type::from_chars("um"));
REQUIRE_x2(bu_abbrev(bu::millimeter)
== bu_abbrev_type::from_chars("mm"));
REQUIRE_x2(bu_abbrev(bu::meter)
== bu_abbrev_type::from_chars("m"));
REQUIRE_x2(bu_abbrev(bu::kilometer)
== bu_abbrev_type::from_chars("km"));
REQUIRE_x2(bu_abbrev(bu::megameter)
== bu_abbrev_type::from_chars("Mm"));
REQUIRE_x2(bu_abbrev(bu::gigameter)
== bu_abbrev_type::from_chars("Gm"));
REQUIRE_x2(bu_abbrev(bu::lightsecond) == flatstring("lsec"));
REQUIRE_x2(bu_abbrev(bu::astronomicalunit) == flatstring("AU"));
REQUIRE_x2(bu_abbrev(bu::inch) == flatstring("in"));
REQUIRE_x2(bu_abbrev(bu::foot) == flatstring("ft"));
REQUIRE_x2(bu_abbrev(bu::yard) == flatstring("yd"));
REQUIRE_x2(bu_abbrev(bu::mile) == flatstring("mi"));
log && log(xtag("time", bu_abbrev(bu::second)));
REQUIRE_x2(bu_abbrev(bu::picosecond) == bu_abbrev_type::from_chars("ps"));
REQUIRE_x2(bu_abbrev(bu::nanosecond) == bu_abbrev_type::from_chars("ns"));
REQUIRE_x2(bu_abbrev(bu::microsecond) == bu_abbrev_type::from_chars("us"));
REQUIRE_x2(bu_abbrev(bu::millisecond) == bu_abbrev_type::from_chars("ms"));
REQUIRE_x2(bu_abbrev(bu::second) == bu_abbrev_type::from_chars("s"));
REQUIRE_x2(bu_abbrev(bu::minute) == bu_abbrev_type::from_chars("min"));
REQUIRE_x2(bu_abbrev(bu::hour) == bu_abbrev_type::from_chars("hr"));
REQUIRE_x2(bu_abbrev(bu::day) == bu_abbrev_type::from_chars("dy"));
REQUIRE_x2(bu_abbrev(bu::week) == bu_abbrev_type::from_chars("wk"));
REQUIRE_x2(bu_abbrev(bu::month) == bu_abbrev_type::from_chars("mo"));
REQUIRE_x2(bu_abbrev(bu::year) == bu_abbrev_type::from_chars("yr"));
REQUIRE_x2(bu_abbrev(bu::year250) == bu_abbrev_type::from_chars("yr250"));
REQUIRE_x2(bu_abbrev(bu::year360) == bu_abbrev_type::from_chars("yr360"));
REQUIRE_x2(bu_abbrev(bu::year365) == bu_abbrev_type::from_chars("yr365"));
log && log(xtag("currency", bu_abbrev(bu::currency)));
REQUIRE_x2(bu_abbrev(bu::currency) == flatstring("ccy"));
log && log(xtag("price", bu_abbrev(bu::price)));
REQUIRE_x2(bu_abbrev(bu::price) == flatstring("px"));
# undef REQUIRE_x2
} /*TEST_CASE(basis_unit1)*/
} /*namespace ut*/
} /*namespace xo*/
/* end bu.test.cpp */

View file

@ -0,0 +1,95 @@
/* @file bpu.test.cpp */
#include "xo/unit/bpu.hpp"
#include "xo/indentlog/scope.hpp"
#include <catch2/catch.hpp>
namespace xo {
using xo::qty::abbrev::bpu_abbrev;
using xo::qty::abbrev::flatstring_from_exponent;
namespace qty {
using bpu64_type = bpu<std::int64_t>;
/* compile-time test:
* verify we can use a bpu64_type instance as a non-type template parameter.
* Will need this for quantity<Repr, Int, natural_unit<Int>>
*/
template <bpu64_type bpu>
constexpr bpu_abbrev_type bpu_mpl_abbrev = bpu.abbrev();
TEST_CASE("bpu", "[bpu]") {
static_assert(bpu_mpl_abbrev<bpu64_type::unit_power(detail::bu::gram)>
== bpu64_type::unit_power(detail::bu::gram).abbrev());
REQUIRE(bpu_mpl_abbrev<bpu64_type::unit_power(detail::bu::gram)>
== bpu64_type::unit_power(detail::bu::gram).abbrev());
} /*TEST_CASE(bpu)*/
TEST_CASE("flatstring_from_exponent", "[flatstring_from_exponent]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.flatstring_from_exponent"));
log && log(xtag("^-3", flatstring_from_exponent(power_ratio_type(-3,1))));
log && log(xtag("^-2", flatstring_from_exponent(power_ratio_type(-2,1))));
log && log(xtag("^-1", flatstring_from_exponent(power_ratio_type(-1,1))));
log && log(xtag("^-1/2", flatstring_from_exponent(power_ratio_type(-1,2))));
log && log(xtag("^0", flatstring_from_exponent(power_ratio_type(0,1))));
log && log(xtag("^1/2", flatstring_from_exponent(power_ratio_type(1,2))));
log && log(xtag("^1", flatstring_from_exponent(power_ratio_type(1,1))));
log && log(xtag("^2", flatstring_from_exponent(power_ratio_type(2,1))));
log && log(xtag("^3", flatstring_from_exponent(power_ratio_type(3,1))));
static_assert(flatstring<5>::from_flatstring(flatstring_from_exponent(power_ratio_type(-3,1)))
== flatstring<5>::from_flatstring(flatstring("^-3")));
static_assert(flatstring<5>::from_flatstring(flatstring_from_exponent(power_ratio_type(-2,1)))
== flatstring<5>::from_flatstring(flatstring("^-2")));
static_assert(flatstring<5>::from_flatstring(flatstring_from_exponent(power_ratio_type(-1,1)))
== flatstring<5>::from_flatstring(flatstring("^-1")));
static_assert(flatstring<5>::from_flatstring(flatstring_from_exponent(power_ratio_type(-1,2)))
== flatstring<5>::from_flatstring(flatstring("^(-1/2)")));
static_assert(flatstring<5>::from_flatstring(flatstring_from_exponent(power_ratio_type(1,2)))
== flatstring<5>::from_flatstring(flatstring("^(1/2)")));
static_assert(flatstring<5>::from_flatstring(flatstring_from_exponent(power_ratio_type(1,1)))
== flatstring<5>::from_flatstring(flatstring("")));
static_assert(flatstring<5>::from_flatstring(flatstring_from_exponent(power_ratio_type(2,1)))
== flatstring<5>::from_flatstring(flatstring("^2")));
static_assert(flatstring<5>::from_flatstring(flatstring_from_exponent(power_ratio_type(3,1)))
== flatstring<5>::from_flatstring(flatstring("^3")));
} /*TEST_CASE(flatstring_from_exponent)*/
TEST_CASE("bpu_abbrev", "[bpu_abbrev]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.bpu2_assemble_abbrev"));
log && log(xtag("1/(kg*kg)", bpu_abbrev(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(-2, 1))));
log && log(xtag("1/kg", bpu_abbrev(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(-1, 1))));
log && log(xtag("kg", bpu_abbrev(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(1, 1))));
log && log(xtag("kg*kg", bpu_abbrev(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(2, 1))));
static_assert(bpu<int64_t>(dim::mass, scalefactor_ratio_type(1, 1), power_ratio_type(1, 1)).abbrev()
== bpu_abbrev_type::from_chars("g"));
static_assert(bpu<int64_t>(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(1, 1)).abbrev()
== bpu_abbrev_type::from_chars("kg"));
static_assert(bpu<int64_t>(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(-1, 1)).abbrev()
== bpu_abbrev_type::from_chars("kg^-1"));
static_assert(bpu<int64_t>(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(-2, 1)).abbrev()
== bpu_abbrev_type::from_chars("kg^-2"));
static_assert(bpu<int64_t>(dim::time, scalefactor_ratio_type(60, 1), power_ratio_type(-2, 1)).abbrev()
== bpu_abbrev_type::from_chars("min^-2"));
static_assert(bpu<int64_t>(dim::time, scalefactor_ratio_type(3600, 1), power_ratio_type(-1, 1)).abbrev()
== bpu_abbrev_type::from_chars("hr^-1"));
static_assert(bpu<int64_t>(dim::time, scalefactor_ratio_type(24*3600, 1), power_ratio_type(-1, 1)).abbrev()
== bpu_abbrev_type::from_chars("dy^-1"));
static_assert(bpu<int64_t>(dim::time, scalefactor_ratio_type(360*24*3600, 1), power_ratio_type(-1, 1)).abbrev()
== bpu_abbrev_type::from_chars("yr360^-1"));
static_assert(bpu<int64_t>(dim::time, scalefactor_ratio_type(360*24*3600, 1), power_ratio_type(-1, 2)).abbrev()
== bpu_abbrev_type::from_chars("yr360^(-1/2)"));
} /*TEST_CASE(bpu_abbrev)*/
} /*namespace qty*/
} /*namespace xo*/
/* end bpu.test.cpp */

View file

@ -0,0 +1,862 @@
/* @file quantity.test.cpp */
#include "xo/unit/mpl/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>
#include <compare>
namespace xo {
using xo::unit::quantity;
using xo::unit::qty::kilograms;
using xo::unit::qty::meters;
using xo::unit::qty::kilometers;
using xo::unit::qty::milliseconds;
using xo::unit::qty::seconds;
using xo::unit::qty::minutes;
using xo::unit::qty::hours;
using xo::unit::qty::volatility30d;
using xo::unit::qty::volatility250d;
using xo::unit::unit_find_bpu_t;
using xo::unit::unit_conversion_factor_t;
using xo::unit::unit_cartesian_product_t;
using xo::unit::unit_cartesian_product;
using xo::unit::unit_invert_t;
using xo::unit::unit_abbrev_v;
using xo::unit::same_dimension_v;
using xo::unit::dim;
using xo::unit::from_ratio;
using xo::unit::stringliteral_from_ratio;
using xo::unit::ratio2str_aux;
using xo::unit::cstr_from_ratio;
using xo::reflect::Reflect;
namespace units = xo::unit::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.basis_power<dim::time> == 1);
static_assert(t.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.basis_power<dim::time> == 1);
static_assert(t2.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.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.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.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.basis_power<dim::mass> == 1);
static_assert(q1.basis_power<dim::time> == -1);
static_assert(q2.basis_power<dim::mass> == 1);
static_assert(q2.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.basis_power<dim::time> == 1);
/* verify scale */
REQUIRE(r.scale() == 1.25);
}
} /*TEST_CASE(div3)*/
TEST_CASE("div4", "[quantity]") {
/* test with exact scalefactor */
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.div4"));
//log && log("(A)", xtag("foo", foo));
auto q1 = milliseconds(1);
auto q2 = minutes(1);
auto r = q1 / q2.with_repr<double>();
/* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy),
* so q1/q2 ~ 0.6928
*/
log && log(XTAG(q1), XTAG(q2), xtag("q1/q2", r));
/* verify dimensionless result */
static_assert(std::same_as<decltype(r), double>);
/* verify scale of result */
CHECK(r == Approx(0.00001666667).epsilon(1e-6));
} /*TEST_CASE(div4)*/
TEST_CASE("div5", "[quantity]") {
/* test with inexact scalefactor */
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.div5"));
//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(div5)*/
TEST_CASE("muldiv5", "[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.muldiv5"));
//log && log("(A)", xtag("foo", foo));
auto t = milliseconds(10);
auto m = kilograms(2.5);
auto a = m / (t * t);
/* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy),
* so q1/q2 ~ 0.6928
*/
log && log(XTAG(m), XTAG(t), xtag("a=m.t^-2", a));
/* verify dimensions of result + sticky units */
CHECK(strcmp(t.unit_cstr(), "ms") == 0);
CHECK(strcmp(m.unit_cstr(), "kg") == 0);
CHECK(strcmp(a.unit_cstr(), "kg.ms^-2") == 0);
CHECK(a.scale() == 0.025);
/* verify scale of result */
//CHECK(r == Approx(0.692820323).epsilon(1e-6));
} /*TEST_CASE(muldiv5)*/
TEST_CASE("rescale", "[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.rescale"));
//log && log("(A)", xtag("foo", foo));
auto q = kilograms(150.0) / minutes(1); /* 150.0kg.min^-1 */
CHECK(strcmp(q.unit_cstr(), "kg.min^-1") == 0);
CHECK(q.scale() == 150.0);
log && log(XTAG(q));
namespace u = xo::unit::units;
auto q1 = q.with_basis_unit<u::millisecond>(); /* 0.0025kg.ms^-1 */
CHECK(strcmp(q1.unit_cstr(), "kg.ms^-1") == 0);
CHECK(q1.scale() == 0.0025);
log && log(XTAG(q1));
auto q2 = q1.with_basis_unit<u::gram>(); /* 2.5g.ms^-1 */
CHECK(strcmp(q2.unit_cstr(), "g.ms^-1") == 0);
CHECK(q2.scale() == 2.5);
log && log(XTAG(q2));
auto q3 = q2.with_basis_unit<u::second>(); /* 2500g.s^-1 */
CHECK(strcmp(q3.unit_cstr(), "g.s^-1") == 0);
CHECK(q3.scale() == 2500.0);
log && log(XTAG(q3));
} /*TEST_CASE(rescale)*/
TEST_CASE("rescale2", "[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.rescale2"));
//log && log("(A)", xtag("foo", foo));
namespace u = xo::unit::unit_qty;
auto q1 = kilometers(150.0) / u::hour;
auto q2 = q1.with_units_from(u::meter / u::second);
log && log(XTAG(q1), XTAG(q2));
} /*TEST_CASE(rescale2)*/
TEST_CASE("compare1", "[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.compare1"));
//log && log("(A)", xtag("foo", foo));
namespace u = xo::unit::unit_qty;
auto q1 = kilometers(150.0) / u::hour;
auto q2 = kilometers(100.0) / u::hour;
CHECK(is_gt(q1 <=> q2));
CHECK(is_eq(q1 <=> q1));
CHECK(is_lt(q2 <=> q1));
CHECK(q1 == q1);
CHECK(q1 != q2);
CHECK(q1 >= q1);
CHECK(q1 <= q1);
CHECK(q1 > q2);
CHECK(q1 >= q2);
CHECK(q2 < q1);
CHECK(q2 <= q1);
log && log(XTAG(q1), XTAG(q2), XTAG(is_gt(q1<=>q2)));
} /*TEST_CASE(compare1)*/
TEST_CASE("compare2", "[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.compare2"));
//log && log("(A)", xtag("foo", foo));
namespace u = xo::unit::unit_qty;
auto q1 = kilometers(150.0) / u::hour;
auto q2 = meters(30.0) / u::second;
CHECK(is_gt(q1 <=> q2));
CHECK(is_eq(q1 <=> q1));
CHECK(is_lt(q2 <=> q1));
CHECK(q1 == q1);
CHECK(q1 != q2);
CHECK(q1 >= q1);
CHECK(q1 <= q1);
CHECK(q1 > q2);
CHECK(q1 >= q2);
CHECK(q2 < q1);
CHECK(q2 <= q1);
log && log(XTAG(q1), XTAG(q2), XTAG(is_gt(q1<=>q2)));
} /*TEST_CASE(compare2)*/
} /*namespace ut*/
} /*namespace xo*/
/* end quantity.test.cpp */

View file

@ -0,0 +1,380 @@
/* @file mpl_unit.test.cpp */
//#include "native_bpu.hpp"
#include "xo/unit/mpl/quantity.hpp"
//#include "xo/unit/unit.hpp"
//#include "xo/unit/Quantity2_iostream.hpp"
//#include "xo/unit/Quantity2.hpp"
//#include "xo/unit/scaled_unit_iostream.hpp"
//#include "xo/unit/natural_unit.hpp"
//#include "xo/unit/natural_unit_iostream.hpp"
//#include "xo/unit/bpu_store.hpp"
//#include "xo/unit/native_bpu2.hpp"
//#include "xo/unit/native_bpu2_iostream.hpp"
//#include "xo/unit/basis_unit2.hpp"
//#include "xo/unit/dim_util2.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>
namespace xo {
namespace ut {
/* compile-time tests */
using xo::reflect::Reflect;
//namespace su2 = xo::unit::su2;
//using xo::unit::Quantity2;
using xo::unit::dim;
//using xo::unit::basis_unit2_abbrev_type;
//using xo::unit::native_unit2;
//using xo::unit::native_unit2_v;
//using xo::unit::scalefactor_ratio_type;
//using xo::unit::units::scaled_native_unit2_abbrev;
//using xo::unit::units::scaled_native_unit2_abbrev_v;
//using xo::unit::basis_unit2;
//using xo::unit::abbrev::basis_unit2_abbrev;;
//using xo::unit::bpu2_abbrev_type;
//using xo::unit::abbrev::bpu2_abbrev;
//using xo::unit::basis_unit2_store;
//using xo::unit::power_ratio_type;
//using xo::unit::abbrev::flatstring_from_exponent;
//using xo::unit::bpu2;
//using xo::unit::detail::bpu2_rescale;
//using xo::unit::detail::bpu2_product;
//using xo::unit::natural_unit;
//using xo::unit::bpu_array_maker;
//using xo::unit::detail::nu_product;
//using xo::unit::unit_qty;
using xo::unit::native_unit_abbrev_v;
using xo::unit::units::scaled_native_unit_abbrev_v;
//using xo::unit::native_dim_abbrev;
using xo::unit::stringliteral_compare;
using xo::unit::literal_size_v;
using xo::unit::stringliteral_from_digit;
using xo::unit::stringliteral_from_int_v;
using xo::unit::stringliteral;
#ifndef __clang__
using xo::unit::stringliteral_concat;
using xo::unit::stringliteral_from_ratio;
using xo::unit::bpu_assemble_abbrev_helper;
using xo::unit::bpu_assemble_abbrev;
#endif
using xo::unit::bpu_node;
using xo::unit::wrap_unit;
using xo::unit::unit_abbrev_v;
//using xo::unit::dim_abbrev_v;
using xo::unit::di_cartesian_product;
using xo::unit::di_cartesian_product1;
using xo::unit::unit_cartesian_product_t;
using xo::unit::bpu_cartesian_product;
using xo::unit::bpu_cartesian_product_helper;
using xo::unit::unit_invert_t;
using xo::unit::units::gram;
using xo::unit::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 = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension"));
//log && log("(A)", xtag("foo", foo));
using t1 = unit::bpu<unit::dim::currency, std::ratio<1,1>>;
static_assert(t1::c_native_dim == unit::dim::currency);
static_assert(t1::power_type::num == 1);
static_assert(t1::power_type::den == 1);
using t2 = unit::bpu<unit::dim::time, std::ratio<1>, std::ratio<-1,2>>;
static_assert(t2::c_native_dim == unit::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<unit::lookup_bpu<d1, 0>::power_unit_type, t1>());
#ifdef NOT_USING
static_assert(unit::lo_basis_elt_of<d1>::c_lo_basis == t1::c_basis);
#endif
static_assert(unit::native_lo_bwp_of<d1>::bwp_type::c_index == 0);
static_assert(unit::native_lo_bwp_of<d1>::bwp_type::c_basis == unit::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<unit::lookup_bpu<d2, 0>::power_unit_type, t2>());
static_assert(unit::native_lo_bwp_of<d2>::bwp_type::c_index == 0);
static_assert(unit::native_lo_bwp_of<d2>::bwp_type::c_basis == unit::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<unit::lookup_bpu<d3, 0>::power_unit_type, t1>());
{
using type = unit::lookup_bpu<d3, 1>::power_unit_type;
//std::cerr << "unit::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(unit::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(unit::native_lo_bwp_of<d3>::bwp_type::c_index == 1);
static_assert(unused_same<unit::without_elt<d3, 0>::dim_type, d2>());
//using type = unit::without_elt<d3, 1>::dim_type;
//std::cerr << "unit::without_elt<d3,1>::dim_type" << xtag("type", reflect::type_name<type>()) << std::endl;
static_assert(unused_same<unit::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 = unit::dimension_impl<t2, unit::dimension_impl<t1>>; /* t^(-1/2).ccy */
REQUIRE(unused_same<unit::lookup_bpu<d3b, 0>::power_unit_type, t2>());
REQUIRE(unused_same<unit::lookup_bpu<d3b, 1>::power_unit_type, t1>());
/* lowest is in pos 0 */
static_assert(unit::native_lo_bwp_of<d3b>::bwp_type::c_index == 0);
static_assert(unused_same<unit::without_elt<d3b, 0>::dim_type, d1>());
static_assert(unused_same<unit::without_elt<d3b, 1>::dim_type, d2>());
static_assert(unused_same<unit::canonical_t<d3>, unit::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 = 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.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 = 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.dimension3"));
//log && log("(A)", xtag("foo", foo));
using u1 = unit_invert_t<second>;
log && log(xtag("second^-1", Reflect::require<u1>()->canonical_name()));
log && log(xtag("u1", unit_abbrev_v<u1>.c_str()));
REQUIRE(strcmp(unit_abbrev_v<u1>.c_str(), "s^-1") == 0);
using u2 = second;
log && log(xtag("second", Reflect::require<u2>()->canonical_name()));
log && log(xtag("u2", unit_abbrev_v<u2>.c_str()));
using u1u2 = unit_cartesian_product_t<u1, u2>;
log && log(xtag("u1u2", Reflect::require<u1u2>()->canonical_name()));
#ifdef NOT_USING
using di1 = d1::dim_type;
using di2 = d2::dim_type;
using di1di2 = di_cartesian_product<di1,di2>::type;
log && log(xtag("di1di2", Reflect::require<di1di2>()->canonical_name()));
#endif
using f1 = u1::dim_type::front_type;
using r1 = u1::dim_type::rest_type;
using tmp = di_cartesian_product1<f1, r1, u2::dim_type>;
log && log(xtag("f1", Reflect::require<f1>()->canonical_name()));
log && log(xtag("r1", Reflect::require<r1>()->canonical_name()));
log && log(xtag("(f1.r1).outer_scalefactor_type", Reflect::require<tmp::outer_scalefactor_type>()->canonical_name()));
log && log(xtag("(f1.r1).bpu_list_type", Reflect::require<tmp::bpu_list_type>()->canonical_name()));
using tmp2 = bpu_cartesian_product<f1, u2::dim_type>;
log && log(xtag("(f1.u2).outer_scalefactor_type", Reflect::require<tmp2::outer_scalefactor_type>()->canonical_name()));
log && log(xtag("(f1.u2).bpu_list_type", Reflect::require<tmp2::bpu_list_type>()->canonical_name()));
using f2 = u2::dim_type::front_type;
log && log(xtag("f2", Reflect::require<f2>()->canonical_name()));
using tmp3 = bpu_cartesian_product_helper<f1, f2, void>;
log && log(xtag("(f1.f2).outer_scalefactor_type", Reflect::require<tmp3::outer_scalefactor_type>()->canonical_name()));
log && log(xtag("(f1.f2).bpu_list_type", Reflect::require<tmp3::bpu_list_type>()->canonical_name()));
} /*TEST_CASE(dimension3)*/
} /*namespace ut*/
} /*namespace xo*/
/* end dimension.test.cpp */

View file

@ -0,0 +1,422 @@
/* @file natural_unit.test.cpp */
#include "xo/unit/scaled_unit.hpp"
#include "xo/unit/scaled_unit_iostream.hpp"
#include "xo/unit/natural_unit.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <catch2/catch.hpp>
namespace xo {
using xo::qty::detail::su_product;
using xo::qty::detail::su_ratio;
using xo::qty::detail::nu_ratio_inplace;
using xo::qty::detail::nu_maker;
using xo::qty::detail::bpu2_rescale; // -> nu_rescale or bpu_rescale
namespace qty {
using nu64_type = natural_unit<std::int64_t>;
/* compile-time test:
* verify we can use an nu64_type instance as a non-type template parameter.
* Will need this for quantity<Repr, Int, natural_unit<Int>>
*/
template <nu64_type nu>
constexpr nu_abbrev_type nu_mpl_abbrev = nu.abbrev();
TEST_CASE("natural_unit0", "[natural_unit]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.natural_unit0"));
{
constexpr natural_unit<int64_t> v
= (nu_maker<int64_t>::make_nu
(bpu<int64_t>(dim::distance, scalefactor_ratio_type(1, 1000), power_ratio_type(2, 1)),
bpu<int64_t>(dim::mass, scalefactor_ratio_type(1, 1000), power_ratio_type(-1, 1))));
static_assert(v.n_bpu() == 2);
log && log(xtag("v.abbrev", v.abbrev()));
static_assert(v.abbrev().size() > 0);
static_assert(v.abbrev() == flatstring("mm^2.mg^-1"));
}
} /*TEST_CASE(natural_unit0)*/
TEST_CASE("natural_unit1", "[natural_unit]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.natural_unit1"));
{
constexpr natural_unit<int64_t> v
= (nu_maker<int64_t>::make_nu
(bpu<int64_t>(dim::distance, scalefactor_ratio_type(1000, 1), power_ratio_type(2, 1))));
static_assert(v.n_bpu() == 1);
log && log(xtag("v.abbrev", v.abbrev()));
static_assert(v.abbrev().size() > 0);
static_assert(v.abbrev() == flatstring("km^2"));
}
} /*TEST_CASE(natural_unit1)*/
TEST_CASE("natural_unit2", "[natural_unit]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.natural_unit2"));
{
constexpr natural_unit<int64_t> v
= (nu_maker<int64_t>::make_nu
(bpu<int64_t>(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(1, 1)),
bpu<int64_t>(dim::distance, scalefactor_ratio_type(1, 1), power_ratio_type(1, 1)),
bpu<int64_t>(dim::time, scalefactor_ratio_type(1, 1), power_ratio_type(-2, 1))));
static_assert(v.n_bpu() == 3);
log && log(xtag("v.abbrev", v.abbrev()));
static_assert(v.abbrev().size() > 0);
static_assert(v.abbrev() == flatstring("kg.m.s^-2"));
}
} /*TEST_CASE(natural_unit2)*/
TEST_CASE("natural_unit3", "[natural_unit]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.natural_unit3"));
{
constexpr natural_unit<int64_t> v
= (nu_maker<int64_t>::make_nu
(bpu<int64_t>(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(1, 1)),
bpu<int64_t>(dim::distance, scalefactor_ratio_type(1, 1), power_ratio_type(1, 1))));
static_assert(v.n_bpu() == 2);
log && log(xtag("v.abbrev", v.abbrev()));
static_assert(v.abbrev().size() > 0);
static_assert(v.abbrev() == flatstring("kg.m"));
{
natural_unit<int64_t> w = v;
nu_ratio_inplace(&w,
bpu<int64_t>(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(1, 1)));
log && log(xtag("w.abbrev", w.abbrev()));
REQUIRE(w.n_bpu() == 1);
REQUIRE(w[0].native_dim() == dim::distance);
REQUIRE(w.abbrev() == flatstring("m"));
}
{
constexpr natural_unit<int64_t> w
= (nu_maker<int64_t>::make_nu
(bpu<int64_t>(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(1, 1))));
static_assert(w.n_bpu() == 1);
log && log(xtag("w.abbrev", w.abbrev()));
constexpr auto rr = su_ratio<int64_t, __int128_t>(v, w);
log && log(xtag("rr", rr));
REQUIRE(rr.natural_unit_.n_bpu() == 1);
REQUIRE(rr.natural_unit_[0].native_dim() == dim::distance);
REQUIRE(rr.natural_unit_.abbrev() == flatstring("m"));
}
{
constexpr natural_unit<int64_t> w
= (nu_maker<int64_t>::make_nu
(bpu<int64_t>(dim::time, scalefactor_ratio_type(1, 1), power_ratio_type(1, 1))));
static_assert(w.n_bpu() == 1);
log && log(xtag("w.abbrev", w.abbrev()));
constexpr auto rr = su_ratio<int64_t, __int128_t>(v, w);
log && log(xtag("rr", rr));
REQUIRE(rr.natural_unit_.n_bpu() == 3);
REQUIRE(rr.natural_unit_[0].native_dim() == dim::mass);
REQUIRE(rr.natural_unit_[1].native_dim() == dim::distance);
REQUIRE(rr.natural_unit_[2].native_dim() == dim::time);
REQUIRE(rr.natural_unit_.abbrev() == flatstring("kg.m.s^-1"));
}
{
natural_unit<int64_t> w = v;
REQUIRE(w.n_bpu() == 2);
REQUIRE(w[0].native_dim() == dim::mass);
nu_ratio_inplace(&w,
bpu<int64_t>(dim::time, scalefactor_ratio_type(1, 1), power_ratio_type(2, 1)));
REQUIRE(w.n_bpu() == 3);
REQUIRE(w[0].native_dim() == dim::mass);
REQUIRE(w[1].native_dim() == dim::distance);
REQUIRE(w[2].native_dim() == dim::time);
log && log(xtag("w.abbrev", w.abbrev()));
REQUIRE(w.n_bpu() == 3);
REQUIRE(w.abbrev() == flatstring("kg.m.s^-2"));
}
}
} /*TEST_CASE(natural_unit3)*/
TEST_CASE("bpu_rescale", "[bpu_rescale]") {
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.bpu_rescale"));
//log && log("(A)", xtag("foo", foo));
/* keep spelled-out test. Will generalize to fractional powers when c++26 available */
{
constexpr auto p = power_ratio_type(1, 1);
constexpr auto orig_bpu = bpu<int64_t>(dim::mass,
scalefactor_ratio_type(1000, 1),
power_ratio_type(1, 1));
static_assert(orig_bpu.native_dim() == dim::mass);
constexpr auto new_scalefactor = scalefactor_ratio_type(1000000, 1);
constexpr auto mult = orig_bpu.scalefactor() / new_scalefactor;
static_assert(mult.num() == 1);
static_assert(mult.den() == 1000);
constexpr auto p_floor = orig_bpu.power().floor();
static_assert(p_floor == 1);
constexpr auto p_frac = orig_bpu.power().frac().template convert_to<double>();
static_assert(p_frac == 0.0);
constexpr auto outer_sf_exact = mult.power(p_floor);
static_assert(outer_sf_exact.num() == 1);
static_assert(outer_sf_exact.den() == 1000);
constexpr auto mult_inexact = mult.template convert_to<double>();
static_assert(mult_inexact == 0.001);
constexpr auto rr = bpu2_rescale<int64_t>(orig_bpu, scalefactor_ratio_type(1000000, 1));
static_assert(rr.bpu_rescaled_.power() == power_ratio_type(1,1));
static_assert(rr.outer_scale_factor_ == outer_sf_exact);
static_assert(rr.outer_scale_sq_ == 1.0);
}
/* keep spelled-out test. Will generalize to other fractional powers when c++26 available */
{
constexpr auto p = power_ratio_type(-1, 2);
constexpr auto orig_bpu = bpu<int64_t>(dim::time,
scalefactor_ratio_type(360*24*3600, 1),
power_ratio_type(-1, 2));
static_assert(orig_bpu.native_dim() == dim::time);
constexpr auto new_scalefactor = scalefactor_ratio_type(30*24*3600, 1);
/* orig ~ 360d volatility, new = 30d volatility */
constexpr auto mult = orig_bpu.scalefactor() / new_scalefactor;
log && log(xtag("mult", mult));
static_assert(mult.num() == 12);
static_assert(mult.den() == 1);
constexpr auto p_floor = orig_bpu.power().floor();
static_assert(p_floor == 0);
constexpr auto p_frac = orig_bpu.power().frac().template convert_to<double>();
static_assert(p_frac == -0.5);
constexpr auto outer_sf_exact = mult.power(p_floor);
static_assert(outer_sf_exact.num() == 1);
static_assert(outer_sf_exact.den() == 1);
constexpr auto mult_inexact = mult.template convert_to<double>();
static_assert(mult_inexact == 12.0);
constexpr auto rr = bpu2_rescale<int64_t>(orig_bpu, scalefactor_ratio_type(30*24*3600, 1));
log && log(xtag("rr.outer_scale_exact", rr.outer_scale_factor_),
xtag("rr.outer_scale_sq", rr.outer_scale_sq_));
static_assert(rr.bpu_rescaled_.power() == power_ratio_type(-1,2));
static_assert(rr.outer_scale_factor_ == outer_sf_exact);
static_assert(rr.outer_scale_sq_ == 1 / 12.0);
}
/* keep spelled-out test. Will generalize to other fractional powers when c++26 available */
{
constexpr auto p = power_ratio_type(-3, 2);
constexpr auto orig_bpu = bpu<int64_t>(dim::time,
scalefactor_ratio_type(360*24*3600, 1),
power_ratio_type(-3, 2));
static_assert(orig_bpu.native_dim() == dim::time);
constexpr auto new_scalefactor = scalefactor_ratio_type(30*24*3600, 1);
constexpr auto mult = orig_bpu.scalefactor() / new_scalefactor;
log && log(xtag("mult", mult));
static_assert(mult.num() == 12);
static_assert(mult.den() == 1);
constexpr auto p_floor = orig_bpu.power().floor();
static_assert(p_floor == -1);
constexpr auto p_frac = orig_bpu.power().frac().template convert_to<double>();
static_assert(p_frac == -0.5);
constexpr auto outer_sf_exact = mult.power(p_floor);
static_assert(outer_sf_exact.num() == 1);
static_assert(outer_sf_exact.den() == 12);
constexpr auto mult_inexact = mult.template convert_to<double>();
static_assert(mult_inexact == 12.0);
constexpr auto rr = bpu2_rescale<int64_t>(orig_bpu, scalefactor_ratio_type(30*24*3600, 1));
log && log(xtag("rr.outer_scale_exact", rr.outer_scale_factor_),
xtag("rr.outer_scale_sq", rr.outer_scale_sq_));
static_assert(rr.bpu_rescaled_.power() == power_ratio_type(-3,2));
static_assert(rr.outer_scale_factor_ == outer_sf_exact);
static_assert(rr.outer_scale_sq_ == 1 / 12.0);
}
} /*TEST_CASE(bpu_rescale)*/
TEST_CASE("bpu_product", "[bpu_product]") {
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.bpu_product"));
//log && log("(A)", xtag("foo", foo));
{
constexpr auto bpu_x = bpu<int64_t>(dim::time,
scalefactor_ratio_type(360*24*3600, 1),
power_ratio_type(-3,2));
static_assert(bpu_x.native_dim() == dim::time);
constexpr auto bpu_y = bpu<int64_t>(dim::time,
scalefactor_ratio_type(360*24*3600, 1),
power_ratio_type(1,2));
static_assert(bpu_y.native_dim() == dim::time);
#ifdef NOT_USING
constexpr auto bpu_prod = bpu2_product<int64_t>(bpu_x, bpu_y);
log && log(xtag("bpu_prod.bpu_rescaled", bpu_prod.bpu_rescaled_));
log && log(xtag("bpu_prod.outer_scale_exact", bpu_prod.outer_scale_exact_));
log && log(xtag("bpu_prod.outer_scale_sq", bpu_prod.outer_scale_sq_));
static_assert(bpu_prod.bpu_rescaled_.native_dim() == dim::time);
static_assert(bpu_prod.bpu_rescaled_.scalefactor() == scalefactor_ratio_type(360*24*3600, 1));
static_assert(bpu_prod.bpu_rescaled_.power() == power_ratio_type(-1, 1));
static_assert(bpu_prod.outer_scale_exact_ == scalefactor_ratio_type(1,1));
static_assert(bpu_prod.outer_scale_sq_ == 1.0);
#endif
}
} /*TEST_CASE(bpu_product)*/
TEST_CASE("bpu_product2", "[bpu_product]") {
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.bpu_product2"));
//log && log("(A)", xtag("foo", foo));
{
constexpr auto bpu_x = bpu<int64_t>(dim::time,
scalefactor_ratio_type(360*24*3600, 1),
power_ratio_type(-3,2));
static_assert(bpu_x.native_dim() == dim::time);
constexpr auto bpu_y = bpu<int64_t>(dim::time,
scalefactor_ratio_type(30*24*3600, 1),
power_ratio_type(1,2));
static_assert(bpu_y.native_dim() == dim::time);
#ifdef NOT_USING
constexpr auto bpu_prod = bpu2_product<int64_t>(bpu_x, bpu_y);
log && log(xtag("bpu_prod.bpu_rescaled", bpu_prod.bpu_rescaled_));
log && log(xtag("bpu_prod.outer_scale_exact", bpu_prod.outer_scale_exact_));
log && log(xtag("bpu_prod.outer_scale_sq", bpu_prod.outer_scale_sq_));
static_assert(bpu_prod.bpu_rescaled_.native_dim() == dim::time);
static_assert(bpu_prod.bpu_rescaled_.scalefactor() == scalefactor_ratio_type(360*24*3600, 1));
static_assert(bpu_prod.bpu_rescaled_.power() == power_ratio_type(-1, 1));
static_assert(bpu_prod.outer_scale_exact_ == scalefactor_ratio_type(1,1));
static_assert(bpu_prod.outer_scale_sq_ == 1.0/12.0);
#endif
}
} /*TEST_CASE(bpu_product2)*/
TEST_CASE("bpu_array", "[bpu_array]") {
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.bpu_array"));
//log && log("(A)", xtag("foo", foo));
{
constexpr natural_unit<int64_t> v;
static_assert(v.n_bpu() == 0);
}
{
constexpr natural_unit<int64_t> v
= (nu_maker<int64_t>::make_nu
(bpu<int64_t>(dim::mass, scalefactor_ratio_type(1000, 1), power_ratio_type(1, 1))));
static_assert(v.n_bpu() == 1);
}
{
constexpr natural_unit<int64_t> v
= (nu_maker<int64_t>::make_nu
(bpu<int64_t>(dim::distance, scalefactor_ratio_type(1, 1000), power_ratio_type(2, 1)),
bpu<int64_t>(dim::mass, scalefactor_ratio_type(1, 1000), power_ratio_type(-1, 1))));
static_assert(v.n_bpu() == 2);
}
} /*TEST_CASE(bpu_array)*/
} /*namespace qty*/
} /*namespace xo*/
/* end natural_unit.test.cpp */

Some files were not shown because too many files have changed in this diff Show more