Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/build_and_test_compiler_zoo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,44 @@ jobs:
shell: bash
run: cmake --build ${{runner.workspace}}/build --target test

string_reals_build:
name: String Reals Build and Test
runs-on: ubuntu-latest
container:
image: dbwy/chemistry
strategy:
matrix:
header_only: [ON,OFF]

steps:
- uses: actions/checkout@v3

- name: Setup Compiler
shell: bash
run: $GITHUB_WORKSPACE/.github/workflows/scripts/compiler_setup.sh gnu 12

- name: Setup Build Type
shell: bash
run: echo "set(CMAKE_BUILD_TYPE Release CACHE BOOL \"\" FORCE)" >>
${GITHUB_WORKSPACE}/${GH_ACTIONS_TOOLCHAIN}

- name: Configure CMake
shell: bash
run: cmake -S $GITHUB_WORKSPACE -B ${{runner.workspace}}/build
-DINTEGRATORXX_ENABLE_STRING_REALS=ON
-DINTEGRATORXX_HEADER_ONLY=${{matrix.header_only}}
-DCMAKE_INSTALL_PREFIX=${{runner.workspace}}/install
-DCMAKE_PREFIX_PATH=${ENV_PREFIX_PATH}
-DCMAKE_TOOLCHAIN_FILE=${GITHUB_WORKSPACE}/${GH_ACTIONS_TOOLCHAIN}

- name: Build
shell: bash
run: cmake --build ${{runner.workspace}}/build -j2

- name: Test
shell: bash
run: cmake --build ${{runner.workspace}}/build --target test

subproject_build:
name: Build as Subproject
needs: release_build
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.*.swp
build*/
10 changes: 10 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ cmake_minimum_required( VERSION 3.17 ) # Require CMake 3.17+
project( IntegratorXX VERSION 1.2.0 LANGUAGES CXX )

option(INTEGRATORXX_HEADER_ONLY "Force header-only build" OFF)
option(INTEGRATORXX_ENABLE_STRING_REALS
"Carry non-integral literals as their decimal source text" OFF)

if(INTEGRATORXX_HEADER_ONLY)
add_library( integratorxx INTERFACE )
Expand All @@ -21,6 +23,14 @@ target_include_directories( integratorxx
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
)

# Non-integral literals carried as text rather than as a pre-rounded double.
# Applied to the target rather than to a directory so that, in the non
# header-only build, the library's own TUs in src/ and its consumers agree on
# what `ixx_real` is; they would otherwise disagree across the ABI.
if(INTEGRATORXX_ENABLE_STRING_REALS)
target_compile_definitions( integratorxx ${INTEGRATORXX_TARGET_TYPE} ENABLE_STRING_REALS )
endif()

include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-Wno-missing-braces" INTEGRATORXX_HAS_NO_MISSING_BRACES )
if( INTEGRATORXX_HAS_NO_MISSING_BRACES )
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,26 @@ To use the runtime generator header-only, one needs to include
`<integratorxx/generators/impl/impl.hpp>` **exactly once** per project,
otherwise duplicate / incompatible symbols will occur.

### High-precision literals

The tabulated solid-angle grids (Lebedev-Laikov, Delley, Ahrens-Beylkin,
Womersley) are stored as `IntegratorXX::ixx_real`, which is `double` by default.
A `double` literal has already been rounded by the time any code can inspect it,
so a type more precise than `double` -- or one that must bound its own error --
cannot recover the value the table intended.

Setting `INTEGRATORXX_ENABLE_STRING_REALS=ON` makes `ixx_real` a
`std::string_view` instead, so the tables carry the exact decimal source text of
each entry and the conversion to the quadrature's value type happens on read.
The default `IntegratorXX::fp_traits::from_real` still parses that text via
`double`, so this option changes nothing on its own; it exists so that a type
which specializes `from_real` can parse the decimal directly. Unless you have
such a type, leave it `OFF`.

**N.B.** the setting changes the type of a table entry, so it is not ABI-neutral:
IntegratorXX and everything that includes its headers must be built with the same
value.

## Contributing and Bug Reports

We welcome any and all contributions and encourage bug reports. Please use the
Expand Down
92 changes: 92 additions & 0 deletions include/integratorxx/config.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#pragma once

#include <cstddef>

#ifdef ENABLE_STRING_REALS
#include <string_view>
#endif

/** @file config.hpp
*
* Build-time policy for how numeric literals are spelled and carried.
*
* Quadrature code distinguishes two kinds of literal:
*
* - **Integral** values, which are exactly representable in every arithmetic
* type the library supports. These are written `IXX_INT(2)` and converted
* with `fp_traits<T>::from_integer`. Note that a value is integral by its
* *value*, not its spelling: a `2.0` in the source is an integral literal.
*
* - **Non-integral** values, which are not exactly representable. These are
* written `IXX_REAL(0.6931471805599453) `and converted with
* `fp_traits<T>::from_real`.
*
* Keeping the two apart lets arithmetic be carried in exact integers for as
* long as possible, converting once at the end, and lets a ratio of two
* integers go through `fp_traits<T>::divide_integer`, which is a single
* correctly-rounded operation rather than a pre-rounded constant.
*
* When `ENABLE_STRING_REALS` is defined, `IXX_REAL` captures the *source text*
* of its argument instead of a `double`. A `double` literal has already lost
* precision by the time any code can inspect it, so a type more precise than
* `double` -- or one that must bound its own error -- cannot recover the
* intended value from it. Carrying the decimal text lets such a type parse it
* directly. See `fp_traits::from_real`.
*/

namespace IntegratorXX {

/** @brief Integer type for counts, indices, and integral literals.
*
* Signed on purpose. Intermediate expressions routinely go negative
* (`2*i - 1`, `npts - 1`, `M - 1`), and an unsigned type would wrap silently
* rather than produce a negative value.
*/
using ixx_int = std::ptrdiff_t;

#ifdef ENABLE_STRING_REALS
/** @brief Non-integral literals carried as their exact decimal source text.
*
* `std::string_view` rather than `std::string`: the solid-angle tables are
* `static constexpr`, which requires a literal type.
*/
using ixx_real = std::string_view;
#else
/// Non-integral literals carried as `double`.
using ixx_real = double;
#endif

} // namespace IntegratorXX

/** @brief Denote an integral literal or an integral expression.
*
* Parenthesised because expressions are passed, e.g. `IXX_INT(2*i - 1)`.
*
* @warning The cast applies to the *result*, not the operands. If the operands
* are unsigned, `IXX_INT(2*i - 1)` evaluates in unsigned arithmetic and only
* then converts, so an underflow has already happened. Convert the count once
* at the top of a scope (`const ixx_int n = IXX_INT(npts);`) and derive the
* rest from it.
*/
#define IXX_INT(x) (static_cast<::IntegratorXX::ixx_int>(x))

#ifdef ENABLE_STRING_REALS
# define IXX_REAL(x) (::IntegratorXX::ixx_real(#x))
#else
/** @brief Denote a non-integral literal.
*
* Deliberately expands to its argument unchanged rather than to `(x)`. The
* argument is by contract a single numeric literal token, so no parentheses
* are needed for precedence, and the omission makes the default build's token
* stream byte-identical to one written with bare literals -- which matters
* when the expansion happens ~1.9 million times across the tables.
*/
# define IXX_REAL(x) x
#endif

namespace IntegratorXX {

/// pi. Spelled as digits because `IXX_REAL(M_PI)` would stringify to "M_PI".
inline constexpr ixx_real ixx_pi = IXX_REAL(3.14159265358979323846);

} // namespace IntegratorXX
Loading