#ifndef STRINGZILLAS_SIMILARITIES_SERIAL_HPP_
#define STRINGZILLAS_SIMILARITIES_SERIAL_HPP_
#include "stringzilla/types.hpp"
#include "stringzilla/memory.h"
#include "stringzilla/utf8_runes/serial.h"
#include "stringzillas/types.hpp"
#include <atomic>
#include <type_traits>
#include <limits>
#include <iterator>
namespace ashvardanian {
namespace stringzillas {
struct error_costs_32x32_t;
constexpr sz_capability_t serialize_capability(sz_capability_t capability) noexcept {
sz_capability_t without_parallel = static_cast<sz_capability_t>(capability & ~sz_cap_parallel_k);
sz_capability_t without_serial = static_cast<sz_capability_t>(without_parallel & ~sz_cap_serial_k);
return without_serial != 0 ? without_serial : without_parallel;
}
template <sz_similarity_objective_t objective_, typename score_type_>
constexpr score_type_ min_or_max(score_type_ a, score_type_ b) noexcept {
if constexpr (objective_ == sz_minimize_distance_k) { return sz_min_of_two(a, b); }
else { return sz_max_of_two(a, b); }
}
template <typename value_type_>
constexpr void rotate_three(value_type_ &a, value_type_ &b, value_type_ &c) noexcept {
value_type_ tmp = a;
a = b;
b = c;
c = tmp;
}
constexpr error_cost_magnitude_t error_cost_abs(error_cost_t x) noexcept {
return static_cast<error_cost_magnitude_t>(x < 0 ? -(i16_t)x : (i16_t)x);
}
struct linear_gap_costs_t {
error_cost_t open_or_extend = 1;
constexpr error_cost_magnitude_t magnitude() const noexcept { return error_cost_abs(open_or_extend); }
};
struct affine_gap_costs_t {
error_cost_t open = 1;
error_cost_t extend = 1;
constexpr error_cost_magnitude_t magnitude() const noexcept {
return std::max(error_cost_abs(open), error_cost_abs(extend));
}
};
template <typename gap_costs_type_>
constexpr sz_similarity_gaps_t gap_type() {
constexpr bool is_linear_k = is_same_type<gap_costs_type_, linear_gap_costs_t>::value;
constexpr bool is_affine_k = is_same_type<gap_costs_type_, affine_gap_costs_t>::value;
static_assert(is_linear_k || is_affine_k, "Invalid gap costs type");
if constexpr (is_linear_k) { return sz_gaps_linear_k; }
else { return sz_gaps_affine_k; }
}
struct uniform_substitution_costs_t {
error_cost_t match = 0;
error_cost_t mismatch = 1;
constexpr error_cost_t operator()(char a, char b) const noexcept { return a == b ? match : mismatch; }
constexpr error_cost_t operator()(rune_t a, rune_t b) const noexcept { return a == b ? match : mismatch; }
constexpr error_cost_magnitude_t magnitude() const noexcept {
return std::max(error_cost_abs(match), error_cost_abs(mismatch));
}
};
static constexpr size_t error_costs_classes_count_k = 32;
struct error_costs_32x32_t {
static constexpr size_t classes_count_k = error_costs_classes_count_k;
u8_t byte_to_class[256] = {0};
error_cost_t class_substitution_costs[classes_count_k][classes_count_k] = {{0}};
constexpr error_cost_t operator()(char a, char b) const noexcept {
return class_substitution_costs[byte_to_class[(u8_t)a]][byte_to_class[(u8_t)b]];
}
constexpr error_cost_t operator()(u8_t a, u8_t b) const noexcept {
return class_substitution_costs[byte_to_class[a]][byte_to_class[b]];
}
constexpr error_cost_magnitude_t magnitude() const noexcept {
error_cost_magnitude_t max_magnitude = 0;
for (size_t i = 0; i != classes_count_k; ++i)
for (size_t j = 0; j != classes_count_k; ++j) max_magnitude = (std::max)(max_magnitude, error_cost_abs(class_substitution_costs[i][j]));
return max_magnitude;
}
static constexpr error_costs_32x32_t blosum62() noexcept {
constexpr error_cost_t na = -128; constexpr error_cost_t cells[26][26] = {
{4, -2, 0, -2, -1, -2, 0, -2, -1, na, -1, -1, -1, -2, na, -1, -1, -1, 1, 0, na, 0, -3, 0, -2, -1},
{-2, 4, -3, 4, 1, -3, -1, 0, -3, na, 0, -4, -3, 3, na, -2, 0, -1, 0, -1, na, -3, -4, -1, -3, 1},
{0, -3, 9, -3, -4, -2, -3, -3, -1, na, -3, -1, -1, -3, na, -3, -3, -3, -1, -1, na, -1, -2, -2, -2, -3},
{-2, 4, -3, 6, 2, -3, -1, -1, -3, na, -1, -4, -3, 1, na, -1, 0, -2, 0, -1, na, -3, -4, -1, -3, 1},
{-1, 1, -4, 2, 5, -3, -2, 0, -3, na, 1, -3, -2, 0, na, -1, 2, 0, 0, -1, na, -2, -3, -1, -2, 4},
{-2, -3, -2, -3, -3, 6, -3, -1, 0, na, -3, 0, 0, -3, na, -4, -3, -3, -2, -2, na, -1, 1, -1, 3, -3},
{0, -1, -3, -1, -2, -3, 6, -2, -4, na, -2, -4, -3, 0, na, -2, -2, -2, 0, -2, na, -3, -2, -1, -3, -2},
{-2, 0, -3, -1, 0, -1, -2, 8, -3, na, -1, -3, -2, 1, na, -2, 0, 0, -1, -2, na, -3, -2, -1, 2, 0},
{-1, -3, -1, -3, -3, 0, -4, -3, 4, na, -3, 2, 1, -3, na, -3, -3, -3, -2, -1, na, 3, -3, -1, -1, -3},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{-1, 0, -3, -1, 1, -3, -2, -1, -3, na, 5, -2, -1, 0, na, -1, 1, 2, 0, -1, na, -2, -3, -1, -2, 1},
{-1, -4, -1, -4, -3, 0, -4, -3, 2, na, -2, 4, 2, -3, na, -3, -2, -2, -2, -1, na, 1, -2, -1, -1, -3},
{-1, -3, -1, -3, -2, 0, -3, -2, 1, na, -1, 2, 5, -2, na, -2, 0, -1, -1, -1, na, 1, -1, -1, -1, -1},
{-2, 3, -3, 1, 0, -3, 0, 1, -3, na, 0, -3, -2, 6, na, -2, 0, 0, 1, 0, na, -3, -4, -1, -2, 0},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{-1, -2, -3, -1, -1, -4, -2, -2, -3, na, -1, -3, -2, -2, na, 7, -1, -2, -1, -1, na, -2, -4, -2, -3, -1},
{-1, 0, -3, 0, 2, -3, -2, 0, -3, na, 1, -2, 0, 0, na, -1, 5, 1, 0, -1, na, -2, -2, -1, -1, 3},
{-1, -1, -3, -2, 0, -3, -2, 0, -3, na, 2, -2, -1, 0, na, -2, 1, 5, -1, -1, na, -3, -3, -1, -2, 0},
{1, 0, -1, 0, 0, -2, 0, -1, -2, na, 0, -2, -1, 1, na, -1, 0, -1, 4, 1, na, -2, -3, 0, -2, 0},
{0, -1, -1, -1, -1, -2, -2, -2, -1, na, -1, -1, -1, 0, na, -1, -1, -1, 1, 5, na, 0, -2, 0, -2, -1},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{0, -3, -1, -3, -2, -1, -3, -3, 3, na, -2, 1, 1, -3, na, -2, -2, -3, -2, 0, na, 4, -3, -1, -1, -2},
{-3, -4, -2, -4, -3, 1, -2, -2, -3, na, -3, -2, -1, -4, na, -4, -2, -3, -3, -2, na, -3, 11, -2, 2, -3},
{0, -1, -2, -1, -1, -1, -1, -1, -1, na, -1, -1, -1, -1, na, -2, -1, -1, 0, 0, na, -1, -2, -1, -1, -1},
{-2, -3, -2, -3, -2, 3, -3, 2, -1, na, -2, -1, -1, -2, na, -3, -1, -2, -2, -2, na, -1, 2, -1, 7, -2},
{-1, 1, -3, 1, 4, -3, -2, 0, -3, na, 1, -3, -1, 0, na, -1, 3, 0, 0, -1, na, -2, -3, -1, -2, 4}};
return from_ascii_26x26_(cells);
}
static constexpr error_costs_32x32_t nuc44() noexcept {
constexpr error_cost_t na = -128; constexpr error_cost_t cells[26][26] = {
{5, -4, -4, -1, na, na, -4, -1, na, na, -4, na, 1, -2, na, na, na, 1, -4, -4, na, -1, 1, na, -4, na},
{-4, -1, -1, -2, na, na, -1, -2, na, na, -1, na, -3, -1, na, na, na, -3, -1, -1, na, -2, -3, na, -1, na},
{-4, -1, 5, -4, na, na, -4, -1, na, na, -4, na, 1, -2, na, na, na, -4, 1, -4, na, -1, -4, na, 1, na},
{-1, -2, -4, -1, na, na, -1, -2, na, na, -1, na, -3, -1, na, na, na, -1, -3, -1, na, -2, -1, na, -3, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{-4, -1, -4, -1, na, na, 5, -4, na, na, 1, na, -4, -2, na, na, na, 1, 1, -4, na, -1, -4, na, -4, na},
{-1, -2, -1, -2, na, na, -4, -1, na, na, -3, na, -1, -1, na, na, na, -3, -3, -1, na, -2, -1, na, -1, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{-4, -1, -4, -1, na, na, 1, -3, na, na, -1, na, -4, -1, na, na, na, -2, -2, 1, na, -3, -2, na, -2, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{1, -3, 1, -3, na, na, -4, -1, na, na, -4, na, -1, -1, na, na, na, -2, -2, -4, na, -1, -2, na, -2, na},
{-2, -1, -2, -1, na, na, -2, -1, na, na, -1, na, -1, -1, na, na, na, -1, -1, -2, na, -1, -1, na, -1, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{1, -3, -4, -1, na, na, 1, -3, na, na, -2, na, -2, -1, na, na, na, -1, -2, -4, na, -1, -2, na, -4, na},
{-4, -1, 1, -3, na, na, 1, -3, na, na, -2, na, -2, -1, na, na, na, -2, -1, -4, na, -1, -4, na, -2, na},
{-4, -1, -4, -1, na, na, -4, -1, na, na, 1, na, -4, -2, na, na, na, -4, -4, 5, na, -4, 1, na, 1, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{-1, -2, -1, -2, na, na, -1, -2, na, na, -3, na, -1, -1, na, na, na, -1, -1, -4, na, -1, -3, na, -3, na},
{1, -3, -4, -1, na, na, -4, -1, na, na, -2, na, -2, -1, na, na, na, -2, -4, 1, na, -3, -1, na, -2, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na},
{-4, -1, 1, -3, na, na, -4, -1, na, na, -2, na, -2, -1, na, na, na, -4, -2, 1, na, -3, -2, na, -1, na},
{na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na}};
return from_ascii_26x26_(cells);
}
private:
static constexpr error_costs_32x32_t from_ascii_26x26_(error_cost_t const (&cells)[26][26]) noexcept {
constexpr error_cost_t na = -128; error_costs_32x32_t result;
u8_t residue_to_class[26] = {0};
u8_t next_class = 1;
for (size_t i = 0; i != 26u; ++i)
if (cells[i][i] != na) {
residue_to_class[i] = next_class;
result.byte_to_class[i + 65u] = next_class;
++next_class;
}
for (size_t i = 0; i != 26u; ++i)
for (size_t j = 0; j != 26u; ++j)
if (residue_to_class[i] != 0 && residue_to_class[j] != 0)
result.class_substitution_costs[residue_to_class[i]][residue_to_class[j]] = cells[i][j];
return result;
}
};
#pragma region Algorithm Building Blocks
template <typename score_type_>
struct diagonal_memory_requirements {
using score_t = score_type_;
static constexpr bool is_signed_k = std::is_signed_v<score_t>;
size_t max_diagonal_length = 0;
bytes_per_cell_t bytes_per_cell = zero_bytes_per_cell_k;
size_t bytes_per_diagonal = 0;
size_t bytes_for_diagonals = 0; size_t total = 0;
constexpr diagonal_memory_requirements( size_t first_length, size_t second_length, sz_similarity_gaps_t gap_type, error_cost_magnitude_t substitute_magnitude, error_cost_magnitude_t gap_magnitude, size_t bytes_per_char, size_t register_width, bytes_per_cell_t min_bytes_per_cell = one_byte_per_cell_k) noexcept {
size_t shorter_length = sz_min_of_two(first_length, second_length);
size_t longer_length = sz_max_of_two(first_length, second_length);
error_cost_magnitude_t magnitude = sz_max_of_two(substitute_magnitude, gap_magnitude);
size_t max_cell_value = (longer_length + 1) * magnitude;
if constexpr (!is_signed_k)
this->bytes_per_cell = max_cell_value < 256 ? one_byte_per_cell_k
: max_cell_value < 65536 ? two_bytes_per_cell_k
: max_cell_value < 4294967296 ? four_bytes_per_cell_k
: eight_bytes_per_cell_k;
else
this->bytes_per_cell = max_cell_value < 127 ? one_byte_per_cell_k
: max_cell_value < 32767 ? two_bytes_per_cell_k
: max_cell_value < 2147483647 ? four_bytes_per_cell_k
: eight_bytes_per_cell_k;
if (this->bytes_per_cell < min_bytes_per_cell) this->bytes_per_cell = min_bytes_per_cell;
if (shorter_length == 0) {
this->max_diagonal_length = 0;
this->bytes_per_diagonal = 0;
this->bytes_for_diagonals = 0;
this->total = 0;
return;
}
this->max_diagonal_length = shorter_length + 1;
this->bytes_per_diagonal = round_up_to_multiple<size_t>(max_diagonal_length * bytes_per_cell, register_width);
size_t diagonals_count = gap_type == sz_gaps_linear_k ? 3 : 7;
size_t first_length_bytes = round_up_to_multiple<size_t>(first_length * bytes_per_char, register_width);
size_t second_length_bytes = round_up_to_multiple<size_t>(second_length * bytes_per_char, register_width);
size_t const widened_read_overhang = register_width > bytes_per_cell ? register_width - bytes_per_cell : 0;
this->bytes_for_diagonals = diagonals_count * bytes_per_diagonal + widened_read_overhang;
this->total = this->bytes_for_diagonals + first_length_bytes + second_length_bytes;
}
};
using scratch_space_t = span<std::byte>;
struct scratch_amount_t {
size_t alignment = std::numeric_limits<size_t>::max();
size_t total = 0;
constexpr operator size_t() const noexcept { return total; }
constexpr scratch_amount_t &operator+=(size_t bytes) noexcept {
total += round_up_to_multiple<size_t>(bytes, alignment);
return *this;
}
};
template <size_t current_k, size_t high_k, typename fixed_type_, typename overflow_type_>
status_t dispatch_word_bucket_(size_t bucket, fixed_type_ &&fixed, overflow_type_ &&overflow) noexcept {
if constexpr (current_k > high_k) return sz_unused_(bucket), overflow();
else if (bucket == current_k) return fixed(std::integral_constant<size_t, current_k> {});
else return dispatch_word_bucket_<current_k + 1, high_k>(bucket, fixed, overflow);
}
#pragma region Core Templates
#if SZ_HAS_CONCEPTS_
template <typename iterator_type_>
concept pointer_like = requires(iterator_type_ iterator, size_t idx) {
{ ++iterator } -> std::same_as<iterator_type_ &>; { *iterator }; { iterator[idx] }; };
template <typename value_type_>
concept score_like = std::integral<value_type_> && std::is_trivial_v<value_type_>;
template <typename substituter_type_>
concept substituter_like = requires(substituter_type_ costs) {
{ costs.magnitude() } -> std::convertible_to<error_cost_magnitude_t>; { costs.operator()(char(), char()) } -> std::convertible_to<error_cost_t>; };
template <typename gap_costs_type_>
concept gap_costs_like = requires(gap_costs_type_ costs) {
{ costs.magnitude() } -> std::convertible_to<error_cost_magnitude_t>; };
#endif
template < typename first_iterator_type_ = char const *, typename second_iterator_type_ = char const *, typename score_type_ = size_t, typename substituter_type_ = uniform_substitution_costs_t, typename gap_costs_type_ = linear_gap_costs_t, sz_similarity_objective_t objective_ = sz_maximize_score_k, sz_similarity_locality_t locality_ = sz_similarity_global_k, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires pointer_like<first_iterator_type_> && pointer_like<second_iterator_type_> && score_like<score_type_> &&
substituter_like<substituter_type_> && gap_costs_like<gap_costs_type_>
#endif
struct tile_scorer;
/**
* @brief Alignment Score and Edit Distance algorithm evaluating the Dynamic Programming matrix
* @b (anti)diagonal-by-(anti)diagonal on a CPU.
*
* Can be used for both global and local alignment, like Needleman-Wunsch and Smith-Waterman.
* Can be used for both linear and affine gap penalties.
*
* ? There are smarter algorithms for computing the Levenshtein distance, mostly based on bit-level operations.
* ? Those, however, don't generalize well to arbitrary length inputs or non-uniform substitution costs.
* ? This algorithm provides a more flexible baseline implementation for future SIMD and GPGPU optimizations.
*
* @tparam char_or_rune_type_ The type of the characters in the strings, generally `char` or @b `rune_t` for UTF-8.
* @tparam score_type_ The smallest type that can hold the distance, ideally `i8_t` or `u8_t`.
* @tparam substituter_type_ A callable type that takes two characters and returns the substitution cost.
* @tparam gap_costs_type_ Whether to use linear or affine gap penalties.
* @tparam objective_ Whether to minimize the distance or maximize the score.
* @tparam locality_ Whether to use the global alignment algorithm or the local one.
* @tparam capability_ Whether to use @b multi-threading or some form of @b SIMD vectorization, or both.
* @tparam enable_ Used to enable/disable the specialization.
*/
template < //
typename char_or_rune_type_ = char, //
typename score_type_ = size_t, //
typename substituter_type_ = uniform_substitution_costs_t, //
typename gap_costs_type_ = linear_gap_costs_t, //
sz_similarity_objective_t objective_ = sz_maximize_score_k, //
sz_similarity_locality_t locality_ = sz_similarity_global_k, //
sz_capability_t capability_ = sz_cap_serial_k, //
typename enable_ = void //
>
#if SZ_HAS_CONCEPTS_
requires score_like<score_type_> && substituter_like<substituter_type_> && gap_costs_like<gap_costs_type_>
#endif
struct diagonal_walker;
/**
* @brief Alignment Score and Edit Distance algorithm evaluating the Dynamic Programming matrix
* @b row-by-row on a CPU, using the conventional Wagner-Fischer algorithm.
*
* Can be used for both global and local alignment, like Needleman-Wunsch and Smith-Waterman.
* Can be used for both linear and affine gap penalties.
*
* @tparam char_or_rune_type_ The type of the characters in the strings, generally `char` or @b `rune_t` for UTF-8.
* @tparam score_type_ The smallest type that can hold the distance, ideally `i8_t` or `u8_t`.
* @tparam substituter_type_ A callable type that takes two characters and returns the substitution cost.
* @tparam gap_costs_type_ Whether to use linear or affine gap penalties.
* @tparam allocator_type_ A default-constructible allocator type for the internal buffers.
* @tparam objective_ Whether to minimize the distance or maximize the score.
* @tparam locality_ Whether to use the global alignment algorithm or the local one.
* @tparam capability_ Whether to use @b multi-threading or some form of @b SIMD vectorization, or both.
* @tparam enable_ Used to enable/disable the specialization.
*
* @note The API of this algorithm is a bit weird, but it's designed to minimize the reliance on the definitions
* in the `stringzilla.hpp` header, making compilation times shorter for the end-user.
* @sa For lower-level API, check `szs_levenshtein_distance[_utf8]` and `szs_needleman_wunsch_score`.
* @sa For simplicity, use the `sz::levenshtein_distance[_utf8]` and `sz::needleman_wunsch_score`.
* @sa For bulk API, use `sz::levenshtein_distances[_utf8]`.
template < typename char_or_rune_type_ = char, typename score_type_ = size_t, typename substituter_type_ = uniform_substitution_costs_t, typename gap_costs_type_ = linear_gap_costs_t, sz_similarity_objective_t objective_ = sz_maximize_score_k, sz_similarity_locality_t locality_ = sz_similarity_global_k, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires score_like<score_type_> && substituter_like<substituter_type_> && gap_costs_like<gap_costs_type_>
#endif
struct horizontal_walker;
/**
* @brief @b Inter-sequence walker: scores a block of candidates against @b one shared query, one candidate per
* SIMD lane, advancing the Dynamic Programming matrix @b row-by-row.
*
* This is the cross-product workhorse. Unlike `diagonal_walker` (which vectorizes the anti-diagonal of a single
* pair and therefore starves its lanes for short strings), here every lane carries an independent candidate, so
* there is no intra-pair left-dependency to break — a plain row walk keeps all lanes busy regardless of length.
* The `sz_cap_serial_k` specialization below is the scalar per-lane @b reference oracle that every SIMD/GPU
* candidate-lane kernel is validated against.
*
* @tparam candidate_lanes_ Number of candidates packed side-by-side (64 for 8-bit cells, 32 for 16-bit).
* @sa `candidate_lanes_block`, `sz_packing_candidates_across_lanes_k`.
template < typename char_or_rune_type_ = char, typename score_type_ = size_t, typename substituter_type_ = uniform_substitution_costs_t, typename gap_costs_type_ = linear_gap_costs_t, sz_similarity_objective_t objective_ = sz_minimize_distance_k, sz_similarity_locality_t locality_ = sz_similarity_global_k, sz_capability_t capability_ = sz_cap_serial_k, size_t candidate_lanes_ = 64, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires score_like<score_type_> && substituter_like<substituter_type_> && gap_costs_like<gap_costs_type_>
#endif
struct candidate_lane_walker;
/**
* @brief Serial reference: scalar per-lane row Dynamic Programming. Differential oracle for the SIMD/GPU
* candidate-lane kernels. Covers @b global alignment with @b linear gaps (Levenshtein when the substituter
* is uniform, Needleman-Wunsch when it is a class-cost matrix); local alignment is a separate specialization.
template <typename char_or_rune_type_, typename score_type_, typename substituter_type_,
sz_similarity_objective_t objective_, size_t candidate_lanes_>
struct candidate_lane_walker<char_or_rune_type_, score_type_, substituter_type_, linear_gap_costs_t, objective_,
sz_similarity_global_k, sz_cap_serial_k, candidate_lanes_, void> {
using char_t = char_or_rune_type_;
using score_t = score_type_;
using substituter_t = substituter_type_;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_serial_k;
static constexpr size_t candidate_lanes_k = candidate_lanes_;
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, linear_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const row_bytes = sizeof(score_t) * (longest_candidate + 1);
scratch_amount_t amount {specs.cache_line_width};
amount += row_bytes; amount += row_bytes; return amount;
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
error_cost_t const gap = gap_costs_.open_or_extend;
size_t const query_length = query.size();
size_t const row_cells = candidates.longest_candidate + 1;
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_cells;
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
for (size_t column = 0; column <= candidate_length; ++column)
previous_row[column] = static_cast<score_t>(gap * column);
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
current_row[0] = static_cast<score_t>(gap * query_position);
char_t const query_char = query[query_position - 1];
for (size_t column = 1; column <= candidate_length; ++column) {
char_t const candidate_char = candidates.character_of_lane(lane_index, column - 1);
error_cost_t const cost_of_substitution = substituter_(query_char, candidate_char);
score_t const if_substitution = previous_row[column - 1] + cost_of_substitution;
score_t const if_gap = min_or_max<objective_k>(previous_row[column], current_row[column - 1]) + gap;
current_row[column] = min_or_max<objective_k>(if_substitution, if_gap);
}
trivial_swap(previous_row, current_row);
}
result_lanes[lane_index] = previous_row[candidate_length];
}
return status_t::success_k;
}
};
template < typename char_or_rune_type_ = char, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
struct levenshtein_distance_myers;
template < typename gap_costs_type_ = linear_gap_costs_t, typename allocator_type_ = dummy_alloc_t, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires gap_costs_like<gap_costs_type_>
#endif
struct levenshtein_distances;
template < typename gap_costs_type_ = linear_gap_costs_t, typename allocator_type_ = dummy_alloc_t, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires gap_costs_like<gap_costs_type_>
#endif
struct levenshtein_distances_utf8;
template < typename substituter_type_ = error_costs_32x32_t, typename gap_costs_type_ = linear_gap_costs_t, typename allocator_type_ = dummy_alloc_t, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires substituter_like<substituter_type_> && gap_costs_like<gap_costs_type_>
#endif
struct needleman_wunsch_scores;
template < typename substituter_type_ = error_costs_32x32_t, typename gap_costs_type_ = linear_gap_costs_t, typename allocator_type_ = dummy_alloc_t, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires substituter_like<substituter_type_> && gap_costs_like<gap_costs_type_>
#endif
struct smith_waterman_scores;
#pragma endregion Core Templates
#pragma region Common Aliases
using malloc_t = std::allocator<char>;
/**
* In non-SIMD backends we still leverage multi-threading for parallelism.
* "Affine Levenshtein" combination is rarely used in practice, so that one only has a serial fallback.
*/
using levenshtein_serial_t = levenshtein_distances<linear_gap_costs_t, malloc_t, sz_cap_serial_k>;
using levenshtein_utf8_serial_t = levenshtein_distances_utf8<linear_gap_costs_t, malloc_t, sz_cap_serial_k>;
using needleman_wunsch_serial_t =
needleman_wunsch_scores<error_costs_32x32_t, linear_gap_costs_t, malloc_t, sz_cap_serial_k>;
using smith_waterman_serial_t =
smith_waterman_scores<error_costs_32x32_t, linear_gap_costs_t, malloc_t, sz_cap_serial_k>;
using affine_levenshtein_serial_t = levenshtein_distances<affine_gap_costs_t, malloc_t, sz_cap_serial_k>;
using affine_levenshtein_utf8_serial_t = levenshtein_distances_utf8<affine_gap_costs_t, malloc_t, sz_cap_serial_k>;
using affine_needleman_wunsch_serial_t =
needleman_wunsch_scores<error_costs_32x32_t, affine_gap_costs_t, malloc_t, sz_cap_serial_k>;
using affine_smith_waterman_serial_t =
smith_waterman_scores<error_costs_32x32_t, affine_gap_costs_t, malloc_t, sz_cap_serial_k>;
/**
* In @b AVX-512:
* - for Global Alignments, we can vectorize the min-max calculation for diagonal "walkers"
* - for Local Alignments, we can vectorize the character substitution lookups for horizontal "walkers"
*/
using levenshtein_icelake_t = levenshtein_distances<linear_gap_costs_t, malloc_t, sz_caps_sil_k>;
using levenshtein_utf8_icelake_t = levenshtein_distances_utf8<linear_gap_costs_t, malloc_t, sz_caps_sil_k>;
using needleman_wunsch_icelake_t =
needleman_wunsch_scores<error_costs_32x32_t, linear_gap_costs_t, malloc_t, sz_caps_sil_k>;
using smith_waterman_icelake_t =
smith_waterman_scores<error_costs_32x32_t, linear_gap_costs_t, malloc_t, sz_caps_sil_k>;
using affine_levenshtein_icelake_t = levenshtein_distances<affine_gap_costs_t, malloc_t, sz_caps_sil_k>;
using affine_needleman_wunsch_icelake_t =
needleman_wunsch_scores<error_costs_32x32_t, affine_gap_costs_t, malloc_t, sz_caps_sil_k>;
using affine_smith_waterman_icelake_t =
smith_waterman_scores<error_costs_32x32_t, affine_gap_costs_t, malloc_t, sz_caps_sil_k>;
/**
* In @b AVX2 (Haswell) we vectorize the per-character substitution lookups for horizontal "walkers",
* emulating the Ice Lake `VPERMB` class lookup with high-nibble-selected `VPSHUFB` blends. The aliases are
* always declared (the composite capability is a plain constant); only their instantiation is `SZ_USE_HASWELL`-gated.
using levenshtein_haswell_t = levenshtein_distances<linear_gap_costs_t, malloc_t, sz_caps_sh_k>;
using levenshtein_utf8_haswell_t = levenshtein_distances_utf8<linear_gap_costs_t, malloc_t, sz_caps_sh_k>;
using needleman_wunsch_haswell_t =
needleman_wunsch_scores<error_costs_32x32_t, linear_gap_costs_t, malloc_t, sz_caps_sh_k>;
using smith_waterman_haswell_t = smith_waterman_scores<error_costs_32x32_t, linear_gap_costs_t, malloc_t, sz_caps_sh_k>;
using affine_levenshtein_haswell_t = levenshtein_distances<affine_gap_costs_t, malloc_t, sz_caps_sh_k>;
using affine_needleman_wunsch_haswell_t =
needleman_wunsch_scores<error_costs_32x32_t, affine_gap_costs_t, malloc_t, sz_caps_sh_k>;
using affine_smith_waterman_haswell_t =
smith_waterman_scores<error_costs_32x32_t, affine_gap_costs_t, malloc_t, sz_caps_sh_k>;
using levenshtein_neon_t = levenshtein_distances<linear_gap_costs_t, malloc_t, sz_caps_sn_k>;
using levenshtein_utf8_neon_t = levenshtein_distances_utf8<linear_gap_costs_t, malloc_t, sz_caps_sn_k>;
using needleman_wunsch_neon_t =
needleman_wunsch_scores<error_costs_32x32_t, linear_gap_costs_t, malloc_t, sz_caps_sn_k>;
using smith_waterman_neon_t = smith_waterman_scores<error_costs_32x32_t, linear_gap_costs_t, malloc_t, sz_caps_sn_k>;
using affine_levenshtein_neon_t = levenshtein_distances<affine_gap_costs_t, malloc_t, sz_caps_sn_k>;
using affine_needleman_wunsch_neon_t =
needleman_wunsch_scores<error_costs_32x32_t, affine_gap_costs_t, malloc_t, sz_caps_sn_k>;
using affine_smith_waterman_neon_t =
smith_waterman_scores<error_costs_32x32_t, affine_gap_costs_t, malloc_t, sz_caps_sn_k>;
using levenshtein_rvv_t = levenshtein_distances<linear_gap_costs_t, malloc_t, sz_caps_sr_k>;
using levenshtein_utf8_rvv_t = levenshtein_distances_utf8<linear_gap_costs_t, malloc_t, sz_caps_sr_k>;
using needleman_wunsch_rvv_t = needleman_wunsch_scores<error_costs_32x32_t, linear_gap_costs_t, malloc_t, sz_caps_sr_k>;
using smith_waterman_rvv_t = smith_waterman_scores<error_costs_32x32_t, linear_gap_costs_t, malloc_t, sz_caps_sr_k>;
using affine_levenshtein_rvv_t = levenshtein_distances<affine_gap_costs_t, malloc_t, sz_caps_sr_k>;
using affine_needleman_wunsch_rvv_t =
needleman_wunsch_scores<error_costs_32x32_t, affine_gap_costs_t, malloc_t, sz_caps_sr_k>;
using affine_smith_waterman_rvv_t =
smith_waterman_scores<error_costs_32x32_t, affine_gap_costs_t, malloc_t, sz_caps_sr_k>;
#pragma endregion Common Aliases
#pragma region Autovectorized Tile Scorer
template <typename first_iterator_type_, typename second_iterator_type_, typename score_type_,
typename substituter_type_, sz_similarity_objective_t objective_>
#if SZ_HAS_CONCEPTS_
requires pointer_like<first_iterator_type_> && pointer_like<second_iterator_type_> && score_like<score_type_> &&
substituter_like<substituter_type_>
#endif
struct tile_scorer<first_iterator_type_, second_iterator_type_, score_type_, substituter_type_, linear_gap_costs_t,
objective_, sz_similarity_global_k, sz_cap_serial_k, void> {
using first_iterator_t = first_iterator_type_;
using second_iterator_t = second_iterator_type_;
using score_t = score_type_;
using substituter_t = substituter_type_;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_serial_k;
using first_char_t = typename std::iterator_traits<first_iterator_t>::value_type;
using second_char_t = typename std::iterator_traits<second_iterator_t>::value_type;
static_assert(is_same_type<first_char_t, second_char_t>::value, "String characters must be of the same type.");
using char_t = remove_cvref<first_char_t>;
using tile_scorer_t = tile_scorer<first_iterator_t, second_iterator_t, score_t, substituter_t, gap_costs_t,
objective_k, locality_k, capability_k>;
protected:
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
score_t last_score_ {0};
bool transpose_ {false};
public:
tile_scorer() = default;
tile_scorer(substituter_t subs, linear_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
void prepare(bool transpose) noexcept { transpose_ = transpose; }
void init_score(score_t &cell, size_t diagonal_index) const noexcept {
cell = gap_costs_.open_or_extend * diagonal_index;
}
score_t score() const noexcept { return last_score_; }
SZ_NOINLINE void score_slice_trampoline_( first_iterator_t first_reversed_slice, second_iterator_t second_slice, score_t const *scores_pre_substitution, score_t const *scores_pre_insertion, score_t const *scores_pre_deletion, score_t *scores_new, size_t from, size_t to) noexcept {
error_cost_t const gap_cost = gap_costs_.open_or_extend;
for (size_t i = from; i < to; ++i) {
score_t pre_substitution = scores_pre_substitution[i];
score_t pre_insertion = scores_pre_insertion[i];
score_t pre_deletion = scores_pre_deletion[i];
error_cost_t cost_of_substitution = transpose_ ? substituter_(second_slice[i], first_reversed_slice[i])
: substituter_(first_reversed_slice[i], second_slice[i]);
score_t if_substitution = pre_substitution + cost_of_substitution;
score_t if_deletion_or_insertion = min_or_max<objective_k>(pre_deletion, pre_insertion) + gap_cost;
score_t cell_score = min_or_max<objective_k>(if_deletion_or_insertion, if_substitution);
scores_new[i] = cell_score;
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( first_iterator_t first_reversed_slice, second_iterator_t second_slice, size_t n, score_t const *scores_pre_substitution, score_t const *scores_pre_insertion, score_t const *scores_pre_deletion,
score_t *scores_new, executor_type_ &&executor = {}) noexcept {
executor.for_slices(n, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion,
scores_pre_deletion, scores_new, from, to);
});
last_score_ = scores_new[n - 1];
}
};
template <typename first_iterator_type_, typename second_iterator_type_, typename score_type_,
typename substituter_type_, sz_similarity_objective_t objective_>
#if SZ_HAS_CONCEPTS_
requires pointer_like<first_iterator_type_> && pointer_like<second_iterator_type_> && score_like<score_type_> &&
substituter_like<substituter_type_>
#endif
struct tile_scorer<first_iterator_type_, second_iterator_type_, score_type_, substituter_type_, linear_gap_costs_t,
objective_, sz_similarity_local_k, sz_cap_serial_k, void> {
using first_iterator_t = first_iterator_type_;
using second_iterator_t = second_iterator_type_;
using score_t = score_type_;
using substituter_t = substituter_type_;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_local_k;
static constexpr sz_capability_t capability_k = sz_cap_serial_k;
using first_char_t = typename std::iterator_traits<first_iterator_t>::value_type;
using second_char_t = typename std::iterator_traits<second_iterator_t>::value_type;
static_assert(is_same_type<first_char_t, second_char_t>::value, "String characters must be of the same type.");
using char_t = first_char_t;
using tile_scorer_t = tile_scorer<first_iterator_t, second_iterator_t, score_t, substituter_t, gap_costs_t,
objective_k, locality_k, capability_k>;
protected:
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
score_t best_score_ {0};
bool transpose_ {false};
public:
tile_scorer() = default;
tile_scorer(substituter_t subs, linear_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
void prepare(bool transpose) noexcept { transpose_ = transpose; }
void init_score(score_t &cell, size_t ) const noexcept { cell = 0; }
score_t score() const noexcept { return best_score_; }
SZ_NOINLINE score_t score_slice_trampoline_( first_iterator_t first_reversed_slice, second_iterator_t second_slice, score_t const *scores_pre_substitution, score_t const *scores_pre_insertion, score_t const *scores_pre_deletion, score_t *scores_new, size_t from, size_t to,
score_t running_best) noexcept {
error_cost_t const gap_cost = gap_costs_.open_or_extend;
for (size_t i = from; i < to; ++i) {
score_t pre_substitution = scores_pre_substitution[i];
score_t pre_insertion = scores_pre_insertion[i];
score_t pre_deletion = scores_pre_deletion[i];
error_cost_t cost_of_substitution = transpose_ ? substituter_(second_slice[i], first_reversed_slice[i])
: substituter_(first_reversed_slice[i], second_slice[i]);
score_t if_substitution = pre_substitution + cost_of_substitution;
score_t if_deletion_or_insertion = min_or_max<objective_k>(pre_deletion, pre_insertion) + gap_cost;
score_t if_substitution_or_reset = min_or_max<objective_k, score_t>(if_substitution, 0);
score_t cell_score = min_or_max<objective_k>(if_deletion_or_insertion, if_substitution_or_reset);
scores_new[i] = cell_score;
running_best = min_or_max<objective_k>(running_best, cell_score);
}
return running_best;
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( first_iterator_t first_reversed_slice, second_iterator_t second_slice, size_t const n, score_t const *scores_pre_substitution, score_t const *scores_pre_insertion, score_t const *scores_pre_deletion,
score_t *scores_new, executor_type_ &&executor = {}) noexcept {
std::atomic<score_t> atomic_best_score {best_score_};
executor.for_slices(n, [&](size_t i_start, size_t i_end) noexcept {
score_t local_best_score = score_slice_trampoline_(
first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion,
scores_new, i_start, i_end, atomic_best_score);
atomic_best_score = min_or_max<objective_k, score_t>(atomic_best_score, local_best_score);
});
best_score_ = min_or_max<objective_k, score_t>(best_score_, atomic_best_score);
}
};
template <typename first_iterator_type_, typename second_iterator_type_, typename score_type_,
typename substituter_type_, sz_similarity_objective_t objective_>
#if SZ_HAS_CONCEPTS_
requires pointer_like<first_iterator_type_> && pointer_like<second_iterator_type_> && score_like<score_type_> &&
substituter_like<substituter_type_>
#endif
struct tile_scorer<first_iterator_type_, second_iterator_type_, score_type_, substituter_type_, affine_gap_costs_t,
objective_, sz_similarity_global_k, sz_cap_serial_k, void> {
using first_iterator_t = first_iterator_type_;
using second_iterator_t = second_iterator_type_;
using score_t = score_type_;
using substituter_t = substituter_type_;
using gap_costs_t = affine_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_serial_k;
using first_char_t = typename std::iterator_traits<first_iterator_t>::value_type;
using second_char_t = typename std::iterator_traits<second_iterator_t>::value_type;
static_assert(is_same_type<first_char_t, second_char_t>::value, "String characters must be of the same type.");
using char_t = remove_cvref<first_char_t>;
using tile_scorer_t = tile_scorer<first_iterator_t, second_iterator_t, score_t, substituter_t, gap_costs_t,
objective_k, locality_k, capability_k>;
protected:
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
score_t last_score_ {0};
bool transpose_ {false};
public:
tile_scorer() = default;
tile_scorer(substituter_t subs, affine_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
void prepare(bool transpose) noexcept { transpose_ = transpose; }
void init_score(score_t &cell, size_t diagonal_index) const noexcept {
cell = diagonal_index ? gap_costs_.open + gap_costs_.extend * (diagonal_index - 1) : 0;
}
void init_gap(score_t &cell, size_t diagonal_index) const noexcept {
cell = (gap_costs_.open + gap_costs_.extend) +
(diagonal_index ? gap_costs_.open + gap_costs_.extend * (diagonal_index - 1) : 0);
}
score_t score() const noexcept { return last_score_; }
SZ_NOINLINE void score_slice_trampoline_( first_iterator_t first_reversed_slice, second_iterator_t second_slice, score_t const *scores_pre_substitution, score_t const *scores_pre_insertion, score_t const *scores_pre_deletion, score_t const *scores_running_insertions, score_t const *scores_running_deletions, score_t *scores_new, score_t *scores_new_insertions, score_t *scores_new_deletions, size_t from, size_t to) noexcept {
for (size_t i = from; i < to; ++i) {
score_t pre_substitution = scores_pre_substitution[i];
score_t pre_insertion_opening = scores_pre_insertion[i];
score_t pre_deletion_opening = scores_pre_deletion[i];
score_t pre_insertion_expansion = scores_running_insertions[i];
score_t pre_deletion_expansion = scores_running_deletions[i];
error_cost_t cost_of_substitution = transpose_ ? substituter_(second_slice[i], first_reversed_slice[i])
: substituter_(first_reversed_slice[i], second_slice[i]);
score_t if_substitution = pre_substitution + cost_of_substitution;
score_t if_insertion = min_or_max<objective_k>(pre_insertion_opening + gap_costs_.open,
pre_insertion_expansion + gap_costs_.extend);
score_t if_deletion = min_or_max<objective_k>(pre_deletion_opening + gap_costs_.open,
pre_deletion_expansion + gap_costs_.extend);
score_t if_deletion_or_insertion = min_or_max<objective_k>(if_deletion, if_insertion);
score_t cell_score = min_or_max<objective_k>(if_deletion_or_insertion, if_substitution);
scores_new[i] = cell_score;
scores_new_insertions[i] = if_insertion;
scores_new_deletions[i] = if_deletion;
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( first_iterator_t first_reversed_slice, second_iterator_t second_slice, size_t n, score_t const *scores_pre_substitution, score_t const *scores_pre_insertion, score_t const *scores_pre_deletion, score_t const *scores_running_insertions, score_t const *scores_running_deletions, score_t *scores_new, score_t *scores_new_insertions, score_t *scores_new_deletions, executor_type_ &&executor = {}) noexcept {
executor.for_slices(n, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion,
scores_pre_deletion, scores_running_insertions, scores_running_deletions,
scores_new, scores_new_insertions, scores_new_deletions, from, to);
});
last_score_ = scores_new[n - 1];
}
};
template <typename first_iterator_type_, typename second_iterator_type_, typename score_type_,
typename substituter_type_, sz_similarity_objective_t objective_>
#if SZ_HAS_CONCEPTS_
requires pointer_like<first_iterator_type_> && pointer_like<second_iterator_type_> && score_like<score_type_> &&
substituter_like<substituter_type_>
#endif
struct tile_scorer<first_iterator_type_, second_iterator_type_, score_type_, substituter_type_, affine_gap_costs_t,
objective_, sz_similarity_local_k, sz_cap_serial_k, void> {
using first_iterator_t = first_iterator_type_;
using second_iterator_t = second_iterator_type_;
using score_t = score_type_;
using substituter_t = substituter_type_;
using gap_costs_t = affine_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_local_k;
static constexpr sz_capability_t capability_k = sz_cap_serial_k;
using first_char_t = typename std::iterator_traits<first_iterator_t>::value_type;
using second_char_t = typename std::iterator_traits<second_iterator_t>::value_type;
static_assert(is_same_type<first_char_t, second_char_t>::value, "String characters must be of the same type.");
using char_t = first_char_t;
using tile_scorer_t = tile_scorer<first_iterator_t, second_iterator_t, score_t, substituter_t, gap_costs_t,
objective_k, locality_k, capability_k>;
protected:
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
score_t best_score_ {0};
bool transpose_ {false};
public:
tile_scorer() = default;
tile_scorer(substituter_t subs, affine_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
void prepare(bool transpose) noexcept { transpose_ = transpose; }
void init_score(score_t &cell, size_t ) const noexcept { cell = 0; }
void init_gap(score_t &cell, size_t ) const noexcept {
cell = gap_costs_.open + gap_costs_.extend;
}
score_t score() const noexcept { return best_score_; }
SZ_NOINLINE score_t score_slice_trampoline_( first_iterator_t first_reversed_slice, second_iterator_t second_slice, score_t const *scores_pre_substitution, score_t const *scores_pre_insertion, score_t const *scores_pre_deletion, score_t const *scores_running_insertions, score_t const *scores_running_deletions, score_t *scores_new, score_t *scores_new_insertions, score_t *scores_new_deletions, size_t from, size_t to, score_t running_best) noexcept {
for (size_t i = from; i < to; ++i) {
score_t pre_substitution = scores_pre_substitution[i];
score_t pre_insertion_opening = scores_pre_insertion[i];
score_t pre_deletion_opening = scores_pre_deletion[i];
score_t pre_insertion_expansion = scores_running_insertions[i];
score_t pre_deletion_expansion = scores_running_deletions[i];
error_cost_t cost_of_substitution = transpose_ ? substituter_(second_slice[i], first_reversed_slice[i])
: substituter_(first_reversed_slice[i], second_slice[i]);
score_t if_substitution = pre_substitution + cost_of_substitution;
score_t if_deletion = min_or_max<objective_k>(pre_deletion_opening + gap_costs_.open,
pre_deletion_expansion + gap_costs_.extend);
score_t if_insertion = min_or_max<objective_k>(pre_insertion_opening + gap_costs_.open,
pre_insertion_expansion + gap_costs_.extend);
score_t if_deletion_or_insertion = min_or_max<objective_k>(if_deletion, if_insertion);
score_t if_substitution_or_reset = min_or_max<objective_k, score_t>(if_substitution, 0);
score_t cell_score = min_or_max<objective_k>(if_deletion_or_insertion, if_substitution_or_reset);
scores_new[i] = cell_score;
scores_new_deletions[i] = if_deletion;
scores_new_insertions[i] = if_insertion;
running_best = min_or_max<objective_k>(running_best, cell_score);
}
return running_best;
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( first_iterator_t first_reversed_slice, second_iterator_t second_slice, size_t const n, score_t const *scores_pre_substitution, score_t const *scores_pre_insertion, score_t const *scores_pre_deletion, score_t const *scores_running_insertions, score_t const *scores_running_deletions, score_t *scores_new, score_t *scores_new_insertions, score_t *scores_new_deletions, executor_type_ &&executor = {}) noexcept {
std::atomic<score_t> atomic_best_score {best_score_};
executor.for_slices(n, [&](size_t i_start, size_t i_end) noexcept {
score_t local_best_score = score_slice_trampoline_(
first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion,
scores_running_insertions, scores_running_deletions, scores_new, scores_new_insertions,
scores_new_deletions, i_start, i_end, atomic_best_score);
atomic_best_score = min_or_max<objective_k, score_t>(atomic_best_score, local_best_score);
});
best_score_ = min_or_max<objective_k, score_t>(best_score_, atomic_best_score);
}
};
#pragma endregion Autovectorized Tile Scorer
#pragma region Diagonal Walker
template <typename char_or_rune_type_, typename score_type_, typename substituter_type_,
sz_similarity_objective_t objective_, sz_similarity_locality_t locality_, sz_capability_t capability_,
typename enable_>
#if SZ_HAS_CONCEPTS_
requires score_like<score_type_> && substituter_like<substituter_type_>
#endif
struct diagonal_walker<char_or_rune_type_, score_type_, substituter_type_, linear_gap_costs_t, objective_, locality_,
capability_, enable_> {
using char_t = char_or_rune_type_;
using score_t = score_type_;
using substituter_t = substituter_type_;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = locality_;
static constexpr sz_capability_t capability_k = capability_;
using tile_scorer_t = tile_scorer<char_t const *, char_t const *, score_t, substituter_t, gap_costs_t, objective_k,
locality_k, capability_k>;
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
diagonal_walker() noexcept {}
diagonal_walker(substituter_t subs, linear_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
struct layout_t {
size_t previous_scores = 0; size_t current_scores = 0; size_t next_scores = 0; size_t shorter_reversed = 0; size_t total = 0; constexpr operator size_t() const noexcept { return total; }
};
layout_t layout(span<char_t const> first, span<char_t const> second, cpu_specs_t const &specs) const noexcept {
size_t const shorter_length = sz_min_of_two(first.size(), second.size());
size_t const diagonal_bytes = sizeof(score_t) * (shorter_length + 1); scratch_amount_t amount {specs.cache_line_width};
layout_t at;
at.previous_scores = amount, amount += diagonal_bytes;
at.current_scores = amount, amount += diagonal_bytes;
at.next_scores = amount, amount += diagonal_bytes;
at.shorter_reversed = amount, amount += shorter_length * sizeof(char_t);
at.total = amount;
return at;
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, score_t &result_ref,
scratch_space_t scratch_space, executor_type_ &&executor,
cpu_specs_t const &specs) const noexcept {
if (first.empty() || second.empty()) {
result_ref = 0;
if constexpr (locality_k == sz_similarity_global_k) {
if (!first.empty() && second.empty()) { result_ref = gap_costs_.open_or_extend * first.size(); }
else if (first.empty() && !second.empty()) { result_ref = gap_costs_.open_or_extend * second.size(); }
}
return status_t::success_k;
}
char_t const *shorter = first.data(), *longer = second.data();
size_t shorter_length = first.size(), longer_length = second.size();
if (shorter_length > longer_length) {
trivial_swap(shorter, longer);
trivial_swap(shorter_length, longer_length);
}
size_t const shorter_dim = shorter_length + 1;
size_t const longer_dim = longer_length + 1;
size_t const diagonals_count = shorter_dim + longer_dim - 1;
size_t const max_diagonal_length = shorter_length + 1;
layout_t const at = layout(first, second, specs);
if (scratch_space.size() < at.total) return status_t::bad_alloc_k;
score_t *previous_scores = (score_t *)(scratch_space.data() + at.previous_scores);
score_t *current_scores = (score_t *)(scratch_space.data() + at.current_scores);
score_t *next_scores = (score_t *)(scratch_space.data() + at.next_scores);
char_t *const shorter_reversed = (char_t *)(scratch_space.data() + at.shorter_reversed);
for (size_t i = 0; i != shorter_length; ++i) shorter_reversed[i] = shorter[shorter_length - 1 - i];
tile_scorer_t scorer {substituter_, gap_costs_};
scorer.prepare(first.size() > second.size());
scorer.init_score(previous_scores[0], 0);
scorer.init_score(current_scores[0], 1);
scorer.init_score(current_scores[1], 1);
size_t next_diagonal_index = 2;
for (; next_diagonal_index < shorter_dim; ++next_diagonal_index) {
size_t const next_diagonal_length = next_diagonal_index + 1;
scorer( shorter_reversed + shorter_length - next_diagonal_index + 1, longer, next_diagonal_length - 2, previous_scores, current_scores, current_scores + 1, next_scores + 1, executor);
scorer.init_score(next_scores[0], next_diagonal_index);
scorer.init_score(next_scores[next_diagonal_length - 1], next_diagonal_index);
rotate_three(previous_scores, current_scores, next_scores);
}
for (; next_diagonal_index < longer_dim; ++next_diagonal_index) {
size_t const next_diagonal_length = shorter_dim;
scorer( shorter_reversed + shorter_length - shorter_dim + 1, longer + next_diagonal_index - shorter_dim, next_diagonal_length - 1, previous_scores, current_scores, current_scores + 1, next_scores, executor);
scorer.init_score(next_scores[next_diagonal_length - 1], next_diagonal_index);
rotate_three(previous_scores, current_scores, next_scores);
sz_move_serial((ptr_t)(previous_scores), (ptr_t)(previous_scores + 1),
(max_diagonal_length - 1) * sizeof(score_t));
}
for (; next_diagonal_index < diagonals_count; ++next_diagonal_index) {
size_t const next_diagonal_length = diagonals_count - next_diagonal_index;
scorer( shorter_reversed + shorter_length - shorter_dim + 1, longer + next_diagonal_index - shorter_dim, next_diagonal_length, previous_scores, current_scores, current_scores + 1, next_scores, executor);
rotate_three(previous_scores, current_scores, next_scores);
previous_scores++;
}
result_ref = scorer.score();
return status_t::success_k;
}
};
template <typename char_or_rune_type_, typename score_type_, typename substituter_type_,
sz_similarity_objective_t objective_, sz_similarity_locality_t locality_, sz_capability_t capability_,
typename enable_>
#if SZ_HAS_CONCEPTS_
requires score_like<score_type_> && substituter_like<substituter_type_>
#endif
struct diagonal_walker<char_or_rune_type_, score_type_, substituter_type_, affine_gap_costs_t, objective_, locality_,
capability_, enable_> {
using char_t = char_or_rune_type_;
using score_t = score_type_;
using substituter_t = substituter_type_;
using gap_costs_t = affine_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = locality_;
static constexpr sz_similarity_gaps_t gaps_k = sz_gaps_affine_k;
static constexpr sz_capability_t capability_k = capability_;
using tile_scorer_t = tile_scorer<char_t const *, char_t const *, score_t, substituter_t, gap_costs_t, objective_k,
locality_k, capability_k>;
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
diagonal_walker() noexcept {}
diagonal_walker(substituter_t subs, affine_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
struct layout_t {
size_t previous_scores = 0; size_t current_scores = 0;
size_t next_scores = 0;
size_t current_inserts = 0; size_t next_inserts = 0;
size_t current_deletes = 0; size_t next_deletes = 0;
size_t shorter_reversed = 0; size_t total = 0; constexpr operator size_t() const noexcept { return total; }
};
layout_t layout(span<char_t const> first, span<char_t const> second, cpu_specs_t const &specs) const noexcept {
size_t const shorter_length = sz_min_of_two(first.size(), second.size());
size_t const diagonal_bytes = sizeof(score_t) * (shorter_length + 1); scratch_amount_t amount {specs.cache_line_width};
layout_t at;
at.previous_scores = amount, amount += diagonal_bytes;
at.current_scores = amount, amount += diagonal_bytes;
at.next_scores = amount, amount += diagonal_bytes;
at.current_inserts = amount, amount += diagonal_bytes;
at.next_inserts = amount, amount += diagonal_bytes;
at.current_deletes = amount, amount += diagonal_bytes;
at.next_deletes = amount, amount += diagonal_bytes;
at.shorter_reversed = amount, amount += shorter_length * sizeof(char_t);
at.total = amount;
return at;
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, score_t &result_ref,
scratch_space_t scratch_space, executor_type_ &&executor,
cpu_specs_t const &specs) const noexcept {
if (first.empty() || second.empty()) {
result_ref = 0;
if constexpr (locality_k == sz_similarity_global_k) {
if (!first.empty() && second.empty()) {
result_ref = gap_costs_.open + gap_costs_.extend * (first.size() - 1);
}
else if (first.empty() && !second.empty()) {
result_ref = gap_costs_.open + gap_costs_.extend * (second.size() - 1);
}
}
return status_t::success_k;
}
char_t const *shorter = first.data(), *longer = second.data();
size_t shorter_length = first.size(), longer_length = second.size();
if (shorter_length > longer_length) {
trivial_swap(shorter, longer);
trivial_swap(shorter_length, longer_length);
}
size_t const shorter_dim = shorter_length + 1;
size_t const longer_dim = longer_length + 1;
size_t const diagonals_count = shorter_dim + longer_dim - 1;
size_t const max_diagonal_length = shorter_length + 1;
layout_t const at = layout(first, second, specs);
if (scratch_space.size() < at.total) return status_t::bad_alloc_k;
score_t *previous_scores = (score_t *)(scratch_space.data() + at.previous_scores);
score_t *current_scores = (score_t *)(scratch_space.data() + at.current_scores);
score_t *next_scores = (score_t *)(scratch_space.data() + at.next_scores);
score_t *current_inserts = (score_t *)(scratch_space.data() + at.current_inserts);
score_t *next_inserts = (score_t *)(scratch_space.data() + at.next_inserts);
score_t *current_deletes = (score_t *)(scratch_space.data() + at.current_deletes);
score_t *next_deletes = (score_t *)(scratch_space.data() + at.next_deletes);
char_t *const shorter_reversed = (char_t *)(scratch_space.data() + at.shorter_reversed);
for (size_t i = 0; i != shorter_length; ++i) shorter_reversed[i] = shorter[shorter_length - 1 - i];
tile_scorer_t scorer {substituter_, gap_costs_};
scorer.prepare(first.size() > second.size());
scorer.init_score(previous_scores[0], 0);
scorer.init_score(current_scores[0], 1);
scorer.init_score(current_scores[1], 1);
scorer.init_gap(current_inserts[0], 1);
scorer.init_gap(current_deletes[1], 1);
size_t next_diagonal_index = 2;
for (; next_diagonal_index < shorter_dim; ++next_diagonal_index) {
size_t const next_diagonal_length = next_diagonal_index + 1;
scorer( shorter_reversed + shorter_length - next_diagonal_index + 1, longer, next_diagonal_length - 2, previous_scores, current_scores, current_scores + 1, current_inserts, current_deletes + 1, next_scores + 1, next_inserts + 1, next_deletes + 1, executor );
scorer.init_score(next_scores[0], next_diagonal_index);
scorer.init_score(next_scores[next_diagonal_length - 1], next_diagonal_index);
scorer.init_gap(next_inserts[0], next_diagonal_index);
scorer.init_gap(next_deletes[next_diagonal_length - 1], next_diagonal_index);
rotate_three(previous_scores, current_scores, next_scores);
trivial_swap(current_inserts, next_inserts);
trivial_swap(current_deletes, next_deletes);
}
for (; next_diagonal_index < longer_dim; ++next_diagonal_index) {
size_t const next_diagonal_length = shorter_dim;
scorer( shorter_reversed + shorter_length - shorter_dim + 1, longer + next_diagonal_index - shorter_dim, next_diagonal_length - 1, previous_scores, current_scores, current_scores + 1, current_inserts, current_deletes + 1, next_scores, next_inserts, next_deletes, executor );
scorer.init_score(next_scores[next_diagonal_length - 1], next_diagonal_index);
scorer.init_gap(next_deletes[next_diagonal_length - 1], next_diagonal_index);
rotate_three(previous_scores, current_scores, next_scores);
trivial_swap(current_inserts, next_inserts);
trivial_swap(current_deletes, next_deletes);
sz_move_serial((ptr_t)(previous_scores), (ptr_t)(previous_scores + 1),
(max_diagonal_length - 1) * sizeof(score_t));
}
for (; next_diagonal_index < diagonals_count; ++next_diagonal_index) {
size_t const next_diagonal_length = diagonals_count - next_diagonal_index;
scorer( shorter_reversed + shorter_length - shorter_dim + 1, longer + next_diagonal_index - shorter_dim, next_diagonal_length, previous_scores, current_scores, current_scores + 1, current_inserts, current_deletes + 1, next_scores, next_inserts, next_deletes, executor );
rotate_three(previous_scores, current_scores, next_scores);
trivial_swap(current_inserts, next_inserts);
trivial_swap(current_deletes, next_deletes);
previous_scores++;
}
result_ref = scorer.score();
return status_t::success_k;
}
};
#pragma endregion Diagonal Walker
#pragma region Horizontal Walker
template <typename char_or_rune_type_, typename score_type_, typename substituter_type_,
sz_similarity_objective_t objective_, sz_similarity_locality_t locality_>
#if SZ_HAS_CONCEPTS_
requires score_like<score_type_> && substituter_like<substituter_type_>
#endif
struct horizontal_walker<char_or_rune_type_, score_type_, substituter_type_, linear_gap_costs_t, objective_, locality_,
sz_cap_serial_k, void> {
using char_t = char_or_rune_type_;
using score_t = score_type_;
using substituter_t = substituter_type_;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = locality_;
static constexpr sz_capability_t capability_k = sz_cap_serial_k;
using walker_t =
horizontal_walker<char_t, score_t, substituter_t, gap_costs_t, objective_k, locality_k, capability_k, void>;
using tile_scorer_t = tile_scorer<constant_iterator<char_t>, char_t const *, score_t, substituter_t, gap_costs_t,
objective_k, locality_k, capability_k>;
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
horizontal_walker() noexcept {}
horizontal_walker(substituter_t subs, linear_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
struct layout_t {
size_t previous_row = 0; size_t current_row = 0;
size_t total = 0; constexpr operator size_t() const noexcept { return total; }
};
layout_t layout(span<char_t const> first, span<char_t const> second, cpu_specs_t const &specs) const noexcept {
size_t const row_bytes = sizeof(score_t) * (sz_min_of_two(first.size(), second.size()) + 1);
scratch_amount_t amount {specs.cache_line_width};
layout_t at;
at.previous_row = amount, amount += row_bytes;
at.current_row = amount, amount += row_bytes;
at.total = amount;
return at;
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, score_t &result_ref,
scratch_space_t scratch_space, executor_type_ &&executor,
cpu_specs_t const &specs) const noexcept {
if (first.empty() || second.empty()) {
result_ref = 0;
if constexpr (locality_k == sz_similarity_global_k) {
if (!first.empty() && second.empty()) { result_ref = gap_costs_.open_or_extend * first.size(); }
else if (first.empty() && !second.empty()) { result_ref = gap_costs_.open_or_extend * second.size(); }
}
return status_t::success_k;
}
char_t const *shorter = first.data(), *longer = second.data();
size_t shorter_length = first.size(), longer_length = second.size();
if (shorter_length > longer_length) {
trivial_swap(shorter, longer);
trivial_swap(shorter_length, longer_length);
}
size_t const shorter_dim = shorter_length + 1;
size_t const longer_dim = longer_length + 1;
layout_t const at = layout(first, second, specs);
if (scratch_space.size() < at.total) return status_t::bad_alloc_k;
score_t *previous_scores = (score_t *)(scratch_space.data() + at.previous_row);
score_t *current_scores = (score_t *)(scratch_space.data() + at.current_row);
tile_scorer_t scorer {substituter_, gap_costs_};
scorer.prepare(first.size() <= second.size());
for (size_t col_idx = 0; col_idx < shorter_dim; ++col_idx) scorer.init_score(previous_scores[col_idx], col_idx);
for (size_t row_idx = 1; row_idx < longer_dim; ++row_idx) {
scorer.init_score(current_scores[0], row_idx);
scorer( constant_iterator<char_t> {longer[row_idx - 1]}, shorter, shorter_dim - 1, previous_scores, previous_scores + 1, current_scores, current_scores + 1, executor );
trivial_swap(previous_scores, current_scores);
}
result_ref = scorer.score();
return status_t::success_k;
}
};
template <typename char_or_rune_type_, typename score_type_, typename substituter_type_,
sz_similarity_objective_t objective_, sz_similarity_locality_t locality_>
#if SZ_HAS_CONCEPTS_
requires score_like<score_type_> && substituter_like<substituter_type_>
#endif
struct horizontal_walker<char_or_rune_type_, score_type_, substituter_type_, affine_gap_costs_t, objective_, locality_,
sz_cap_serial_k, void> {
using char_t = char_or_rune_type_;
using score_t = score_type_;
using substituter_t = substituter_type_;
using gap_costs_t = affine_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = locality_;
static constexpr sz_capability_t capability_k = sz_cap_serial_k;
using walker_t =
horizontal_walker<char_t, score_t, substituter_t, gap_costs_t, objective_k, locality_k, capability_k, void>;
using tile_scorer_t = tile_scorer<constant_iterator<char_t>, char_t const *, score_t, substituter_t, gap_costs_t,
objective_k, locality_k, capability_k>;
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
horizontal_walker() noexcept {}
horizontal_walker(substituter_t subs, affine_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
struct layout_t {
size_t previous_scores = 0; size_t current_scores = 0;
size_t previous_inserts = 0; size_t current_inserts = 0;
size_t previous_deletes = 0; size_t current_deletes = 0;
size_t total = 0; constexpr operator size_t() const noexcept { return total; }
};
layout_t layout(span<char_t const> first, span<char_t const> second, cpu_specs_t const &specs) const noexcept {
size_t const row_bytes = sizeof(score_t) * (sz_min_of_two(first.size(), second.size()) + 1);
scratch_amount_t amount {specs.cache_line_width};
layout_t at;
at.previous_scores = amount, amount += row_bytes;
at.current_scores = amount, amount += row_bytes;
at.previous_inserts = amount, amount += row_bytes;
at.current_inserts = amount, amount += row_bytes;
at.previous_deletes = amount, amount += row_bytes;
at.current_deletes = amount, amount += row_bytes;
at.total = amount;
return at;
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, score_t &result_ref,
scratch_space_t scratch_space, executor_type_ &&executor,
cpu_specs_t const &specs) const noexcept {
if (first.empty() || second.empty()) {
result_ref = 0;
if constexpr (locality_k == sz_similarity_global_k) {
if (!first.empty() && second.empty()) {
result_ref = static_cast<score_t>(gap_costs_.open + gap_costs_.extend * (first.size() - 1));
}
else if (first.empty() && !second.empty()) {
result_ref = static_cast<score_t>(gap_costs_.open + gap_costs_.extend * (second.size() - 1));
}
}
return status_t::success_k;
}
char_t const *shorter = first.data(), *longer = second.data();
size_t shorter_length = first.size(), longer_length = second.size();
if (shorter_length > longer_length) {
trivial_swap(shorter, longer);
trivial_swap(shorter_length, longer_length);
}
size_t const shorter_dim = shorter_length + 1;
size_t const longer_dim = longer_length + 1;
layout_t const at = layout(first, second, specs);
if (scratch_space.size() < at.total) return status_t::bad_alloc_k;
score_t *previous_scores = (score_t *)(scratch_space.data() + at.previous_scores);
score_t *current_scores = (score_t *)(scratch_space.data() + at.current_scores);
score_t *previous_inserts = (score_t *)(scratch_space.data() + at.previous_inserts);
score_t *current_inserts = (score_t *)(scratch_space.data() + at.current_inserts);
score_t *previous_deletes = (score_t *)(scratch_space.data() + at.previous_deletes);
score_t *current_deletes = (score_t *)(scratch_space.data() + at.current_deletes);
tile_scorer_t scorer {substituter_, gap_costs_};
scorer.prepare(first.size() <= second.size());
previous_scores[0] = 0;
for (size_t col_idx = 1; col_idx < shorter_dim; ++col_idx) {
scorer.init_score(previous_scores[col_idx], col_idx);
scorer.init_gap(previous_deletes[col_idx], col_idx);
}
for (size_t row_idx = 1; row_idx < longer_dim; ++row_idx) {
scorer.init_score(current_scores[0], row_idx);
scorer.init_gap(current_inserts[0], row_idx);
scorer( constant_iterator<char_t> {longer[row_idx - 1]}, shorter, shorter_dim - 1, previous_scores, current_scores, previous_scores + 1, current_inserts, previous_deletes + 1, current_scores + 1, current_inserts + 1, current_deletes + 1, executor );
trivial_swap(previous_scores, current_scores);
trivial_swap(previous_inserts, current_inserts);
trivial_swap(previous_deletes, current_deletes);
}
result_ref = scorer.score();
return status_t::success_k;
}
};
#pragma endregion Horizontal Walker
#pragma endregion Algorithm Building Blocks
#pragma region Pairwise Algorithms on CPU
template <>
struct levenshtein_distance_myers<char, sz_cap_serial_k> {
using char_t = char;
static constexpr sz_capability_t capability_k = sz_cap_serial_k;
levenshtein_distance_myers() noexcept {}
static constexpr size_t stack_words_capacity_k = 8;
static constexpr size_t words_count_for(size_t shorter_length) noexcept { return (shorter_length + 63) / 64; }
static constexpr size_t dispatch_words_count_for(size_t shorter_length) noexcept {
return shorter_length <= 64 ? 1
: shorter_length <= 128 ? 2
: shorter_length <= 256 ? 4
: shorter_length <= 512 ? 8
: words_count_for(shorter_length);
}
struct layout_t {
size_t match_masks = 0;
size_t vertical_positives = 0;
size_t vertical_negatives = 0;
size_t total = 0;
constexpr operator size_t() const noexcept { return total; }
};
layout_t layout(span<char_t const> first, span<char_t const> second, cpu_specs_t const &specs) const noexcept {
size_t const shorter_length = sz_min_of_two(first.size(), second.size());
size_t const words_count = dispatch_words_count_for(shorter_length);
scratch_amount_t amount {specs.cache_line_width};
layout_t at;
at.match_masks = amount, amount += sizeof(u64_t) * words_count * 256;
if (words_count > stack_words_capacity_k) {
at.vertical_positives = amount, amount += sizeof(u64_t) * words_count;
at.vertical_negatives = amount, amount += sizeof(u64_t) * words_count;
}
at.total = amount;
return at;
}
using index_t = u32_t;
template <index_t words_count_> status_t unrolled_(span<char const> shorter, span<char const> longer, size_t &result_ref,
scratch_space_t scratch_space) const noexcept {
index_t const shorter_length = (index_t)shorter.size();
size_t const longer_length = longer.size();
if (shorter_length > words_count_ * 64) return status_t::unexpected_dimensions_k;
if (shorter_length == 0) {
result_ref = longer_length;
return status_t::success_k;
}
if (scratch_space.size() < sizeof(u64_t) * words_count_ * 256) return status_t::bad_alloc_k;
using match_masks_t = u64_t[words_count_][256];
match_masks_t &match_masks = *reinterpret_cast<match_masks_t *>(scratch_space.data());
for (index_t position = 0; position != shorter_length; ++position)
for (index_t word = 0; word != words_count_; ++word) match_masks[word][(u8_t)shorter[position]] = 0;
for (size_t position = 0; position != longer_length; ++position)
for (index_t word = 0; word != words_count_; ++word) match_masks[word][(u8_t)longer[position]] = 0;
for (index_t position = 0; position != shorter_length; ++position)
match_masks[position >> 6][(u8_t)shorter[position]] |= (u64_t)1 << (position & 63);
u64_t vertical_positives[words_count_], vertical_negatives[words_count_]; for (index_t word = 0; word != words_count_; ++word)
vertical_positives[word] = ~(u64_t)0, vertical_negatives[word] = 0;
index_t const last_word = (shorter_length - 1) >> 6, last_bit = (shorter_length - 1) & 63;
size_t distance = shorter_length;
for (size_t longer_position = 0; longer_position != longer_length; ++longer_position) {
u8_t const symbol = (u8_t)longer[longer_position];
u64_t horizontal_positive_carry = 1, horizontal_negative_carry = 0; for (index_t word = 0; word != words_count_; ++word) {
u64_t const pattern_matches = match_masks[word][symbol];
u64_t const vertical_carry = pattern_matches | vertical_negatives[word];
u64_t const matched_with_carry = pattern_matches | horizontal_negative_carry;
u64_t const diagonal_zero =
(((matched_with_carry & vertical_positives[word]) + vertical_positives[word]) ^
vertical_positives[word]) |
matched_with_carry;
u64_t horizontal_positive = vertical_negatives[word] | ~(diagonal_zero | vertical_positives[word]);
u64_t horizontal_negative = vertical_positives[word] & diagonal_zero;
if (word == last_word) {
distance += (horizontal_positive >> last_bit) & 1;
distance -= (horizontal_negative >> last_bit) & 1;
}
u64_t const horizontal_positive_carry_next = horizontal_positive >> 63;
u64_t const horizontal_negative_carry_next = horizontal_negative >> 63;
horizontal_positive = (horizontal_positive << 1) | horizontal_positive_carry;
horizontal_negative = (horizontal_negative << 1) | horizontal_negative_carry;
horizontal_positive_carry = horizontal_positive_carry_next;
horizontal_negative_carry = horizontal_negative_carry_next;
vertical_positives[word] = horizontal_negative | ~(vertical_carry | horizontal_positive);
vertical_negatives[word] = horizontal_positive & vertical_carry;
}
}
for (index_t position = 0; position != shorter_length; ++position)
match_masks[position >> 6][(u8_t)shorter[position]] = 0;
result_ref = distance;
return status_t::success_k;
}
status_t generic_(span<char const> shorter, span<char const> longer, size_t &result_ref,
scratch_space_t scratch_space) const noexcept {
size_t const shorter_length = shorter.size();
size_t const longer_length = longer.size();
if (shorter_length == 0) {
result_ref = longer_length;
return status_t::success_k;
}
size_t const words_count = words_count_for(shorter_length);
size_t const match_masks_bytes = sizeof(u64_t) * words_count * 256;
size_t const vertical_bytes = words_count > stack_words_capacity_k ? sizeof(u64_t) * words_count : 0;
if (scratch_space.size() < match_masks_bytes + 2 * vertical_bytes) return status_t::bad_alloc_k;
u64_t *const match_masks = reinterpret_cast<u64_t *>(scratch_space.data());
u64_t stack_vertical_positives[stack_words_capacity_k], stack_vertical_negatives[stack_words_capacity_k];
u64_t *vertical_positives = stack_vertical_positives;
u64_t *vertical_negatives = stack_vertical_negatives;
if (words_count > stack_words_capacity_k) {
std::byte *const scratch_end = scratch_space.data() + scratch_space.size();
vertical_negatives = reinterpret_cast<u64_t *>(scratch_end - vertical_bytes);
vertical_positives = reinterpret_cast<u64_t *>(scratch_end - 2 * vertical_bytes);
}
for (size_t position = 0; position != shorter_length; ++position)
for (size_t word = 0; word != words_count; ++word)
match_masks[(size_t)(u8_t)shorter[position] * words_count + word] = 0;
for (size_t position = 0; position != longer_length; ++position)
for (size_t word = 0; word != words_count; ++word)
match_masks[(size_t)(u8_t)longer[position] * words_count + word] = 0;
for (size_t position = 0; position != shorter_length; ++position)
match_masks[(size_t)(u8_t)shorter[position] * words_count + (position >> 6)] |= (u64_t)1 << (position & 63);
for (size_t word = 0; word != words_count; ++word)
vertical_positives[word] = ~(u64_t)0, vertical_negatives[word] = 0;
size_t const last_word = (shorter_length - 1) >> 6, last_bit = (shorter_length - 1) & 63;
size_t distance = shorter_length;
for (size_t longer_position = 0; longer_position != longer_length; ++longer_position) {
u8_t const symbol = (u8_t)longer[longer_position];
u64_t const *const match_row = &match_masks[(size_t)symbol * words_count];
u64_t horizontal_positive_carry = 1, horizontal_negative_carry = 0; for (size_t word = 0; word != words_count; ++word) {
u64_t const pattern_matches = match_row[word];
u64_t const vertical_carry = pattern_matches | vertical_negatives[word];
u64_t const matched_with_carry = pattern_matches | horizontal_negative_carry;
u64_t const diagonal_zero =
(((matched_with_carry & vertical_positives[word]) + vertical_positives[word]) ^
vertical_positives[word]) |
matched_with_carry;
u64_t horizontal_positive = vertical_negatives[word] | ~(diagonal_zero | vertical_positives[word]);
u64_t horizontal_negative = vertical_positives[word] & diagonal_zero;
if (word == last_word) {
distance += (horizontal_positive >> last_bit) & 1;
distance -= (horizontal_negative >> last_bit) & 1;
}
u64_t const horizontal_positive_carry_next = horizontal_positive >> 63;
u64_t const horizontal_negative_carry_next = horizontal_negative >> 63;
horizontal_positive = (horizontal_positive << 1) | horizontal_positive_carry;
horizontal_negative = (horizontal_negative << 1) | horizontal_negative_carry;
horizontal_positive_carry = horizontal_positive_carry_next;
horizontal_negative_carry = horizontal_negative_carry_next;
vertical_positives[word] = horizontal_negative | ~(vertical_carry | horizontal_positive);
vertical_negatives[word] = horizontal_positive & vertical_carry;
}
}
for (size_t position = 0; position != shorter_length; ++position)
match_masks[(size_t)(u8_t)shorter[position] * words_count + (position >> 6)] = 0;
result_ref = distance;
return status_t::success_k;
}
status_t operator()(span<char const> const &first, span<char const> const &second, size_t &result_ref,
scratch_space_t scratch_space) noexcept {
bool const first_is_shorter = first.size() <= second.size();
span<char const> shorter = first_is_shorter ? first : second;
span<char const> longer = first_is_shorter ? second : first;
size_t const shorter_length = shorter.size();
if (shorter_length <= 64) return unrolled_<1>(shorter, longer, result_ref, scratch_space);
if (shorter_length <= 128) return unrolled_<2>(shorter, longer, result_ref, scratch_space);
if (shorter_length <= 256) return unrolled_<4>(shorter, longer, result_ref, scratch_space);
if (shorter_length <= 512) return unrolled_<8>(shorter, longer, result_ref, scratch_space);
return generic_(shorter, longer, result_ref, scratch_space); }
};
template <>
struct levenshtein_distance_myers<rune_t, sz_cap_serial_k> {
using char_t = rune_t;
using index_t = u32_t;
static constexpr sz_capability_t capability_k = sz_cap_serial_k;
levenshtein_distance_myers() noexcept {}
static constexpr size_t stack_words_capacity_k = 8;
static constexpr size_t words_count_for(size_t shorter_length) noexcept {
return divide_round_up<size_t>(shorter_length, 64);
}
static index_t hash_capacity_for(size_t distinct_upper_bound) noexcept {
size_t const slots_wanted = sz_max_of_two(2 * distinct_upper_bound, (size_t)1);
return static_cast<index_t>(sz_size_bit_ceil(slots_wanted));
}
static constexpr rune_t empty_slot_k = static_cast<rune_t>(0xFFFFFFFFu);
static index_t hash_rune(rune_t rune, index_t capacity) noexcept {
u64_t const mixed = static_cast<u64_t>(static_cast<u32_t>(rune)) * 0x9E3779B97F4A7C15ull;
return static_cast<index_t>((mixed >> 32) & static_cast<u64_t>(capacity - 1));
}
struct layout_t {
size_t slot_keys = 0;
size_t slot_masks = 0;
size_t absent_row = 0;
size_t vertical_positives = 0;
size_t vertical_negatives = 0;
size_t total = 0;
constexpr operator size_t() const noexcept { return total; }
};
layout_t layout(span<char_t const> first, span<char_t const> second, cpu_specs_t const &specs) const noexcept {
size_t const shorter_length = sz_min_of_two(first.size(), second.size());
size_t const words_count = words_count_for(shorter_length);
index_t const capacity = hash_capacity_for(shorter_length);
scratch_amount_t amount {specs.cache_line_width};
layout_t at;
at.slot_keys = amount, amount += sizeof(rune_t) * capacity;
at.slot_masks = amount, amount += sizeof(u64_t) * static_cast<size_t>(capacity) * words_count;
at.absent_row = amount, amount += sizeof(u64_t) * words_count;
if (words_count > stack_words_capacity_k) {
at.vertical_positives = amount, amount += sizeof(u64_t) * words_count;
at.vertical_negatives = amount, amount += sizeof(u64_t) * words_count;
}
at.total = amount;
return at;
}
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, size_t &result_ref,
scratch_space_t scratch_space) const noexcept {
bool const first_is_shorter = first.size() <= second.size();
span<char_t const> shorter = first_is_shorter ? first : second;
span<char_t const> longer = first_is_shorter ? second : first;
size_t const shorter_length = shorter.size();
size_t const longer_length = longer.size();
if (shorter_length == 0) {
result_ref = longer_length;
return status_t::success_k;
}
size_t const words_count = words_count_for(shorter_length);
index_t const capacity = hash_capacity_for(shorter_length);
size_t const slot_keys_bytes = sizeof(rune_t) * capacity;
size_t const slot_masks_bytes = sizeof(u64_t) * static_cast<size_t>(capacity) * words_count;
size_t const absent_row_bytes = sizeof(u64_t) * words_count;
size_t const vertical_bytes = words_count > stack_words_capacity_k ? sizeof(u64_t) * words_count : 0;
if (scratch_space.size() < slot_keys_bytes + slot_masks_bytes + absent_row_bytes + 2 * vertical_bytes)
return status_t::bad_alloc_k;
rune_t *const slot_keys = reinterpret_cast<rune_t *>(scratch_space.data());
u64_t *const slot_masks = reinterpret_cast<u64_t *>(scratch_space.data() + slot_keys_bytes);
u64_t *const absent_row = reinterpret_cast<u64_t *>(scratch_space.data() + slot_keys_bytes + slot_masks_bytes);
u64_t stack_vertical_positives[stack_words_capacity_k], stack_vertical_negatives[stack_words_capacity_k];
u64_t *vertical_positives = stack_vertical_positives;
u64_t *vertical_negatives = stack_vertical_negatives;
if (words_count > stack_words_capacity_k) {
std::byte *const scratch_end = scratch_space.data() + scratch_space.size();
vertical_negatives = reinterpret_cast<u64_t *>(scratch_end - vertical_bytes);
vertical_positives = reinterpret_cast<u64_t *>(scratch_end - 2 * vertical_bytes);
}
for (index_t slot = 0; slot != capacity; ++slot) slot_keys[slot] = empty_slot_k;
for (size_t word = 0; word != words_count; ++word) absent_row[word] = 0;
for (size_t position = 0; position != shorter_length; ++position) {
rune_t const rune = shorter[position];
index_t slot = hash_rune(rune, capacity);
for (;; slot = (slot + 1) & (capacity - 1)) {
if (slot_keys[slot] == rune) break;
if (slot_keys[slot] == empty_slot_k) {
slot_keys[slot] = rune;
for (size_t word = 0; word != words_count; ++word)
slot_masks[static_cast<size_t>(slot) * words_count + word] = 0;
break;
}
}
slot_masks[static_cast<size_t>(slot) * words_count + (position >> 6)] |= (u64_t)1 << (position & 63);
}
for (size_t word = 0; word != words_count; ++word)
vertical_positives[word] = ~(u64_t)0, vertical_negatives[word] = 0;
size_t const last_word = (shorter_length - 1) >> 6, last_bit = (shorter_length - 1) & 63;
size_t distance = shorter_length;
for (size_t longer_position = 0; longer_position != longer_length; ++longer_position) {
rune_t const symbol = longer[longer_position];
u64_t const *match_row = absent_row;
for (index_t slot = hash_rune(symbol, capacity);; slot = (slot + 1) & (capacity - 1)) {
rune_t const key = slot_keys[slot];
if (key == symbol) {
match_row = &slot_masks[static_cast<size_t>(slot) * words_count];
break;
}
if (key == empty_slot_k) break;
}
u64_t horizontal_positive_carry = 1, horizontal_negative_carry = 0; for (size_t word = 0; word != words_count; ++word) {
u64_t const pattern_matches = match_row[word];
u64_t const vertical_carry = pattern_matches | vertical_negatives[word];
u64_t const matched_with_carry = pattern_matches | horizontal_negative_carry;
u64_t const diagonal_zero =
(((matched_with_carry & vertical_positives[word]) + vertical_positives[word]) ^
vertical_positives[word]) |
matched_with_carry;
u64_t horizontal_positive = vertical_negatives[word] | ~(diagonal_zero | vertical_positives[word]);
u64_t horizontal_negative = vertical_positives[word] & diagonal_zero;
if (word == last_word) {
distance += (horizontal_positive >> last_bit) & 1;
distance -= (horizontal_negative >> last_bit) & 1;
}
u64_t const horizontal_positive_carry_next = horizontal_positive >> 63;
u64_t const horizontal_negative_carry_next = horizontal_negative >> 63;
horizontal_positive = (horizontal_positive << 1) | horizontal_positive_carry;
horizontal_negative = (horizontal_negative << 1) | horizontal_negative_carry;
horizontal_positive_carry = horizontal_positive_carry_next;
horizontal_negative_carry = horizontal_negative_carry_next;
vertical_positives[word] = horizontal_negative | ~(vertical_carry | horizontal_positive);
vertical_negatives[word] = horizontal_positive & vertical_carry;
}
}
result_ref = distance;
return status_t::success_k;
}
};
template < typename char_or_rune_type_ = char, typename gap_costs_type_ = linear_gap_costs_t, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires gap_costs_like<gap_costs_type_>
#endif
struct levenshtein_distance {
using char_t = char_or_rune_type_;
using gap_costs_t = gap_costs_type_;
static constexpr sz_capability_t capability_k = capability_;
static constexpr sz_capability_t capability_serialized_k = serialize_capability(capability_k);
using myers_t = levenshtein_distance_myers<char_t, capability_serialized_k>;
static constexpr bool myers_handles_any_length_k = capability_serialized_k == sz_cap_serial_k;
using horizontal_u8_t = horizontal_walker<char_t, u8_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k, sz_similarity_global_k, capability_serialized_k>;
using diagonal_u8_t = diagonal_walker<char_t, u8_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k, sz_similarity_global_k, capability_serialized_k>;
using diagonal_u16_t = diagonal_walker<char_t, u16_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using diagonal_u32_t = diagonal_walker<char_t, u32_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using diagonal_u64_t = diagonal_walker<char_t, u64_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using linearized_fallback_t = levenshtein_distance<char_t, linear_gap_costs_t, capability_k>;
uniform_substitution_costs_t substituter_ {};
gap_costs_t gap_costs_ {};
levenshtein_distance() noexcept {}
levenshtein_distance(uniform_substitution_costs_t subs, gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(span<char_t const> first, span<char_t const> second,
cpu_specs_t const &specs) const noexcept {
if constexpr (is_same_type<gap_costs_t, affine_gap_costs_t>::value)
if (gap_costs_.open == gap_costs_.extend) {
linear_gap_costs_t linear_gap {gap_costs_.open};
linearized_fallback_t linear_backend(substituter_, linear_gap);
return linear_backend.scratch_space_needed(first, second, specs);
}
if constexpr (is_same_type<gap_costs_t, linear_gap_costs_t>::value && sizeof(char_t) == 1)
if (substituter_.match == 0 && substituter_.mismatch == 1 && gap_costs_.open_or_extend == 1 &&
(myers_handles_any_length_k || (std::min)(first.size(), second.size()) <= 512)) {
return myers_t {}.layout(first, second, specs);
}
using diagonal_memory_requirements_t = diagonal_memory_requirements<size_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
if (requirements.bytes_per_cell <= 1 && requirements.max_diagonal_length < 16)
return horizontal_u8_t {substituter_, gap_costs_}.layout(first, second, specs);
if (requirements.bytes_per_cell <= 1)
return diagonal_u8_t {substituter_, gap_costs_}.layout(first, second, specs);
if (requirements.bytes_per_cell == 2)
return diagonal_u16_t {substituter_, gap_costs_}.layout(first, second, specs);
if (requirements.bytes_per_cell == 4)
return diagonal_u32_t {substituter_, gap_costs_}.layout(first, second, specs);
return diagonal_u64_t {substituter_, gap_costs_}.layout(first, second, specs);
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, size_t &result_ref,
scratch_space_t scratch_space, executor_type_ &executor,
cpu_specs_t const &specs) const noexcept {
if constexpr (is_same_type<gap_costs_t, affine_gap_costs_t>::value)
if (gap_costs_.open == gap_costs_.extend) {
linear_gap_costs_t linear_gap {gap_costs_.open};
linearized_fallback_t linear_backend(substituter_, linear_gap);
return linear_backend(first, second, result_ref, scratch_space, executor, specs);
}
if constexpr (is_same_type<gap_costs_t, linear_gap_costs_t>::value && sizeof(char_t) == 1)
if (substituter_.match == 0 && substituter_.mismatch == 1 && gap_costs_.open_or_extend == 1 &&
(myers_handles_any_length_k || (std::min)(first.size(), second.size()) <= 512))
return myers_t {}(first, second, result_ref, scratch_space);
using diagonal_memory_requirements_t = diagonal_memory_requirements<size_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
if (requirements.bytes_per_cell <= 1 && requirements.max_diagonal_length < 16) {
u8_t result_u8 = std::numeric_limits<u8_t>::max();
status_t status = horizontal_u8_t {substituter_, gap_costs_}(first, second, result_u8, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u8;
}
else if (requirements.bytes_per_cell <= 1) {
u8_t result_u8 = std::numeric_limits<u8_t>::max();
status_t status = diagonal_u8_t {substituter_, gap_costs_}(first, second, result_u8, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u8;
}
else if (requirements.bytes_per_cell == 2) {
u16_t result_u16 = std::numeric_limits<u16_t>::max();
status_t status = diagonal_u16_t {substituter_, gap_costs_}(first, second, result_u16, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u16;
}
else if (requirements.bytes_per_cell == 4) {
u32_t result_u32 = std::numeric_limits<u32_t>::max();
status_t status = diagonal_u32_t {substituter_, gap_costs_}(first, second, result_u32, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u32;
}
else if (requirements.bytes_per_cell == 8) {
u64_t result_u64 = std::numeric_limits<u64_t>::max();
status_t status = diagonal_u64_t {substituter_, gap_costs_}(first, second, result_u64, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u64;
}
return status_t::success_k;
}
};
template < typename gap_costs_type_ = linear_gap_costs_t, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires gap_costs_like<gap_costs_type_>
#endif
struct levenshtein_distance_utf8 {
using gap_costs_t = gap_costs_type_;
static constexpr sz_capability_t capability_k = capability_;
static constexpr sz_capability_t capability_serialized_k = serialize_capability(capability_k);
using horizontal_u8_t = horizontal_walker<rune_t, u8_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k, sz_similarity_global_k, capability_serialized_k>;
using diagonal_u8_t = diagonal_walker<rune_t, u8_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k, sz_similarity_global_k, capability_serialized_k>;
using diagonal_u16_t = diagonal_walker<rune_t, u16_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using diagonal_u32_t = diagonal_walker<rune_t, u32_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using diagonal_u64_t = diagonal_walker<rune_t, u64_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using linearized_fallback_t = levenshtein_distance<char, linear_gap_costs_t, capability_k>;
using ascii_fallback_t = levenshtein_distance<char, gap_costs_t, capability_k>;
using rune_myers_t = levenshtein_distance_myers<rune_t, sz_cap_serial_k>;
static constexpr bool rune_myers_available_k = capability_serialized_k == sz_cap_serial_k;
uniform_substitution_costs_t substituter_ {};
gap_costs_t gap_costs_ {};
levenshtein_distance_utf8() noexcept {}
levenshtein_distance_utf8(uniform_substitution_costs_t subs, gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
struct transcode_layout_t {
size_t first_ceiling, second_ceiling, total;
};
SZ_INLINE transcode_layout_t transcode_layout_(span<char const> first, span<char const> second,
cpu_specs_t const &specs) const noexcept {
size_t const first_ceiling = round_up_to_multiple(sizeof(rune_t) * first.size(), specs.cache_line_width);
size_t const second_ceiling = round_up_to_multiple(sizeof(rune_t) * second.size(), specs.cache_line_width);
return {first_ceiling, second_ceiling, first_ceiling + second_ceiling};
}
size_t scratch_space_needed(span<char const> first, span<char const> second,
cpu_specs_t const &specs) const noexcept {
size_t const transcode_bytes = transcode_layout_(first, second, specs).total;
diagonal_memory_requirements<size_t> rune_requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(rune_t), specs.cache_line_width);
size_t utf8_path = transcode_bytes + rune_requirements.total;
if constexpr (rune_myers_available_k && is_same_type<gap_costs_t, linear_gap_costs_t>::value)
if (substituter_.match == 0 && substituter_.mismatch == 1 && gap_costs_.open_or_extend == 1) {
span<rune_t const> const first_runes_upper_bound {nullptr, first.size()};
span<rune_t const> const second_runes_upper_bound {nullptr, second.size()};
size_t const myers_path =
transcode_bytes +
rune_myers_t {}.layout(first_runes_upper_bound, second_runes_upper_bound, specs).total;
utf8_path = sz_max_of_two(utf8_path, myers_path);
}
size_t const ascii_path = ascii_fallback_t {substituter_, gap_costs_}.scratch_space_needed(first, second,
specs);
return sz_max_of_two(utf8_path, ascii_path);
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char const> const &first, span<char const> const &second, size_t &result_ref,
scratch_space_t scratch_space, executor_type_ &executor,
cpu_specs_t const &specs) const noexcept {
if constexpr (is_same_type<gap_costs_t, affine_gap_costs_t>::value)
if (gap_costs_.open == gap_costs_.extend) {
linear_gap_costs_t linear_gap {gap_costs_.open};
linearized_fallback_t linear_backend(substituter_, linear_gap);
return linear_backend(first, second, result_ref, scratch_space, executor, specs);
}
if (sz_isascii(first.data(), first.size()) && sz_isascii(second.data(), second.size()))
return ascii_fallback_t {substituter_, gap_costs_}(first, second, result_ref, scratch_space, executor,
specs);
transcode_layout_t const layout = transcode_layout_(first, second, specs);
size_t const transcode_bytes = layout.total;
if (scratch_space.size() < transcode_bytes) return status_t::bad_alloc_k;
rune_t *const first_data_utf32 = reinterpret_cast<rune_t *>(scratch_space.data());
rune_t *const second_data_utf32 = reinterpret_cast<rune_t *>(scratch_space.data() + layout.first_ceiling);
scratch_space_t const walker_scratch = scratch_space.subspan(transcode_bytes,
scratch_space.size() - transcode_bytes);
rune_length_t rune_length;
size_t first_length_utf32 = 0, second_length_utf32 = 0;
for (size_t progress_utf8 = 0; progress_utf8 < first.size();
progress_utf8 += rune_length, ++first_length_utf32) {
rune_length = sz_rune_decode_unchecked(first.data() + progress_utf8, first_data_utf32 + first_length_utf32);
if (rune_length == sz_rune_invalid_k) return status_t::invalid_utf8_k;
}
for (size_t progress_utf8 = 0; progress_utf8 < second.size();
progress_utf8 += rune_length, ++second_length_utf32) {
rune_length = sz_rune_decode_unchecked(second.data() + progress_utf8,
second_data_utf32 + second_length_utf32);
if (rune_length == sz_rune_invalid_k) return status_t::invalid_utf8_k;
}
using diagonal_memory_requirements_t = diagonal_memory_requirements<size_t>;
diagonal_memory_requirements_t requirements( first_length_utf32, second_length_utf32, gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(rune_t), specs.cache_line_width);
span<rune_t const> const first_utf32 {first_data_utf32, first_length_utf32};
span<rune_t const> const second_utf32 {second_data_utf32, second_length_utf32};
if constexpr (rune_myers_available_k && is_same_type<gap_costs_t, linear_gap_costs_t>::value)
if (substituter_.match == 0 && substituter_.mismatch == 1 && gap_costs_.open_or_extend == 1)
return rune_myers_t {}(first_utf32, second_utf32, result_ref, walker_scratch);
if (requirements.bytes_per_cell <= 1 && requirements.max_diagonal_length < 16) {
u8_t result_u8 = std::numeric_limits<u8_t>::max();
status_t status = horizontal_u8_t {substituter_, gap_costs_}(first_utf32, second_utf32, result_u8,
walker_scratch, executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u8;
}
else if (requirements.bytes_per_cell <= 1) {
u8_t result_u8 = std::numeric_limits<u8_t>::max();
status_t status = diagonal_u8_t {substituter_, gap_costs_}(first_utf32, second_utf32, result_u8,
walker_scratch, executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u8;
}
else if (requirements.bytes_per_cell == 2) {
u16_t result_u16 = std::numeric_limits<u16_t>::max();
status_t status = diagonal_u16_t {substituter_, gap_costs_}(first_utf32, second_utf32, result_u16,
walker_scratch, executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u16;
}
else if (requirements.bytes_per_cell == 4) {
u32_t result_u32 = std::numeric_limits<u32_t>::max();
status_t status = diagonal_u32_t {substituter_, gap_costs_}(first_utf32, second_utf32, result_u32,
walker_scratch, executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u32;
}
else if (requirements.bytes_per_cell == 8) {
u64_t result_u64 = std::numeric_limits<u64_t>::max();
status_t status = diagonal_u64_t {substituter_, gap_costs_}(first_utf32, second_utf32, result_u64,
walker_scratch, executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u64;
}
return status_t::success_k;
}
};
template < typename char_or_rune_type_ = char, typename substituter_type_ = error_costs_32x32_t, typename gap_costs_type_ = linear_gap_costs_t, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires gap_costs_like<gap_costs_type_>
#endif
struct needleman_wunsch_score {
using char_t = char_or_rune_type_;
using substituter_t = substituter_type_;
using gap_costs_t = gap_costs_type_;
static constexpr sz_capability_t capability_k = capability_;
static constexpr sz_capability_t capability_serialized_k = serialize_capability(capability_k);
using horizontal_i16_t = horizontal_walker<char_t, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_global_k, capability_serialized_k>;
using diagonal_i16_t = diagonal_walker<char_t, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_global_k, capability_serialized_k>;
using diagonal_i32_t = diagonal_walker<char_t, i32_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_global_k, capability_k>;
using diagonal_i64_t = diagonal_walker<char_t, i64_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_global_k, capability_k>;
substituter_t substituter_ {};
gap_costs_t gap_costs_ {};
needleman_wunsch_score() noexcept {}
needleman_wunsch_score(substituter_t subs, gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(span<char_t const> first, span<char_t const> second,
cpu_specs_t const &specs) const noexcept {
using diagonal_memory_requirements_t = diagonal_memory_requirements<ssize_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
if (requirements.bytes_per_cell <= 2 && requirements.max_diagonal_length < 16)
return horizontal_i16_t {substituter_, gap_costs_}.layout(first, second, specs);
if (requirements.bytes_per_cell <= 2)
return diagonal_i16_t {substituter_, gap_costs_}.layout(first, second, specs);
if (requirements.bytes_per_cell == 4)
return diagonal_i32_t {substituter_, gap_costs_}.layout(first, second, specs);
return diagonal_i64_t {substituter_, gap_costs_}.layout(first, second, specs);
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, ssize_t &result_ref,
scratch_space_t scratch_space, executor_type_ &executor,
cpu_specs_t const &specs) const noexcept {
using diagonal_memory_requirements_t = diagonal_memory_requirements<ssize_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
status_t status = status_t::success_k;
if (requirements.bytes_per_cell <= 2 && requirements.max_diagonal_length < 16) {
i16_t result_i16 = std::numeric_limits<i16_t>::min();
status = horizontal_i16_t {substituter_, gap_costs_}(first, second, result_i16, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i16;
}
else if (requirements.bytes_per_cell <= 2) {
i16_t result_i16 = std::numeric_limits<i16_t>::min();
status = diagonal_i16_t {substituter_, gap_costs_}(first, second, result_i16, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i16;
}
else if (requirements.bytes_per_cell == 4) {
i32_t result_i32 = std::numeric_limits<i32_t>::min();
status = diagonal_i32_t {substituter_, gap_costs_}(first, second, result_i32, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i32;
}
else if (requirements.bytes_per_cell == 8) {
i64_t result_i64 = std::numeric_limits<i64_t>::min();
status = diagonal_i64_t {substituter_, gap_costs_}(first, second, result_i64, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i64;
}
return status;
}
};
template < typename char_or_rune_type_ = char, typename substituter_type_ = error_costs_32x32_t, typename gap_costs_type_ = linear_gap_costs_t, sz_capability_t capability_ = sz_cap_serial_k, typename enable_ = void >
#if SZ_HAS_CONCEPTS_
requires gap_costs_like<gap_costs_type_>
#endif
struct smith_waterman_score {
using char_t = char_or_rune_type_;
using substituter_t = substituter_type_;
using gap_costs_t = gap_costs_type_;
static constexpr sz_capability_t capability_k = capability_;
static constexpr sz_capability_t capability_serialized_k = serialize_capability(capability_k);
using horizontal_i16_t = horizontal_walker<char_t, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_local_k, capability_serialized_k>;
using diagonal_i16_t = diagonal_walker<char_t, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_local_k, capability_serialized_k>;
using diagonal_i32_t = diagonal_walker<char_t, i32_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_local_k, capability_k>;
using diagonal_i64_t = diagonal_walker<char_t, i64_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_local_k, capability_k>;
substituter_t substituter_ {};
gap_costs_t gap_costs_ {};
smith_waterman_score() noexcept {}
smith_waterman_score(substituter_t subs, gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(span<char_t const> first, span<char_t const> second,
cpu_specs_t const &specs) const noexcept {
using diagonal_memory_requirements_t = diagonal_memory_requirements<ssize_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
if (requirements.bytes_per_cell <= 2 && requirements.max_diagonal_length < 16)
return horizontal_i16_t {substituter_, gap_costs_}.layout(first, second, specs);
if (requirements.bytes_per_cell <= 2)
return diagonal_i16_t {substituter_, gap_costs_}.layout(first, second, specs);
if (requirements.bytes_per_cell == 4)
return diagonal_i32_t {substituter_, gap_costs_}.layout(first, second, specs);
return diagonal_i64_t {substituter_, gap_costs_}.layout(first, second, specs);
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, ssize_t &result_ref,
scratch_space_t scratch_space, executor_type_ &executor,
cpu_specs_t const &specs) const noexcept {
if (first.empty() || second.empty()) {
result_ref = 0;
return status_t::success_k;
}
using diagonal_memory_requirements_t = diagonal_memory_requirements<ssize_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
if (requirements.bytes_per_cell <= 2 && requirements.max_diagonal_length < 16) {
i16_t result_i16 = std::numeric_limits<i16_t>::min();
status_t status = horizontal_i16_t {substituter_, gap_costs_}(first, second, result_i16, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_i16;
}
else if (requirements.bytes_per_cell <= 2) {
i16_t result_i16 = std::numeric_limits<i16_t>::min();
status_t status = diagonal_i16_t {substituter_, gap_costs_}(first, second, result_i16, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_i16;
}
else if (requirements.bytes_per_cell == 4) {
i32_t result_i32 = std::numeric_limits<i32_t>::min();
status_t status = diagonal_i32_t {substituter_, gap_costs_}(first, second, result_i32, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_i32;
}
else if (requirements.bytes_per_cell == 8) {
i64_t result_i64 = std::numeric_limits<i64_t>::min();
status_t status = diagonal_i64_t {substituter_, gap_costs_}(first, second, result_i64, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_i64;
}
return status_t::success_k;
}
};
#pragma endregion
#pragma region Parallel Batch Algorithms
template < typename score_type_, typename scoring_engine_type_, typename queries_type_, typename candidates_type_, typename results_type_, typename scratch_buffer_type_ >
#if SZ_HAS_CONCEPTS_
requires score_like<score_type_>
#endif
status_t cross_sequentially_( scoring_engine_type_ &&scoring, queries_type_ const &queries, candidates_type_ const &candidates,
results_type_ &&results, cross_similarities_t cross_kind, scratch_buffer_type_ &scratch_buffer,
cpu_specs_t const &specs) noexcept {
using score_t = score_type_;
bool const is_symmetric = cross_kind == cross_similarities_t::symmetric_k;
size_t const queries_count = queries.size();
size_t const candidates_count = candidates.size();
size_t max_memory_requirement = 0;
if (queries_count != 0 && candidates_count != 0) {
size_t longest_query_index = 0, longest_candidate_index = 0;
for (size_t query_index = 1; query_index < queries_count; ++query_index)
if (queries[query_index].size() > queries[longest_query_index].size()) longest_query_index = query_index;
for (size_t candidate_index = 1; candidate_index < candidates_count; ++candidate_index)
if (candidates[candidate_index].size() > candidates[longest_candidate_index].size())
longest_candidate_index = candidate_index;
max_memory_requirement = scoring.scratch_space_needed(to_view(queries[longest_query_index]),
to_view(candidates[longest_candidate_index]), specs);
}
if (status_t status = scratch_buffer.try_resize(max_memory_requirement); status != status_t::success_k)
return status;
for (size_t query_index = 0; query_index < queries_count; ++query_index) {
size_t const candidate_end = is_symmetric ? query_index + 1 : candidates_count;
for (size_t candidate_index = 0; candidate_index < candidate_end; ++candidate_index) {
score_t result_score = 0;
dummy_executor_t dummy_executor;
status_t status = scoring(to_view(queries[query_index]), to_view(candidates[candidate_index]), result_score,
scratch_space_t(scratch_buffer), dummy_executor, specs);
if (status != status_t::success_k) return status;
results.data[query_index * results.row_stride + candidate_index] = result_score;
if (is_symmetric && candidate_index != query_index)
results.data[candidate_index * results.row_stride + query_index] = result_score;
}
}
return status_t::success_k;
}
template < typename score_type_, typename scoring_engine_type_, typename queries_type_, typename candidates_type_, typename results_type_, typename scratch_buffer_type_, typename executor_type_ = dummy_executor_t >
#if SZ_HAS_CONCEPTS_
requires score_like<score_type_> && executor_like<executor_type_>
#endif
status_t cross_in_parallel_( scoring_engine_type_ &&scoring, queries_type_ const &queries, candidates_type_ const &candidates, results_type_ &&results, cross_similarities_t cross_kind, scratch_buffer_type_ &scratch_buffer, executor_type_ &&executor, cpu_specs_t const &specs) noexcept {
using score_t = score_type_;
using executor_t = typename std::decay<executor_type_>::type;
using prong_t = typename executor_t::prong_t;
bool const is_symmetric = cross_kind == cross_similarities_t::symmetric_k;
size_t const queries_count = queries.size();
size_t const candidates_count = candidates.size();
if (queries_count == 0 || candidates_count == 0) return status_t::success_k;
size_t max_memory_per_small = 0, max_memory_for_large = 0;
auto const is_small = [&specs](size_t query_length, size_t candidate_length) noexcept {
return std::min(query_length, candidate_length) <= specs.l1_bytes;
};
for (size_t query_index = 0; query_index < queries_count; ++query_index) {
size_t const candidate_end = is_symmetric ? query_index + 1 : candidates_count;
for (size_t candidate_index = 0; candidate_index < candidate_end; ++candidate_index) {
size_t const needed = scoring.scratch_space_needed(to_view(queries[query_index]),
to_view(candidates[candidate_index]), specs);
if (is_small(queries[query_index].size(), candidates[candidate_index].size()))
max_memory_per_small = std::max(max_memory_per_small, needed);
else max_memory_for_large = std::max(max_memory_for_large, needed);
}
}
size_t const threads_count = executor.threads_count();
size_t const max_memory_requirement = std::max(max_memory_per_small * threads_count, max_memory_for_large);
if (status_t status = scratch_buffer.try_resize(max_memory_requirement); status != status_t::success_k)
return status;
std::atomic<status_t> error {status_t::success_k};
auto const write_cell = [&](size_t query_index, size_t candidate_index, score_t result_score) noexcept {
results.data[query_index * results.row_stride + candidate_index] = result_score;
if (is_symmetric && candidate_index != query_index)
results.data[candidate_index * results.row_stride + query_index] = result_score;
};
size_t const flattened_cells = queries_count * candidates_count;
executor.for_n_dynamic(flattened_cells, [&](prong_t prong) noexcept {
if (error.load() != status_t::success_k) return;
size_t const query_index = prong.task / candidates_count;
size_t const candidate_index = prong.task % candidates_count;
if (is_symmetric && candidate_index > query_index) return;
if (!is_small(queries[query_index].size(), candidates[candidate_index].size())) return;
score_t result_score = 0;
scratch_space_t worker_scratch = scratch_space_t(scratch_buffer).part_i_of_n(prong.thread, threads_count);
dummy_executor_t dummy_executor; status_t status = scoring(to_view(queries[query_index]), to_view(candidates[candidate_index]), result_score,
worker_scratch, dummy_executor, specs);
if (status == status_t::success_k) write_cell(query_index, candidate_index, result_score);
else error.store(status);
});
for (size_t query_index = 0; query_index < queries_count && error.load() == status_t::success_k; ++query_index) {
size_t const candidate_end = is_symmetric ? query_index + 1 : candidates_count;
for (size_t candidate_index = 0; candidate_index < candidate_end; ++candidate_index) {
if (is_small(queries[query_index].size(), candidates[candidate_index].size())) continue;
score_t result_score = 0;
status_t status = scoring(to_view(queries[query_index]), to_view(candidates[candidate_index]), result_score,
scratch_space_t(scratch_buffer), executor, specs);
if (status != status_t::success_k) {
error.store(status);
break;
}
write_cell(query_index, candidate_index, result_score);
}
}
return error.load();
}
#pragma region Shared Candidate Lane Cross Product Driver
template <typename value_type_>
struct cross_cell_destination_t {
value_type_ *primary = nullptr;
value_type_ *mirror = nullptr;
};
SZ_INLINE size_t triangular_number_(size_t rows) noexcept { return rows * (rows + 1) / 2; }
SZ_INLINE size_t cross_live_cells_count_(size_t queries_count, size_t candidates_count,
cross_similarities_t cross_kind) noexcept {
if (cross_kind == cross_similarities_t::symmetric_k) return triangular_number_(queries_count);
return queries_count * candidates_count;
}
SZ_INLINE void cross_cell_to_indices_(size_t cell_index, size_t candidates_count, cross_similarities_t cross_kind,
size_t &query_index, size_t &candidate_index) noexcept {
if (cross_kind == cross_similarities_t::symmetric_k) {
size_t row = 0;
while (triangular_number_(row + 1) <= cell_index) ++row;
query_index = row;
candidate_index = cell_index - triangular_number_(row);
}
else {
query_index = cell_index / candidates_count;
candidate_index = cell_index % candidates_count;
}
}
SZ_INLINE int candidate_length_bucket_(size_t length) noexcept {
return length <= 1 ? 0 : (int)(64 - sz_u64_clz((sz_u64_t)(length - 1)));
}
template <typename narrow_kernel_type_, typename wide_kernel_type_, typename fallback_type_, typename queries_type_,
typename candidates_type_, typename results_type_, typename fits_narrow_type_, typename fits_wide_type_,
typename empty_cell_type_>
status_t cross_product_candidate_lanes_range_( narrow_kernel_type_ &narrow_kernel, wide_kernel_type_ &wide_kernel, fallback_type_ &fallback,
queries_type_ const &queries, candidates_type_ const &candidates, results_type_ &&results,
cross_similarities_t cross_kind, size_t cell_begin, size_t cell_end, fits_narrow_type_ &&fits_narrow,
fits_wide_type_ &&fits_wide, empty_cell_type_ &&empty_cell, scratch_space_t scratch,
cpu_specs_t const &specs) noexcept {
using narrow_t = remove_cvref<narrow_kernel_type_>;
using wide_t = remove_cvref<wide_kernel_type_>;
using element_t = typename narrow_t::char_t; using value_t = remove_cvref<decltype(results.data[0])>;
constexpr size_t narrow_lanes_k = narrow_t::candidate_lanes_k;
using fallback_score_t =
typename std::conditional<narrow_t::objective_k == sz_minimize_distance_k, size_t, ssize_t>::type;
bool const is_symmetric = cross_kind == cross_similarities_t::symmetric_k;
size_t const candidates_count = candidates.size();
size_t longest_candidate = 0;
for (size_t cell_index = cell_begin; cell_index != cell_end; ++cell_index) {
size_t query_index = 0, candidate_index = 0;
cross_cell_to_indices_(cell_index, candidates_count, cross_kind, query_index, candidate_index);
size_t const query_length = to_view(queries[query_index]).size();
size_t const candidate_length = to_view(candidates[candidate_index]).size();
if (query_length != 0 && candidate_length != 0 && fits_wide(query_length, candidate_length))
longest_candidate = sz_max_of_two(longest_candidate, candidate_length);
}
size_t const transpose_bytes = narrow_lanes_k * longest_candidate * sizeof(element_t);
size_t const walker_scratch = longest_candidate
? sz_max_of_two(narrow_kernel.scratch_space_needed(longest_candidate, specs),
wide_kernel.scratch_space_needed(longest_candidate, specs))
: 0;
element_t *transposed = reinterpret_cast<element_t *>(scratch.data());
scratch_space_t walker_scratch_space = scratch.subspan(transpose_bytes, walker_scratch);
scratch_space_t fallback_scratch_space = scratch;
auto const destination_for = [&](size_t query_index, size_t candidate_index) noexcept {
cross_cell_destination_t<value_t> destination;
destination.primary = results.data + query_index * results.row_stride + candidate_index;
if (is_symmetric && candidate_index != query_index)
destination.mirror = results.data + candidate_index * results.row_stride + query_index;
return destination;
};
auto const scatter = [&](cross_cell_destination_t<value_t> const &destination, value_t score) noexcept {
*destination.primary = score;
if (destination.mirror) *destination.mirror = score;
};
dummy_executor_t dummy;
size_t lengths[narrow_lanes_k];
size_t block_candidates[narrow_lanes_k];
cross_cell_destination_t<value_t> destinations[narrow_lanes_k];
span<element_t const> query;
size_t query_length = 0, seed_query_index = 0, row_base = 0, cell_index = cell_begin, row_end = cell_begin;
auto const emit_block = [&](auto &chosen_kernel, size_t lanes_count, size_t block_longest) noexcept {
using chosen_t = remove_cvref<decltype(chosen_kernel)>;
constexpr size_t lane_capacity = chosen_t::candidate_lanes_k;
typename chosen_t::score_t result_lanes[narrow_t::candidate_lanes_k];
for (size_t position = 0; position != lane_capacity * block_longest; ++position) transposed[position] = 0;
for (size_t lane_index = 0; lane_index != lanes_count; ++lane_index) {
auto const lane_candidate = to_view(candidates[block_candidates[lane_index]]);
for (size_t position = 0; position < lane_candidate.size(); ++position)
transposed[position * lane_capacity + lane_index] = lane_candidate[position];
}
candidate_lanes_block<element_t> block;
block.transposed = transposed;
block.lane_capacity = lane_capacity;
block.lanes_count = lanes_count;
block.lengths = lengths;
block.longest_candidate = block_longest;
status_t status = chosen_kernel(query, block, result_lanes, walker_scratch_space, specs);
if (status != status_t::success_k) return status;
for (size_t lane_index = 0; lane_index != lanes_count; ++lane_index)
scatter(destinations[lane_index], static_cast<value_t>(result_lanes[lane_index]));
return status_t::success_k;
};
auto const run_tier = [&](auto &chosen_kernel, auto &&in_tier, size_t lane_capacity) noexcept {
int max_bucket = -1;
for (size_t r = cell_index; r != row_end; ++r) {
size_t const candidate_length = to_view(candidates[r - row_base]).size();
if (candidate_length == 0 || !in_tier(candidate_length)) continue;
int const bucket = candidate_length_bucket_(candidate_length);
if (bucket > max_bucket) max_bucket = bucket;
}
for (int bucket = 0; bucket <= max_bucket; ++bucket) {
size_t lanes_count = 0, block_longest = 0;
for (size_t r = cell_index; r != row_end; ++r) {
size_t const candidate_index = r - row_base;
size_t const candidate_length = to_view(candidates[candidate_index]).size();
if (candidate_length == 0 || !in_tier(candidate_length)) continue;
if (candidate_length_bucket_(candidate_length) != bucket) continue;
block_candidates[lanes_count] = candidate_index;
lengths[lanes_count] = candidate_length;
destinations[lanes_count] = destination_for(seed_query_index, candidate_index);
block_longest = sz_max_of_two(block_longest, candidate_length);
++lanes_count;
if (lanes_count == lane_capacity) {
if (status_t status = emit_block(chosen_kernel, lanes_count, block_longest);
status != status_t::success_k)
return status;
lanes_count = 0, block_longest = 0;
}
}
if (lanes_count)
if (status_t status = emit_block(chosen_kernel, lanes_count, block_longest);
status != status_t::success_k)
return status;
}
return status_t::success_k;
};
while (cell_index != cell_end) {
size_t seed_candidate_index = 0;
cross_cell_to_indices_(cell_index, candidates_count, cross_kind, seed_query_index, seed_candidate_index);
query = to_view(queries[seed_query_index]);
query_length = query.size();
row_base = is_symmetric ? triangular_number_(seed_query_index) : seed_query_index * candidates_count;
size_t const row_full_end = is_symmetric ? row_base + seed_query_index + 1 : row_base + candidates_count;
row_end = sz_min_of_two(row_full_end, cell_end);
for (size_t r = cell_index; r != row_end; ++r) {
size_t const candidate_index = r - row_base;
size_t const candidate_length = to_view(candidates[candidate_index]).size();
if (query_length == 0 || candidate_length == 0) {
scatter(destination_for(seed_query_index, candidate_index),
static_cast<value_t>(empty_cell(query_length, candidate_length)));
continue;
}
if (!fits_wide(query_length, candidate_length)) {
fallback_score_t result_score = 0;
if (status_t status = fallback(query, to_view(candidates[candidate_index]), result_score,
fallback_scratch_space, dummy, specs);
status != status_t::success_k)
return status;
scatter(destination_for(seed_query_index, candidate_index), static_cast<value_t>(result_score));
}
}
auto const fits_narrow_tier = [&](size_t candidate_length) noexcept {
return fits_narrow(query_length, candidate_length);
};
auto const fits_wide_tier = [&](size_t candidate_length) noexcept {
return !fits_narrow(query_length, candidate_length) && fits_wide(query_length, candidate_length);
};
if (status_t status = run_tier(narrow_kernel, fits_narrow_tier, narrow_t::candidate_lanes_k);
status != status_t::success_k)
return status;
if (status_t status = run_tier(wide_kernel, fits_wide_tier, wide_t::candidate_lanes_k);
status != status_t::success_k)
return status;
cell_index = row_end;
}
return status_t::success_k;
}
template <typename narrow_kernel_type_, typename wide_kernel_type_, typename fallback_type_, typename queries_type_,
typename candidates_type_, typename fits_wide_type_>
size_t cross_product_candidate_lanes_scratch_( narrow_kernel_type_ &narrow_kernel, wide_kernel_type_ &wide_kernel, fallback_type_ &fallback,
queries_type_ const &queries, candidates_type_ const &candidates, fits_wide_type_ &&fits_wide,
cpu_specs_t const &specs) noexcept {
constexpr size_t narrow_lanes_k = remove_cvref<narrow_kernel_type_>::candidate_lanes_k;
using element_t = typename remove_cvref<narrow_kernel_type_>::char_t;
size_t longest_query = 0, longest_query_index = 0, longest_candidate = 0, longest_candidate_index = 0;
for (size_t index = 0; index < queries.size(); ++index)
if (to_view(queries[index]).size() > longest_query)
longest_query = to_view(queries[index]).size(), longest_query_index = index;
for (size_t index = 0; index < candidates.size(); ++index)
if (to_view(candidates[index]).size() > longest_candidate)
longest_candidate = to_view(candidates[index]).size(), longest_candidate_index = index;
size_t const transpose_bytes = narrow_lanes_k * longest_candidate * sizeof(element_t);
size_t const walker_scratch = longest_candidate
? sz_max_of_two(narrow_kernel.scratch_space_needed(longest_candidate, specs),
wide_kernel.scratch_space_needed(longest_candidate, specs))
: 0;
size_t fallback_scratch = 0;
if (queries.size() && candidates.size() && !fits_wide(longest_query, longest_candidate))
fallback_scratch = fallback.scratch_space_needed(to_view(queries[longest_query_index]),
to_view(candidates[longest_candidate_index]), specs);
return sz_max_of_two(transpose_bytes + walker_scratch, fallback_scratch);
}
template <typename narrow_kernel_type_, typename wide_kernel_type_, typename fallback_type_, typename queries_type_,
typename candidates_type_, typename results_type_, typename scratch_buffer_type_, typename executor_type_,
typename fits_narrow_type_, typename fits_wide_type_, typename empty_cell_type_>
status_t cross_product_candidate_lanes_parallel_( narrow_kernel_type_ &narrow_kernel, wide_kernel_type_ &wide_kernel, fallback_type_ &fallback,
queries_type_ const &queries, candidates_type_ const &candidates, results_type_ &&results,
cross_similarities_t cross_kind, scratch_buffer_type_ &scratch_buffer, executor_type_ &&executor,
fits_narrow_type_ &&fits_narrow, fits_wide_type_ &&fits_wide, empty_cell_type_ &&empty_cell,
cpu_specs_t const &specs) noexcept {
size_t const cells_count = cross_live_cells_count_(queries.size(), candidates.size(), cross_kind);
size_t const worker_scratch = cross_product_candidate_lanes_scratch_(narrow_kernel, wide_kernel, fallback, queries,
candidates, fits_wide, specs);
size_t const workers = sz_max_of_two(executor.threads_count(), (size_t)1);
if (status_t status = scratch_buffer.try_resize(worker_scratch * workers); status != status_t::success_k)
return status;
using prong_t = typename remove_cvref<executor_type_>::prong_t;
std::atomic<status_t> error {status_t::success_k};
executor.for_n_dynamic(cells_count, [&](prong_t prong) noexcept {
scratch_space_t slice = scratch_space_t(scratch_buffer).subspan(prong.thread * worker_scratch, worker_scratch);
status_t status = cross_product_candidate_lanes_range_(
narrow_kernel, wide_kernel, fallback, queries, candidates, results, cross_kind, prong.task,
prong.task + 1, fits_narrow, fits_wide, empty_cell, slice, specs);
if (status != status_t::success_k) error.store(status);
});
return error.load();
}
#pragma endregion Shared Candidate Lane Cross Product Driver
template < typename gap_costs_type_, typename allocator_type_, sz_capability_t capability_, typename enable_ >
#if SZ_HAS_CONCEPTS_
requires gap_costs_like<gap_costs_type_>
#endif
struct levenshtein_distances {
using gap_costs_t = gap_costs_type_;
using allocator_t = allocator_type_;
static constexpr sz_capability_t capability_k = capability_;
using scoring_t = levenshtein_distance<char, gap_costs_t, capability_k>;
uniform_substitution_costs_t substituter_ {};
gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
safe_vector<std::byte, scratch_allocator_t> scratch_ {alloc_};
levenshtein_distances(allocator_t alloc = {}) noexcept : alloc_(alloc) {}
levenshtein_distances(uniform_substitution_costs_t subs, gap_costs_t gaps,
allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc) {}
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
return cross_sequentially_<size_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_similarities_t::all_pairs_k, scratch_, specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
return cross_in_parallel_<size_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_similarities_t::all_pairs_k, scratch_, executor, specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
return cross_sequentially_<size_t>(scoring_t {substituter_, gap_costs_}, sequences, sequences, results,
cross_similarities_t::symmetric_k, scratch_, specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
return cross_in_parallel_<size_t>(scoring_t {substituter_, gap_costs_}, sequences, sequences, results,
cross_similarities_t::symmetric_k, scratch_, executor, specs);
}
};
template < typename gap_costs_type_, typename allocator_type_, sz_capability_t capability_, typename enable_ >
#if SZ_HAS_CONCEPTS_
requires gap_costs_like<gap_costs_type_>
#endif
struct levenshtein_distances_utf8 {
using gap_costs_t = gap_costs_type_;
using allocator_t = allocator_type_;
static constexpr sz_capability_t capability_k = capability_;
using scoring_t = levenshtein_distance_utf8<gap_costs_t, capability_k>;
uniform_substitution_costs_t substituter_ {};
gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
safe_vector<std::byte, scratch_allocator_t> scratch_ {alloc_};
levenshtein_distances_utf8(allocator_t alloc = {}) noexcept : alloc_(alloc) {}
levenshtein_distances_utf8(uniform_substitution_costs_t subs, gap_costs_t gaps,
allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc) {}
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
return cross_sequentially_<size_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_similarities_t::all_pairs_k, scratch_, specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
return cross_in_parallel_<size_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_similarities_t::all_pairs_k, scratch_, executor, specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
return cross_sequentially_<size_t>(scoring_t {substituter_, gap_costs_}, sequences, sequences, results,
cross_similarities_t::symmetric_k, scratch_, specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
return cross_in_parallel_<size_t>(scoring_t {substituter_, gap_costs_}, sequences, sequences, results,
cross_similarities_t::symmetric_k, scratch_, executor, specs);
}
};
template < typename substituter_type_, typename gap_costs_type_, typename allocator_type_, sz_capability_t capability_, typename enable_ >
#if SZ_HAS_CONCEPTS_
requires substituter_like<substituter_type_> && gap_costs_like<gap_costs_type_>
#endif
struct needleman_wunsch_scores {
using substituter_t = substituter_type_;
using gap_costs_t = gap_costs_type_;
using allocator_t = allocator_type_;
static constexpr sz_capability_t capability_k = capability_;
using scoring_t = needleman_wunsch_score<char, substituter_t, gap_costs_t, capability_k>;
substituter_t substituter_ {};
gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
safe_vector<std::byte, scratch_allocator_t> scratch_ {alloc_};
needleman_wunsch_scores(allocator_t alloc = {}) noexcept : alloc_(alloc) {}
needleman_wunsch_scores(substituter_t subs, gap_costs_t gaps, allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc) {}
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
return cross_sequentially_<ssize_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_similarities_t::all_pairs_k, scratch_, specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
return cross_in_parallel_<ssize_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_similarities_t::all_pairs_k, scratch_, executor, specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
return cross_sequentially_<ssize_t>(scoring_t {substituter_, gap_costs_}, sequences, sequences, results,
cross_similarities_t::symmetric_k, scratch_, specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
return cross_in_parallel_<ssize_t>(scoring_t {substituter_, gap_costs_}, sequences, sequences, results,
cross_similarities_t::symmetric_k, scratch_, executor, specs);
}
};
template < typename substituter_type_, typename gap_costs_type_, typename allocator_type_, sz_capability_t capability_, typename enable_ >
#if SZ_HAS_CONCEPTS_
requires substituter_like<substituter_type_> && gap_costs_like<gap_costs_type_>
#endif
struct smith_waterman_scores {
using substituter_t = substituter_type_;
using gap_costs_t = gap_costs_type_;
using allocator_t = allocator_type_;
static constexpr sz_capability_t capability_k = capability_;
using scoring_t = smith_waterman_score<char, substituter_t, gap_costs_t, capability_k>;
substituter_t substituter_ {};
gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
safe_vector<std::byte, scratch_allocator_t> scratch_ {alloc_};
smith_waterman_scores(allocator_t alloc = {}) noexcept : alloc_(alloc) {}
smith_waterman_scores(substituter_t subs, gap_costs_t gaps, allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc) {}
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
return cross_sequentially_<ssize_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_similarities_t::all_pairs_k, scratch_, specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
return cross_in_parallel_<ssize_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_similarities_t::all_pairs_k, scratch_, executor, specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
return cross_sequentially_<ssize_t>(scoring_t {substituter_, gap_costs_}, sequences, sequences, results,
cross_similarities_t::symmetric_k, scratch_, specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
return cross_in_parallel_<ssize_t>(scoring_t {substituter_, gap_costs_}, sequences, sequences, results,
cross_similarities_t::symmetric_k, scratch_, executor, specs);
}
};
#pragma endregion
} }
#endif