diff --git a/Doxyfile b/Doxyfile index bc9cfbd..37f24f4 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = fourdst::libcomposition # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = v2.0.6 +PROJECT_NUMBER = v2.1.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewers a diff --git a/build-config/meson.build b/build-config/meson.build index efdf5a1..42a90e8 100644 --- a/build-config/meson.build +++ b/build-config/meson.build @@ -2,4 +2,5 @@ cmake = import('cmake') subdir('fourdst') subdir('cppad') +subdir('xxHash') diff --git a/build-config/xxHash/LICENSE b/build-config/xxHash/LICENSE new file mode 100644 index 0000000..94092e1 --- /dev/null +++ b/build-config/xxHash/LICENSE @@ -0,0 +1,33 @@ +xxHash - Extremely Fast Hash algorithm +Header File +Copyright (C) 2012-2021 Yann Collet + +BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + +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 +OWNER 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. + +You can contact the author at: + - xxHash homepage: https://www.xxhash.com + - xxHash source repository: https://github.com/Cyan4973/xxHash + diff --git a/build-config/xxHash/include/constexpr-xxh3.h b/build-config/xxHash/include/constexpr-xxh3.h new file mode 100644 index 0000000..c5840bc --- /dev/null +++ b/build-config/xxHash/include/constexpr-xxh3.h @@ -0,0 +1,355 @@ +/* +BSD 2-Clause License + +constexpr-xxh3 - C++20 constexpr implementation of the XXH3 64-bit variant of xxHash +Copyright (c) 2021-2023, chys +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. + +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. +*/ + +/* +This file uses code from Yann Collet's xxHash implementation. +Original xxHash copyright notice: + +xxHash - Extremely Fast Hash algorithm +Header File +Copyright (C) 2012-2020 Yann Collet +*/ + +#pragma once + +#include +#include +#include // for std::data, std::size +#include +#include + +namespace constexpr_xxh3 { + +template +concept ByteType = (std::is_integral_v && sizeof(T) == 1) +#if defined __cpp_lib_byte && __cpp_lib_byte >= 201603 + || std::is_same_v +#endif + ; + +template +concept BytePtrType = requires (T ptr) { + requires std::is_pointer_v; + requires ByteType>; +}; + +template +concept BytesType = requires (const T& bytes) { + { std::data(bytes) }; + requires BytePtrType; + // -> std::convertible_to is not supported widely enough + { static_cast(std::size(bytes)) }; +}; + +inline constexpr uint32_t swap32(uint32_t x) noexcept { + return ((x << 24) & 0xff000000) | ((x << 8) & 0x00ff0000) | + ((x >> 8) & 0x0000ff00) | ((x >> 24) & 0x000000ff); +} + +template +inline constexpr uint32_t readLE32(const T* ptr) noexcept { + return uint8_t(ptr[0]) | uint32_t(uint8_t(ptr[1])) << 8 | + uint32_t(uint8_t(ptr[2])) << 16 | uint32_t(uint8_t(ptr[3])) << 24; +} + +inline constexpr uint64_t swap64(uint64_t x) noexcept { + return ((x << 56) & 0xff00000000000000ULL) | + ((x << 40) & 0x00ff000000000000ULL) | + ((x << 24) & 0x0000ff0000000000ULL) | + ((x << 8) & 0x000000ff00000000ULL) | + ((x >> 8) & 0x00000000ff000000ULL) | + ((x >> 24) & 0x0000000000ff0000ULL) | + ((x >> 40) & 0x000000000000ff00ULL) | + ((x >> 56) & 0x00000000000000ffULL); +} + +template +inline constexpr uint64_t readLE64(const T* ptr) noexcept { + return readLE32(ptr) | uint64_t(readLE32(ptr + 4)) << 32; +} + +inline constexpr void writeLE64(uint8_t* dst, uint64_t v) noexcept { + for (int i = 0; i < 8; ++i) dst[i] = uint8_t(v >> (i * 8)); +} + +inline constexpr uint32_t PRIME32_1 = 0x9E3779B1U; +inline constexpr uint32_t PRIME32_2 = 0x85EBCA77U; +inline constexpr uint32_t PRIME32_3 = 0xC2B2AE3DU; + +inline constexpr uint64_t PRIME64_1 = 0x9E3779B185EBCA87ULL; +inline constexpr uint64_t PRIME64_2 = 0xC2B2AE3D27D4EB4FULL; +inline constexpr uint64_t PRIME64_3 = 0x165667B19E3779F9ULL; +inline constexpr uint64_t PRIME64_4 = 0x85EBCA77C2B2AE63ULL; +inline constexpr uint64_t PRIME64_5 = 0x27D4EB2F165667C5ULL; + +inline constexpr size_t SECRET_DEFAULT_SIZE = 192; +inline constexpr size_t SECRET_SIZE_MIN = 136; + +inline constexpr uint8_t kSecret[SECRET_DEFAULT_SIZE]{ + 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, + 0xf7, 0x21, 0xad, 0x1c, 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, + 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f, 0xcb, 0x79, 0xe6, 0x4e, + 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21, + 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, + 0x81, 0x3a, 0x26, 0x4c, 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, + 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3, 0x71, 0x64, 0x48, 0x97, + 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8, + 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, + 0xc7, 0x0b, 0x4f, 0x1d, 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, + 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64, 0xea, 0xc5, 0xac, 0x83, + 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb, + 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, + 0x29, 0xd4, 0x68, 0x9e, 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, + 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce, 0x45, 0xcb, 0x3a, 0x8f, + 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e, +}; + +inline constexpr std::pair mult64to128( + uint64_t lhs, uint64_t rhs) noexcept { + uint64_t lo_lo = uint64_t(uint32_t(lhs)) * uint32_t(rhs); + uint64_t hi_lo = (lhs >> 32) * uint32_t(rhs); + uint64_t lo_hi = uint32_t(lhs) * (rhs >> 32); + uint64_t hi_hi = (lhs >> 32) * (rhs >> 32); + uint64_t cross = (lo_lo >> 32) + uint32_t(hi_lo) + lo_hi; + uint64_t upper = (hi_lo >> 32) + (cross >> 32) + hi_hi; + uint64_t lower = (cross << 32) | uint32_t(lo_lo); + return {lower, upper}; +} + +inline constexpr uint64_t mul128_fold64(uint64_t lhs, uint64_t rhs) noexcept { +#if defined __GNUC__ && __WORDSIZE >= 64 + // It appears both GCC and Clang support evaluating __int128 as constexpr + auto product = static_cast(lhs) * rhs; + return uint64_t(product >> 64) ^ uint64_t(product); +#else + auto product = mult64to128(lhs, rhs); + return product.first ^ product.second; +#endif +} + +inline constexpr uint64_t XXH64_avalanche(uint64_t h) noexcept { + h = (h ^ (h >> 33)) * PRIME64_2; + h = (h ^ (h >> 29)) * PRIME64_3; + return h ^ (h >> 32); +} + +inline constexpr uint64_t XXH3_avalanche(uint64_t h) noexcept { + h = (h ^ (h >> 37)) * 0x165667919E3779F9ULL; + return h ^ (h >> 32); +} + +inline constexpr uint64_t rrmxmx(uint64_t h, uint64_t len) noexcept { + h ^= ((h << 49) | (h >> 15)) ^ ((h << 24) | (h >> 40)); + h *= 0x9FB21C651E98DF25ULL; + h ^= (h >> 35) + len; + h *= 0x9FB21C651E98DF25ULL; + return h ^ (h >> 28); +} + +template +constexpr uint64_t mix16B(const T* input, const S* secret, + uint64_t seed) noexcept { + return mul128_fold64(readLE64(input) ^ (readLE64(secret) + seed), + readLE64(input + 8) ^ (readLE64(secret + 8) - seed)); +} + +inline constexpr size_t STRIPE_LEN = 64; +inline constexpr size_t SECRET_CONSUME_RATE = 8; +inline constexpr size_t ACC_NB = STRIPE_LEN / sizeof(uint64_t); + +template +constexpr void accumulate_512(uint64_t* acc, const T* input, + const S* secret) noexcept { + for (size_t i = 0; i < ACC_NB; i++) { + uint64_t data_val = readLE64(input + 8 * i); + uint64_t data_key = data_val ^ readLE64(secret + i * 8); + acc[i ^ 1] += data_val; + acc[i] += uint32_t(data_key) * (data_key >> 32); + } +} + +template +constexpr uint64_t hashLong_64b_internal(const T* input, size_t len, + const S* secret, + size_t secretSize) noexcept { + uint64_t acc[ACC_NB]{PRIME32_3, PRIME64_1, PRIME64_2, PRIME64_3, + PRIME64_4, PRIME32_2, PRIME64_5, PRIME32_1}; + size_t nbStripesPerBlock = (secretSize - STRIPE_LEN) / SECRET_CONSUME_RATE; + size_t block_len = STRIPE_LEN * nbStripesPerBlock; + size_t nb_blocks = (len - 1) / block_len; + + for (size_t n = 0; n < nb_blocks; n++) { + for (size_t i = 0; i < nbStripesPerBlock; i++) + accumulate_512(acc, input + n * block_len + i * STRIPE_LEN, + secret + i * SECRET_CONSUME_RATE); + for (size_t i = 0; i < ACC_NB; i++) + acc[i] = (acc[i] ^ (acc[i] >> 47) ^ + readLE64(secret + secretSize - STRIPE_LEN + 8 * i)) * + PRIME32_1; + } + + size_t nbStripes = ((len - 1) - (block_len * nb_blocks)) / STRIPE_LEN; + for (size_t i = 0; i < nbStripes; i++) + accumulate_512(acc, input + nb_blocks * block_len + i * STRIPE_LEN, + secret + i * SECRET_CONSUME_RATE); + accumulate_512(acc, input + len - STRIPE_LEN, + secret + secretSize - STRIPE_LEN - 7); + uint64_t result = len * PRIME64_1; + for (size_t i = 0; i < 4; i++) + result += + mul128_fold64(acc[2 * i] ^ readLE64(secret + 11 + 16 * i), + acc[2 * i + 1] ^ readLE64(secret + 11 + 16 * i + 8)); + return XXH3_avalanche(result); +} + +template +constexpr uint64_t XXH3_64bits_internal(const T* input, size_t len, + uint64_t seed, const S* secret, + size_t secretLen, + HashLong f_hashLong) noexcept { + if (len == 0) { + return XXH64_avalanche(seed ^ + (readLE64(secret + 56) ^ readLE64(secret + 64))); + } else if (len < 4) { + uint64_t keyed = ((uint32_t(uint8_t(input[0])) << 16) | + (uint32_t(uint8_t(input[len >> 1])) << 24) | + uint8_t(input[len - 1]) | (uint32_t(len) << 8)) ^ + ((readLE32(secret) ^ readLE32(secret + 4)) + seed); + return XXH64_avalanche(keyed); + } else if (len <= 8) { + uint64_t keyed = + (readLE32(input + len - 4) + (uint64_t(readLE32(input)) << 32)) ^ + ((readLE64(secret + 8) ^ readLE64(secret + 16)) - + (seed ^ (uint64_t(swap32(uint32_t(seed))) << 32))); + return rrmxmx(keyed, len); + } else if (len <= 16) { + uint64_t input_lo = + readLE64(input) ^ + ((readLE64(secret + 24) ^ readLE64(secret + 32)) + seed); + uint64_t input_hi = + readLE64(input + len - 8) ^ + ((readLE64(secret + 40) ^ readLE64(secret + 48)) - seed); + uint64_t acc = + len + swap64(input_lo) + input_hi + mul128_fold64(input_lo, input_hi); + return XXH3_avalanche(acc); + } else if (len <= 128) { + uint64_t acc = len * PRIME64_1; + size_t secret_off = 0; + for (size_t i = 0, j = len; j > i; i += 16, j -= 16) { + acc += mix16B(input + i, secret + secret_off, seed); + acc += mix16B(input + j - 16, secret + secret_off + 16, seed); + secret_off += 32; + } + return XXH3_avalanche(acc); + } else if (len <= 240) { + uint64_t acc = len * PRIME64_1; + for (size_t i = 0; i < 128; i += 16) + acc += mix16B(input + i, secret + i, seed); + acc = XXH3_avalanche(acc); + for (size_t i = 128; i < len / 16 * 16; i += 16) + acc += mix16B(input + i, secret + (i - 128) + 3, seed); + acc += mix16B(input + len - 16, secret + SECRET_SIZE_MIN - 17, seed); + return XXH3_avalanche(acc); + } else { + return f_hashLong(input, len, seed, secret, secretLen); + } +} + +template +constexpr size_t bytes_size(const Bytes& bytes) noexcept { + return std::size(bytes); +} + +template +constexpr size_t bytes_size(T (&)[N]) noexcept { + return (N ? N - 1 : 0); +} + +/// Basic interfaces + +template +consteval uint64_t XXH3_64bits_const(const T* input, size_t len) noexcept { + return XXH3_64bits_internal( + input, len, 0, kSecret, sizeof(kSecret), + [](const T* input, size_t len, uint64_t, const void*, + size_t) constexpr noexcept { + return hashLong_64b_internal(input, len, kSecret, sizeof(kSecret)); + }); +} + +template +consteval uint64_t XXH3_64bits_withSecret_const(const T* input, size_t len, + const S* secret, + size_t secretSize) noexcept { + return XXH3_64bits_internal( + input, len, 0, secret, secretSize, + [](const T* input, size_t len, uint64_t, const S* secret, + size_t secretLen) constexpr noexcept { + return hashLong_64b_internal(input, len, secret, secretLen); + }); +} + +template +consteval uint64_t XXH3_64bits_withSeed_const(const T* input, size_t len, + uint64_t seed) noexcept { + if (seed == 0) return XXH3_64bits_const(input, len); + return XXH3_64bits_internal( + input, len, seed, kSecret, sizeof(kSecret), + [](const T* input, size_t len, uint64_t seed, const void*, + size_t) constexpr noexcept { + uint8_t secret[SECRET_DEFAULT_SIZE]; + for (size_t i = 0; i < SECRET_DEFAULT_SIZE; i += 16) { + writeLE64(secret + i, readLE64(kSecret + i) + seed); + writeLE64(secret + i + 8, readLE64(kSecret + i + 8) - seed); + } + return hashLong_64b_internal(input, len, secret, sizeof(secret)); + }); +} + +/// Convenient interfaces + +template +consteval uint64_t XXH3_64bits_const(const Bytes& input) noexcept { + return XXH3_64bits_const(std::data(input), bytes_size(input)); +} + +template +consteval uint64_t XXH3_64bits_withSecret_const(const Bytes& input, + const Secret& secret) noexcept { + return XXH3_64bits_withSecret_const(std::data(input), bytes_size(input), + std::data(secret), bytes_size(secret)); +} + +template +consteval uint64_t XXH3_64bits_withSeed_const(const Bytes& input, + uint64_t seed) noexcept { + return XXH3_64bits_withSeed_const(std::data(input), bytes_size(input), seed); +} + +} // namespace constexpr_xxh3 diff --git a/build-config/xxHash/include/xxhash64.h b/build-config/xxHash/include/xxhash64.h new file mode 100644 index 0000000..4d0bbc5 --- /dev/null +++ b/build-config/xxHash/include/xxhash64.h @@ -0,0 +1,202 @@ +// ////////////////////////////////////////////////////////// +// xxhash64.h +// Copyright (c) 2016 Stephan Brumme. All rights reserved. +// see http://create.stephan-brumme.com/disclaimer.html +// + +#pragma once +#include // for uint32_t and uint64_t + +/// XXHash (64 bit), based on Yann Collet's descriptions, see http://cyan4973.github.io/xxHash/ +/** How to use: + uint64_t myseed = 0; + XXHash64 myhash(myseed); + myhash.add(pointerToSomeBytes, numberOfBytes); + myhash.add(pointerToSomeMoreBytes, numberOfMoreBytes); // call add() as often as you like to ... + // and compute hash: + uint64_t result = myhash.hash(); + + // or all of the above in one single line: + uint64_t result2 = XXHash64::hash(mypointer, numBytes, myseed); + + Note: my code is NOT endian-aware ! +**/ +class XXHash64 +{ +public: + /// create new XXHash (64 bit) + /** @param seed your seed value, even zero is a valid seed **/ + explicit XXHash64(uint64_t seed) + { + state[0] = seed + Prime1 + Prime2; + state[1] = seed + Prime2; + state[2] = seed; + state[3] = seed - Prime1; + bufferSize = 0; + totalLength = 0; + } + + /// add a chunk of bytes + /** @param input pointer to a continuous block of data + @param length number of bytes + @return false if parameters are invalid / zero **/ + bool add(const void* input, uint64_t length) + { + // no data ? + if (!input || length == 0) + return false; + + totalLength += length; + // byte-wise access + const unsigned char* data = (const unsigned char*)input; + + // unprocessed old data plus new data still fit in temporary buffer ? + if (bufferSize + length < MaxBufferSize) + { + // just add new data + while (length-- > 0) + buffer[bufferSize++] = *data++; + return true; + } + + // point beyond last byte + const unsigned char* stop = data + length; + const unsigned char* stopBlock = stop - MaxBufferSize; + + // some data left from previous update ? + if (bufferSize > 0) + { + // make sure temporary buffer is full (16 bytes) + while (bufferSize < MaxBufferSize) + buffer[bufferSize++] = *data++; + + // process these 32 bytes (4x8) + process(buffer, state[0], state[1], state[2], state[3]); + } + + // copying state to local variables helps optimizer A LOT + uint64_t s0 = state[0], s1 = state[1], s2 = state[2], s3 = state[3]; + // 32 bytes at once + while (data <= stopBlock) + { + // local variables s0..s3 instead of state[0]..state[3] are much faster + process(data, s0, s1, s2, s3); + data += 32; + } + // copy back + state[0] = s0; state[1] = s1; state[2] = s2; state[3] = s3; + + // copy remainder to temporary buffer + bufferSize = stop - data; + for (uint64_t i = 0; i < bufferSize; i++) + buffer[i] = data[i]; + + // done + return true; + } + + /// get current hash + /** @return 64 bit XXHash **/ + uint64_t hash() const + { + // fold 256 bit state into one single 64 bit value + uint64_t result; + if (totalLength >= MaxBufferSize) + { + result = rotateLeft(state[0], 1) + + rotateLeft(state[1], 7) + + rotateLeft(state[2], 12) + + rotateLeft(state[3], 18); + result = (result ^ processSingle(0, state[0])) * Prime1 + Prime4; + result = (result ^ processSingle(0, state[1])) * Prime1 + Prime4; + result = (result ^ processSingle(0, state[2])) * Prime1 + Prime4; + result = (result ^ processSingle(0, state[3])) * Prime1 + Prime4; + } + else + { + // internal state wasn't set in add(), therefore original seed is still stored in state2 + result = state[2] + Prime5; + } + + result += totalLength; + + // process remaining bytes in temporary buffer + const unsigned char* data = buffer; + // point beyond last byte + const unsigned char* stop = data + bufferSize; + + // at least 8 bytes left ? => eat 8 bytes per step + for (; data + 8 <= stop; data += 8) + result = rotateLeft(result ^ processSingle(0, *(uint64_t*)data), 27) * Prime1 + Prime4; + + // 4 bytes left ? => eat those + if (data + 4 <= stop) + { + result = rotateLeft(result ^ (*(uint32_t*)data) * Prime1, 23) * Prime2 + Prime3; + data += 4; + } + + // take care of remaining 0..3 bytes, eat 1 byte per step + while (data != stop) + result = rotateLeft(result ^ (*data++) * Prime5, 11) * Prime1; + + // mix bits + result ^= result >> 33; + result *= Prime2; + result ^= result >> 29; + result *= Prime3; + result ^= result >> 32; + return result; + } + + + /// combine constructor, add() and hash() in one static function (C style) + /** @param input pointer to a continuous block of data + @param length number of bytes + @param seed your seed value, e.g. zero is a valid seed + @return 64 bit XXHash **/ + static uint64_t hash(const void* input, uint64_t length, uint64_t seed) + { + XXHash64 hasher(seed); + hasher.add(input, length); + return hasher.hash(); + } + +private: + /// magic constants :-) + static const uint64_t Prime1 = 11400714785074694791ULL; + static const uint64_t Prime2 = 14029467366897019727ULL; + static const uint64_t Prime3 = 1609587929392839161ULL; + static const uint64_t Prime4 = 9650029242287828579ULL; + static const uint64_t Prime5 = 2870177450012600261ULL; + + /// temporarily store up to 31 bytes between multiple add() calls + static const uint64_t MaxBufferSize = 31+1; + + uint64_t state[4]; + unsigned char buffer[MaxBufferSize]; + uint64_t bufferSize; + uint64_t totalLength; + + /// rotate bits, should compile to a single CPU instruction (ROL) + static inline uint64_t rotateLeft(uint64_t x, unsigned char bits) + { + return (x << bits) | (x >> (64 - bits)); + } + + /// process a single 64 bit value + static inline uint64_t processSingle(uint64_t previous, uint64_t input) + { + return rotateLeft(previous + input * Prime2, 31) * Prime1; + } + + /// process a block of 4x4 bytes, this is the main part of the XXHash32 algorithm + static inline void process(const void* data, uint64_t& state0, uint64_t& state1, uint64_t& state2, uint64_t& state3) + { + const uint64_t* block = (const uint64_t*) data; + state0 = processSingle(state0, block[0]); + state1 = processSingle(state1, block[1]); + state2 = processSingle(state2, block[2]); + state3 = processSingle(state3, block[3]); + } +}; diff --git a/build-config/xxHash/meson.build b/build-config/xxHash/meson.build new file mode 100644 index 0000000..a8551b5 --- /dev/null +++ b/build-config/xxHash/meson.build @@ -0,0 +1,4 @@ + +xxhash_dep = declare_dependency( + include_directories: include_directories('include') +) \ No newline at end of file diff --git a/meson.build b/meson.build index 358f202..b16b3e1 100644 --- a/meson.build +++ b/meson.build @@ -18,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # *********************************************************************** # -project('libcomposition', 'cpp', version: 'v2.0.6', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0') +project('libcomposition', 'cpp', version: 'v2.1.0', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0') # Add default visibility for all C++ targets add_project_arguments('-fvisibility=default', language: 'cpp') diff --git a/src/composition/include/fourdst/composition/composition.h b/src/composition/include/fourdst/composition/composition.h index 9cbf27d..98f8e9e 100644 --- a/src/composition/include/fourdst/composition/composition.h +++ b/src/composition/include/fourdst/composition/composition.h @@ -274,6 +274,8 @@ namespace fourdst::composition { */ Composition(const Composition& composition); + explicit Composition(const CompositionAbstract& composition); + /** * @brief Assignment operator. * @param other The Composition to assign from. @@ -820,4 +822,22 @@ namespace fourdst::composition { } }; + + inline bool operator==(const Composition& a, const Composition& b) noexcept { + if (a.size() != b.size()) return false; + + // Compare species sets quickly + if (a.getRegisteredSpecies() != b.getRegisteredSpecies()) + return false; + + // Compare all abundances + for (auto itA = a.begin(), itB = b.begin(); + itA != a.end() && itB != b.end(); ++itA, ++itB) { + if (itA->first != itB->first) + return false; + if (itA->second != itB->second) + return false; + } + return true; + } }; // namespace fourdst::composition diff --git a/src/composition/include/fourdst/composition/utils/composition_hash.h b/src/composition/include/fourdst/composition/utils/composition_hash.h new file mode 100644 index 0000000..f2d7e0a --- /dev/null +++ b/src/composition/include/fourdst/composition/utils/composition_hash.h @@ -0,0 +1,157 @@ +#pragma once + +#include +#include +#include +#include + +#include "xxhash64.h" + +namespace fourdst::composition::utils { + struct CompositionHash { + static constexpr uint64_t kSeed = 0xC04D5EEDBEEFull; + static constexpr char kTag[] = "4DSTAR:Composition"; + + template + static uint64_t hash_exact(const CompositionT& comp) { + std::vector buf; + reserve_bytes(comp, buf); + write_header(comp, buf); + + for (auto it = comp.begin(); it != comp.end(); ++it) { + const auto& species = it->first; + const double abundance = it->second; + + const std::uint32_t spWord = pack_species(species); + push_le32(buf, spWord); + + const std::uint64_t bits = normalize_double_bits(abundance); + push_le64(buf, bits); + } + + return XXHash64::hash(buf.data(), buf.size(), kSeed); + } + + static inline bool is_finite(double v) noexcept { + return std::isfinite(v); + } + + static inline std::int64_t quantize_index(double v, double eps) noexcept { + const auto ld_v = static_cast(v); + const auto ld_eps = static_cast(eps); + + const long double scaled = ld_v / ld_eps; + const long long idx = std::llroundl(scaled); + return static_cast(idx); + } + + template + static uint64_t hash_quantized(const CompositionT& comp, double eps) noexcept { + std::vector buf; + reserve_bytes(comp, buf); + write_header(comp, buf); + push_bytes(buf, reinterpret_cast("quantized"), 9); + push_le64(buf, encode_fp64(eps)); + + for (auto it = comp.begin(); it != comp.end(); ++it) { + const auto& species = it->first; + const double abundance = it->second; + + const std::uint32_t spWord = pack_species(species); + push_le32(buf, spWord); + + if (!is_finite(abundance) || eps <= 0.0) { + const std::uint64_t bits = normalize_double_bits(abundance); + push_le64(buf, bits); + } else { + const std::int64_t idx = quantize_index(abundance, eps); + push_le64(buf, static_cast(idx)); + } + } + + return XXHash64::hash(buf.data(), buf.size(), kSeed ^ 0x7319'BEEF'1234ull); + } + + + private: + template + static std::uint32_t pack_species(const SpeciesT& s) noexcept { + // Adjust accessors if your Species API differs. + const auto z = static_cast(s.z()); + const auto a = static_cast(s.a()); + return (static_cast(z) << 16) | static_cast(a); + } + + static inline std::uint64_t normalize_double_bits(double v) noexcept { + if (v == 0.0) v = 0.0; // fold -0.0 -> +0.0 + if (std::isnan(v)) { + return 0x7ff8000000000000ULL; // canonical quiet NaN + } + return std::bit_cast(v); + } + + static inline double quantize(double v, double eps) noexcept { + if (!std::isfinite(v) || eps <= 0.0) return v; + const double q = std::nearbyint(v / eps) * eps; + return (q == 0.0) ? 0.0 : q; + } + + static inline std::uint64_t encode_fp64(double v) noexcept { + return std::bit_cast(v); + } + + // ---------- byte helpers (explicit little-endian) ---------- + static inline void push_le32(std::vector& b, std::uint32_t x) { + b.push_back(static_cast( x & 0xFF)); + b.push_back(static_cast((x >> 8 ) & 0xFF)); + b.push_back(static_cast((x >> 16) & 0xFF)); + b.push_back(static_cast((x >> 24) & 0xFF)); + } + + static inline void push_le64(std::vector& b, std::uint64_t x) noexcept { + b.push_back(static_cast( x & 0xFF)); + b.push_back(static_cast((x >> 8 ) & 0xFF)); + b.push_back(static_cast((x >> 16) & 0xFF)); + b.push_back(static_cast((x >> 24) & 0xFF)); + b.push_back(static_cast((x >> 32) & 0xFF)); + b.push_back(static_cast((x >> 40) & 0xFF)); + b.push_back(static_cast((x >> 48) & 0xFF)); + b.push_back(static_cast((x >> 56) & 0xFF)); + } + + static inline void push_bytes(std::vector& b, const std::uint8_t* p, std::size_t n) noexcept{ + b.insert(b.end(), p, p + n); + } + + template + static void write_header(const CompositionT& comp, std::vector& buf) noexcept { + push_bytes(buf, reinterpret_cast(kTag), sizeof(kTag) - 1); + + const std::size_t nRegistered = comp.getRegisteredSpecies().size(); + std::size_t nMolar = 0; + for (auto it = comp.begin(); it != comp.end(); ++it) { ++nMolar; } + + push_le64(buf, static_cast(nRegistered)); + push_le64(buf, static_cast(nMolar)); + } + + template + static void reserve_bytes(const CompositionT& comp, std::vector& buf) noexcept { + std::size_t nMolar = 0; + for (auto it = comp.begin(); it != comp.end(); ++it) { ++nMolar; } + const std::size_t approx = (sizeof(kTag) - 1) + 16 + nMolar * (4 + 8 + 0 /*quantized flag optional*/); + buf.reserve(approx); + } + }; +} + +namespace std { + template<> + struct hash { + std::size_t operator()(const fourdst::composition::Composition& c) const noexcept { + return static_cast( + fourdst::composition::utils::CompositionHash::hash_exact(c) + ); + } + }; +} \ No newline at end of file diff --git a/src/composition/include/fourdst/config.h.in b/src/composition/include/fourdst/config.h.in new file mode 100644 index 0000000..c68bf5b --- /dev/null +++ b/src/composition/include/fourdst/config.h.in @@ -0,0 +1,5 @@ +// Define the project name +#define PROJECT_NAME @PROJECT_NAME@ + +// Define the project version +#define PROJECT_VERSION @PROJECT_VERSION@ \ No newline at end of file diff --git a/src/composition/lib/composition.cpp b/src/composition/lib/composition.cpp index 3f48c82..6646d59 100644 --- a/src/composition/lib/composition.cpp +++ b/src/composition/lib/composition.cpp @@ -165,6 +165,13 @@ namespace fourdst::composition { m_molarAbundances = composition.m_molarAbundances; } + Composition::Composition(const CompositionAbstract &composition) { + for (const auto& species : composition.getRegisteredSpecies()) { + registerSpecies(species); + setMolarAbundance(species, composition.getMolarAbundance(species)); + } + } + Composition& Composition::operator=( const Composition &other ) { diff --git a/src/composition/meson.build b/src/composition/meson.build index fc32ac4..828e8ce 100644 --- a/src/composition/meson.build +++ b/src/composition/meson.build @@ -30,7 +30,8 @@ dependencies = [ species_weight_dep, const_dep, config_dep, - log_dep + log_dep, + xxhash_dep ] # Define the libcomposition library so it can be linked against by other parts of the build system @@ -69,3 +70,15 @@ composition_exception_headers = files( 'include/fourdst/composition/exceptions/exceptions_composition.h', ) install_headers(composition_exception_headers, subdir : 'fourdst/fourdst/composition/exceptions') + +v = meson.project_version() + +conf_data = configuration_data() +conf_data.set_quoted('PROJECT_VERSION', v) +conf_data.set_quoted('PROJECT_NAME', meson.project_name()) + +configure_file( + input : 'include/fourdst/config.h.in', + output : 'config.h', + configuration : conf_data +) diff --git a/tests/composition/compositionTest.cpp b/tests/composition/compositionTest.cpp index 3637610..2201fbe 100644 --- a/tests/composition/compositionTest.cpp +++ b/tests/composition/compositionTest.cpp @@ -9,6 +9,7 @@ #include "fourdst/composition/exceptions/exceptions_composition.h" #include "fourdst/composition/utils.h" #include "fourdst/composition/decorators/composition_masked.h" +#include "fourdst/composition/utils/composition_hash.h" #include "fourdst/config/config.h" @@ -248,6 +249,102 @@ TEST_F(compositionTest, decorators) { comp.setMolarAbundance("H-1", 1.0); ASSERT_NE(mComp.getMolarAbundance(fourdst::atomic::H_1), 1.0); - - +} + +TEST_F(compositionTest, orderInvariance) { + fourdst::composition::Composition a; + a.registerSymbol("He-4"); a.registerSymbol("H-1"); a.registerSymbol("O-16"); + a.setMolarAbundance("H-1", 0.6); a.setMolarAbundance("He-4", 0.6); + + fourdst::composition::Composition b; + b.registerSymbol("O-16"); b.registerSymbol("H-1"); b.registerSymbol("He-4"); + b.setMolarAbundance("He-4", 0.6); b.setMolarAbundance("H-1", 0.6); + + const std::uint64_t ha = fourdst::composition::utils::CompositionHash::hash_exact(a); + const std::uint64_t hb = fourdst::composition::utils::CompositionHash::hash_exact(b); + ASSERT_EQ(ha, hb); +} + +TEST_F(compositionTest, negativeZeroEqualsPositiveZero) { + fourdst::composition::Composition a, b; + a.registerSymbol("H-1"); b.registerSymbol("H-1"); + a.setMolarAbundance("H-1", 0.0); + b.setMolarAbundance("H-1", -0.0); + + ASSERT_EQ(fourdst::composition::utils::CompositionHash::hash_exact(a), + fourdst::composition::utils::CompositionHash::hash_exact(b)); +} + +TEST_F(compositionTest, quantizedIgnoresTinyJitter) { + fourdst::composition::Composition a, b; + a.registerSymbol("H-1"); b.registerSymbol("H-1"); + a.setMolarAbundance("H-1", 1.0); + b.setMolarAbundance("H-1", 1.0 + 5e-14); // smaller than eps + + const double eps = 1e-12; + const auto hqa = fourdst::composition::utils::CompositionHash::hash_quantized(a, eps); + const auto hqb = fourdst::composition::utils::CompositionHash::hash_quantized(b, eps); + ASSERT_EQ(hqa, hqb); + + // But exact should differ if the bit pattern changes + const auto hea = fourdst::composition::utils::CompositionHash::hash_exact(a); + const auto heb = fourdst::composition::utils::CompositionHash::hash_exact(b); + ASSERT_NE(hea, heb); +} + +TEST_F(compositionTest, quantizedDetectsAboveEps) { + fourdst::composition::Composition a, b; + a.registerSymbol("H-1"); b.registerSymbol("H-1"); + a.setMolarAbundance("H-1", 1.0); + b.setMolarAbundance("H-1", 1.0 + 2e-12); // larger than eps + + const double eps = 1e-12; + ASSERT_NE(fourdst::composition::utils::CompositionHash::hash_quantized(a, eps), + fourdst::composition::utils::CompositionHash::hash_quantized(b, eps)); +} + +TEST_F(compositionTest, exactVsQuantizedDifferentSeeds) { + fourdst::composition::Composition a; + a.registerSymbol("H-1"); a.setMolarAbundance("H-1", 0.5); + + const auto he = fourdst::composition::utils::CompositionHash::hash_exact(a); + const auto hq = fourdst::composition::utils::CompositionHash::hash_quantized(a, 1e-12); + ASSERT_NE(he, hq); +} + +TEST_F(compositionTest, cloneAndCopyStable) { + std::vector symbols = {"H-1", "He-4"}; + std::vector abundances = {0.6, 0.4}; + fourdst::composition::Composition a(symbols, abundances); + fourdst::composition::Composition b(a); + + const auto ha = fourdst::composition::utils::CompositionHash::hash_exact(a); + const auto hb = fourdst::composition::utils::CompositionHash::hash_exact(b); + ASSERT_EQ(ha, hb); + + std::unique_ptr cptr = a.clone(); + const auto hc = fourdst::composition::utils::CompositionHash::hash_exact( + *static_cast(cptr.get())); + ASSERT_EQ(ha, hc); +} + +TEST_F(compositionTest, bothSidesRegisterSameZeroSpeciesEquality) { + fourdst::composition::Composition a, b; + a.registerSymbol("H-1"); b.registerSymbol("H-1"); + a.setMolarAbundance("H-1", 0.6); b.setMolarAbundance("H-1", 0.6); + + a.registerSymbol("He-4"); b.registerSymbol("He-4"); + ASSERT_EQ(fourdst::composition::utils::CompositionHash::hash_exact(a), + fourdst::composition::utils::CompositionHash::hash_exact(b)); +} + +TEST_F(compositionTest, canonicalizeNaNIfAllowed) { + fourdst::composition::Composition a, b; + a.registerSymbol("H-1"); b.registerSymbol("H-1"); + double qnan1 = std::numeric_limits::quiet_NaN(); + double qnan2 = std::bit_cast(std::uint64_t{0x7ff80000'00000042ULL}); + a.setMolarAbundance("H-1", qnan1); + b.setMolarAbundance("H-1", qnan2); + ASSERT_EQ(fourdst::composition::utils::CompositionHash::hash_exact(a), + fourdst::composition::utils::CompositionHash::hash_exact(b)); }