From 5d54e4e50386b16951f39ee7db39e6a8027ccddd Mon Sep 17 00:00:00 2001 From: Lucas <30107107+aabbdev@users.noreply.github.com> Date: Tue, 18 Aug 2026 20:35:51 +0800 Subject: [PATCH] Add exact long-context RLBWT backends --- README.md | 6 + native/src/rosa_native_step.cpp | 3224 ++++++++++++++++++++++++++++++- native/tests/rlbwt_smoke.py | 188 ++ native/uv.lock | 808 ++++++++ src/rosa/__init__.py | 122 +- src/rosa/_rlbwt_backend.py | 469 +++++ tests/test_rlbwt_backend.py | 415 ++++ 7 files changed, 5222 insertions(+), 10 deletions(-) create mode 100644 native/tests/rlbwt_smoke.py create mode 100644 native/uv.lock create mode 100644 src/rosa/_rlbwt_backend.py create mode 100644 tests/test_rlbwt_backend.py diff --git a/README.md b/README.md index 4d5807f..9b6bfc4 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,12 @@ for token in generated_token_ids: # each tensor has shape [2] state.reset() ``` +Experimental top-1 RLBWT backends are also available: `rlbwt` is the Python +oracle, `rlbwt_native` is the exact fused implementation, and +`rlbwt_compact256` is optimized for externally encoded IDs in `[0, 255]`. +`rlbwt_mc128` and `rlbwt_mc192` provide explicitly opt-in Monte-Carlo LCEs. +These backends are uniform-only and are never selected by `backend="auto"`. + Capacity is fixed at initialization for predictable memory use. Exceeding it raises `RuntimeError` before mutation. States are mutable, isolated, and must not be shared concurrently between decoding requests. `forward_step` implements diff --git a/native/src/rosa_native_step.cpp b/native/src/rosa_native_step.cpp index a1b506b..ae33e26 100644 --- a/native/src/rosa_native_step.cpp +++ b/native/src/rosa_native_step.cpp @@ -2,12 +2,15 @@ #include #include +#include #include #include +#include #include #include #include #include +#include #include #include #include @@ -85,6 +88,9 @@ class RowThreadPool { RowThreadPool &operator=(const RowThreadPool &) = delete; size_t worker_count() const { return workers_.size(); } + size_t storage_bytes() const noexcept { + return sizeof(*this) + workers_.capacity() * sizeof(std::thread); + } ~RowThreadPool() { { @@ -1760,9 +1766,3190 @@ class NativeCandidateState { bool ragged_mode_ = false; }; +// An owning implementation of the exact reversed-prefix RLBWT prototype. +// Unlike NativeState and NativeCandidateState, this class does not borrow any +// Python-owned storage. Each row is independent, so the existing persistent +// row pool can safely update rows in parallel. +class NativeRLBWTState { +public: + NativeRLBWTState(int64_t batch_size, int64_t max_length) + : NativeRLBWTState(batch_size, max_length, 0, 0, 0) {} + + NativeRLBWTState(int64_t batch_size, int64_t max_length, uint32_t lanes, + uint64_t seed) + : NativeRLBWTState(batch_size, max_length, lanes, seed, 0) {} + +protected: + NativeRLBWTState(int64_t batch_size, int64_t max_length, uint32_t lanes, + uint64_t seed, uint32_t vocabulary_size) + : batch_(batch_size), max_length_(max_length), lanes_(lanes), seed_(seed), + vocabulary_size_(vocabulary_size), identity_codes_(vocabulary_size != 0) { + if (batch_ <= 0) + throw py::value_error("batch_size must be > 0"); + if (max_length_ <= 0) + throw py::value_error("max_length must be > 0"); + if (static_cast(max_length_) >= + static_cast(std::numeric_limits::max())) + throw py::value_error("max_length must be < UINT32_MAX"); + if (lanes_ != 0 && lanes_ != 2 && lanes_ != 3) + throw py::value_error("lanes must be 2 or 3"); + if (identity_codes_ && (vocabulary_size_ < 1 || vocabulary_size_ > 256)) + throw py::value_error("vocabulary_size must be in [1, 256]"); + uint64_t splitmix_state = seed_; + for (uint32_t lane = 0; lane < lanes_; ++lane) { + bases_[lane] = splitmix64(splitmix_state) | uint64_t{1}; + if (bases_[lane] < 257) + bases_[lane] += 256; + while (std::find(bases_.begin(), bases_.begin() + lane, bases_[lane]) != + bases_.begin() + lane) { + bases_[lane] += 2; + if (bases_[lane] < 257) + bases_[lane] += 256; + } + powers_[lane].resize(static_cast(max_length_) + 1); + powers_[lane][0] = 1; + for (size_t index = 1; index < powers_[lane].size(); ++index) + powers_[lane][index] = powers_[lane][index - 1] * bases_[lane]; + } + rows_.reserve(static_cast(batch_)); + for (int64_t b = 0; b < batch_; ++b) + rows_.emplace_back(max_length_, identity_codes_); + if (lanes_ != 0) { + row_hashes_.resize(static_cast(batch_)); + for (auto &row_hashes : row_hashes_) + for (uint32_t lane = 0; lane < lanes_; ++lane) + row_hashes[lane].resize(static_cast(max_length_) + 1); + } + } + +public: + + py::array_t step(py::array tokens_object) { + auto tokens = checked_tokens(tokens_object, 1); + py::array_t output(batch_); + const int64_t *input = tokens.data(); + int64_t *result = output.mutable_data(); + { + py::gil_scoped_release release; + std::lock_guard lock(call_mutex_); + if (position_ >= max_length_) + throw std::runtime_error("inference state capacity exceeded"); + ensure_pool(128); + parallel_for_rows(128, [&](int64_t b) { + result[b] = step_row(rows_[static_cast(b)], position_, input[b]); + }); + ++position_; + } + return output; + } + + py::array_t prefill(py::array tokens_object) { + auto tokens = checked_tokens(tokens_object, 2); + if (tokens.shape(0) != batch_) + throw py::value_error( + "tokens must be contiguous int64 [batch_size, sequence_length]"); + const int64_t count = tokens.shape(1); + py::array_t output({batch_, count}); + const int64_t *input = tokens.data(); + int64_t *result = output.mutable_data(); + { + py::gil_scoped_release release; + std::lock_guard lock(call_mutex_); + if (position_ != 0) + throw std::runtime_error("prefill requires an empty inference state"); + if (count > max_length_) + throw std::runtime_error("inference state capacity exceeded"); + if (count != 0) { + ensure_pool(4); + parallel_for_rows(4, [&](int64_t b) { + Row &row = rows_[static_cast(b)]; + const int64_t *row_input = input + b * count; + int64_t *row_result = result + b * count; + // Keep the token loop inside the row job: no transposes, temporary + // columns, or repeated calls through the Python/C++ boundary. + for (int64_t position = 0; position < count; ++position) + row_result[position] = + step_row(row, position, row_input[position]); + }); + } + position_ = count; + } + return output; + } + + // Chunked long-context ingestion. Unlike the compatibility `prefill`, + // this method deliberately resumes at the current position so callers can + // bound their temporary token/output buffers while the owned index grows. + py::array_t prefill_append(py::array tokens_object) { + auto tokens = checked_tokens(tokens_object, 2); + const int64_t count = tokens.shape(1); + py::array_t output({batch_, count}); + const int64_t *input = tokens.data(); + int64_t *result = output.mutable_data(); + { + py::gil_scoped_release release; + std::lock_guard lock(call_mutex_); + if (count > max_length_ - position_) + throw std::runtime_error("inference state capacity exceeded"); + const int64_t base = position_; + if (count != 0) { + ensure_pool(4); + parallel_for_rows(4, [&](int64_t b) { + Row &row = rows_[static_cast(b)]; + const int64_t *row_input = input + b * count; + int64_t *row_result = result + b * count; + for (int64_t offset = 0; offset < count; ++offset) + row_result[offset] = + step_row(row, base + offset, row_input[offset]); + }); + } + position_ += count; + } + return output; + } + + void reset() { + py::gil_scoped_release release; + std::lock_guard lock(call_mutex_); + // Prepare every allocation first so reset cannot leave a partially reset + // batch. Histories release all pages and return to PACKED4 during commit. + std::vector reset_sequences; + reset_sequences.reserve(rows_.size()); + for (size_t row = 0; row < rows_.size(); ++row) { + reset_sequences.emplace_back(static_cast(max_length_) + 1); + } + for (size_t row = 0; row < rows_.size(); ++row) + rows_[row].reset(std::move(reset_sequences[row])); + for (auto &row_hashes : row_hashes_) + for (uint32_t lane = 0; lane < lanes_; ++lane) + std::fill(row_hashes[lane].begin(), row_hashes[lane].end(), uint64_t{0}); + position_ = 0; + } + + int64_t position() const { + std::lock_guard lock(call_mutex_); + return position_; + } + int64_t batch_size() const { return batch_; } + int64_t max_length() const { return max_length_; } + uint32_t lanes() const { return lanes_; } + uint64_t seed() const { return seed_; } + + py::array_t sources() const { + std::lock_guard lock(call_mutex_); + py::array_t result(batch_); + for (int64_t b = 0; b < batch_; ++b) + result.mutable_data()[b] = rows_[static_cast(b)].source; + return result; + } + + py::array_t lrs_lengths() const { + std::lock_guard lock(call_mutex_); + py::array_t result(batch_); + for (int64_t b = 0; b < batch_; ++b) + result.mutable_data()[b] = rows_[static_cast(b)].lrs; + return result; + } + + py::array_t run_counts() const { + std::lock_guard lock(call_mutex_); + py::array_t result(batch_); + for (int64_t b = 0; b < batch_; ++b) { + const Row &row = rows_[static_cast(b)]; + result.mutable_data()[b] = row.sequence.run_count(); + } + return result; + } + + int64_t storage_bytes() const { + std::lock_guard lock(call_mutex_); + size_t bytes = sizeof(NativeRLBWTState); + checked_add(bytes, checked_product(rows_.capacity(), sizeof(Row))); + checked_add(bytes, + checked_product(row_hashes_.capacity(), + sizeof(decltype(row_hashes_)::value_type))); + for (size_t row_index = 0; row_index < rows_.size(); ++row_index) { + const Row &row = rows_[row_index]; + checked_add(bytes, row.history.storage_bytes()); + checked_add(bytes, row.sequence.storage_bytes()); + if (identity_codes_) { + checked_add(bytes, sizeof(Row::IdentityCounts)); + } else { + checked_add(bytes, + checked_product(row.counts.capacity(), sizeof(SymbolCount))); + checked_add(bytes, checked_product(row.code_values.capacity(), + sizeof(int64_t))); + } + for (uint32_t lane = 0; lane < lanes_; ++lane) + checked_add(bytes, checked_product(row_hashes_[row_index][lane].capacity(), + sizeof(uint64_t))); + } + for (uint32_t lane = 0; lane < lanes_; ++lane) + checked_add(bytes, checked_product(powers_[lane].capacity(), + sizeof(uint64_t))); + if (row_pool_) + checked_add(bytes, row_pool_->storage_bytes()); + if (bytes > static_cast(std::numeric_limits::max())) + throw std::overflow_error("native RLBWT storage size exceeds int64"); + return static_cast(bytes); + } + + py::dict storage_breakdown() const { + std::lock_guard lock(call_mutex_); + size_t history = 0; + size_t dictionaries = 0; + std::array sequence{}; + for (const Row &row : rows_) { + checked_add(history, row.history.storage_bytes()); + if (identity_codes_) { + checked_add(dictionaries, sizeof(Row::IdentityCounts)); + } else { + checked_add(dictionaries, + checked_product(row.counts.capacity(), sizeof(SymbolCount))); + checked_add(dictionaries, checked_product(row.code_values.capacity(), + sizeof(int64_t))); + } + const auto components = row.sequence.storage_components(); + for (size_t index = 0; index < sequence.size(); ++index) + checked_add(sequence[index], components[index]); + } + py::dict result; + result["state"] = py::int_(sizeof(NativeRLBWTState)); + result["rows"] = py::int_(checked_product(rows_.capacity(), sizeof(Row))); + result["history"] = py::int_(history); + result["dictionary"] = py::int_(dictionaries); + result["arenas"] = py::int_(sequence[0]); + result["bwt"] = py::int_(sequence[1]); + result["pa"] = py::int_(sequence[2]); + result["lcs"] = py::int_(sequence[3]); + result["histograms"] = py::int_(sequence[4]); + return result; + } + + py::tuple row_snapshot(int64_t batch_index) const { + std::lock_guard lock(call_mutex_); + if (batch_index < 0 || batch_index >= batch_) + throw py::index_error("batch_index is out of range"); + const Row &row = rows_[static_cast(batch_index)]; + const int64_t live = position_ + 1; + py::array_t pa(live), lcs(live), bwt(live); + py::array_t sentinel_mask(live); + row.sequence.snapshot(row.code_values, identity_codes_, pa.mutable_data(), + lcs.mutable_data(), bwt.mutable_data(), + sentinel_mask.mutable_data()); + return py::make_tuple(std::move(pa), std::move(lcs), std::move(bwt), + std::move(sentinel_mask)); + } + +private: + static uint64_t splitmix64(uint64_t &state) noexcept { + uint64_t value = (state += 0x9e3779b97f4a7c15ULL); + value = (value ^ (value >> 30)) * 0xbf58476d1ce4e5b9ULL; + value = (value ^ (value >> 27)) * 0x94d049bb133111ebULL; + return value ^ (value >> 31); + } + + static size_t checked_product(size_t left, size_t right) { + if (right != 0 && left > std::numeric_limits::max() / right) + throw std::overflow_error("native RLBWT storage size overflow"); + return left * right; + } + + static void checked_add(size_t &total, size_t increment) { + if (increment > std::numeric_limits::max() - total) + throw std::overflow_error("native RLBWT storage size overflow"); + total += increment; + } + + static constexpr uint32_t kSentinelCode = + std::numeric_limits::max(); + + struct Run { + uint32_t code = kSentinelCode; + uint32_t length = 1; + }; + + struct SymbolCount { + int64_t symbol = 0; + uint32_t code = 0; + uint32_t count = 0; + }; + + // Append-only paged token history. No storage or page directory is sized + // from max_length: both grow only as live endpoints cross page boundaries. + // Exactly one payload width owns pages at a time, and promotion stages a + // complete replacement before swapping it into the live history. + class CompactArray { + public: + static constexpr size_t kPageCodes = 4096; + static constexpr size_t kPackedPageBytes = kPageCodes / 2; + template + using Pages = std::vector>; + + CompactArray() = default; + explicit CompactArray(size_t) {} + + size_t size() const noexcept { return live_size_; } + bool is_byte() const noexcept { return width_ == 1; } + uint32_t width() const noexcept { return width_; } + + void prepare_append(size_t index) { + if (index != live_size_) + throw std::runtime_error("history append position is out of range"); + const size_t page = index / kPageCodes; + if (width_ == 0) + ensure_packed_page(page); + else if (width_ == 4) + ensure_page(wide_pages_, page); + else if (width_ == 2) + ensure_page(narrow_pages_, page); + else + ensure_page(byte_pages_, page); + } + + void finish_append() noexcept { ++live_size_; } + + uint32_t get(size_t index) const noexcept { + const size_t page = index / kPageCodes, slot = index % kPageCodes; + if (width_ == 0) return get_packed(packed_pages_[page].get(), slot); + if (width_ == 4) return wide_pages_[page][slot]; + if (width_ == 2) return narrow_pages_[page][slot]; + return byte_pages_[page][slot]; + } + void set(size_t index, uint32_t value) noexcept { + const size_t page = index / kPageCodes, slot = index % kPageCodes; + if (width_ == 0) set_packed(packed_pages_[page].get(), slot, value); + else if (width_ == 4) wide_pages_[page][slot] = value; + else if (width_ == 2) + narrow_pages_[page][slot] = static_cast(value); + else byte_pages_[page][slot] = static_cast(value); + } + + size_t common_suffix(size_t left, size_t right) const noexcept { + const size_t available = std::min(left, right); + if (width_ == 0) + return common_suffix_packed(left, right, available); + if (width_ == 4) + return common_suffix_pages(wide_pages_, left, right, available); + if (width_ == 2) + return common_suffix_pages(narrow_pages_, left, right, available); + return common_suffix_pages(byte_pages_, left, right, available); + } + + template + Pages prepare() const { + const size_t active_pages = width_ == 0 ? packed_pages_.size() + : width_ == 4 ? wide_pages_.size() + : width_ == 2 ? narrow_pages_.size() + : byte_pages_.size(); + Pages result; + result.reserve(active_pages); + for (size_t page = 0; page < active_pages; ++page) + result.push_back(std::make_unique(kPageCodes)); + for (size_t index = 0; index < live_size_; ++index) + result[index / kPageCodes][index % kPageCodes] = + static_cast(get(index)); + return result; + } + + template + Pages prepare_promotion_append() const { + const size_t required_pages = + (live_size_ + 1 + kPageCodes - 1) / kPageCodes; + Pages result; + result.reserve(required_pages); + for (size_t page = 0; page < required_pages; ++page) + result.push_back(std::make_unique(kPageCodes)); + for (size_t index = 0; index < live_size_; ++index) + result[index / kPageCodes][index % kPageCodes] = + static_cast(get(index)); + return result; + } + + void commit(Pages values) noexcept { + byte_pages_.swap(values); + Pages().swap(packed_pages_); + Pages().swap(narrow_pages_); + Pages().swap(wide_pages_); + width_ = 1; + } + + void commit(Pages values) noexcept { + wide_pages_.swap(values); + Pages().swap(packed_pages_); + Pages().swap(narrow_pages_); + Pages().swap(byte_pages_); + width_ = 4; + } + + void commit(Pages values) noexcept { + narrow_pages_.swap(values); + Pages().swap(packed_pages_); + Pages().swap(wide_pages_); + Pages().swap(byte_pages_); + width_ = 2; + } + + void reset() noexcept { + Pages().swap(packed_pages_); + Pages().swap(byte_pages_); + Pages().swap(narrow_pages_); + Pages().swap(wide_pages_); + width_ = 0; + live_size_ = 0; + } + + size_t storage_bytes() const { + size_t bytes = checked_product(packed_pages_.capacity(), + sizeof(std::unique_ptr)); + checked_add(bytes, checked_product(byte_pages_.capacity(), + sizeof(std::unique_ptr))); + checked_add(bytes, checked_product(narrow_pages_.capacity(), + sizeof(std::unique_ptr))); + checked_add(bytes, checked_product(wide_pages_.capacity(), + sizeof(std::unique_ptr))); + checked_add(bytes, + checked_product(packed_pages_.size(), kPackedPageBytes)); + checked_add(bytes, checked_product(byte_pages_.size(), kPageCodes)); + checked_add(bytes, checked_product(narrow_pages_.size(), + kPageCodes * sizeof(uint16_t))); + checked_add(bytes, checked_product(wide_pages_.size(), + kPageCodes * sizeof(uint32_t))); + return bytes; + } + + private: + static uint32_t get_packed(const uint8_t *values, size_t index) noexcept { + const uint8_t byte = values[index >> 1]; + return (index & 1u) == 0 ? byte & 0x0fu : byte >> 4; + } + + static void set_packed(uint8_t *values, size_t index, + uint32_t value) noexcept { + uint8_t &byte = values[index >> 1]; + if ((index & 1u) == 0) + byte = static_cast((byte & 0xf0u) | value); + else + byte = static_cast((byte & 0x0fu) | (value << 4)); + } + + void ensure_packed_page(size_t page) { + if (page < packed_pages_.size()) return; + if (page != packed_pages_.size()) + throw std::runtime_error("history page position is out of range"); + auto payload = std::make_unique(kPackedPageBytes); + packed_pages_.push_back(std::move(payload)); + } + + static uint64_t load_little_u64(const uint8_t *data) noexcept { + uint64_t value; + std::memcpy(&value, data, sizeof(value)); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + value = __builtin_bswap64(value); +#endif + return value; + } + + static uint64_t packed_word(const uint8_t *page, + size_t start) noexcept { + const size_t byte = start >> 1; + const uint64_t low = load_little_u64(page + byte); + if ((start & 1u) == 0) return low; + return (low >> 4) | + (static_cast(page[byte + 8] & 0x0fu) << 60); + } + + size_t common_suffix_packed(size_t left, size_t right, + size_t available) const noexcept { + static constexpr size_t kChunkCodes = 16; + if (left == right) return available; + size_t matched = 0; + // Random streams almost always differ on their newest code. Avoid two + // unaligned word loads and normalization in that overwhelmingly common + // case, while retaining the word path for repetitive suffixes. + const size_t scalar_prefix = std::min(available, size_t{4}); + while (matched < scalar_prefix) { + if (get(left - matched - 1) != get(right - matched - 1)) + return matched; + ++matched; + } + while (matched < available) { + const size_t left_end = left - matched; + const size_t right_end = right - matched; + const size_t contiguous = std::min( + {available - matched, (left_end - 1) % kPageCodes + 1, + (right_end - 1) % kPageCodes + 1}); + if (contiguous >= kChunkCodes) { + const size_t left_start = left_end - kChunkCodes; + const size_t right_start = right_end - kChunkCodes; + const uint64_t difference = + packed_word(packed_pages_[left_start / kPageCodes].get(), + left_start % kPageCodes) ^ + packed_word(packed_pages_[right_start / kPageCodes].get(), + right_start % kPageCodes); + if (difference == 0) { + matched += kChunkCodes; + continue; + } + return matched + static_cast(__builtin_clzll(difference) / 4); + } + size_t edge = contiguous; + while (edge != 0) { + if (get(left - matched - 1) != get(right - matched - 1)) + return matched; + ++matched; + --edge; + } + } + return matched; + } + + template + static void ensure_page(Pages &pages, size_t page) { + if (page < pages.size()) + return; + if (page != pages.size()) + throw std::runtime_error("history page position is out of range"); + auto payload = std::make_unique(kPageCodes); + pages.push_back(std::move(payload)); + } + + template + static size_t common_suffix_pages(const Pages &pages, size_t left, + size_t right, + size_t available) noexcept { + size_t matched = 0; + while (matched < available) { + const size_t left_end = left - matched; + const size_t right_end = right - matched; + const size_t contiguous = std::min( + {available - matched, (left_end - 1) % kPageCodes + 1, + (right_end - 1) % kPageCodes + 1}); + const Value *left_data = + pages[(left_end - 1) / kPageCodes].get() + + (left_end % kPageCodes == 0 ? kPageCodes : left_end % kPageCodes) - + contiguous; + const Value *right_data = + pages[(right_end - 1) / kPageCodes].get() + + (right_end % kPageCodes == 0 ? kPageCodes : right_end % kPageCodes) - + contiguous; + if (std::memcmp(left_data, right_data, + contiguous * sizeof(Value)) == 0) { + matched += contiguous; + continue; + } + while (matched < available && + pages[(left - matched - 1) / kPageCodes] + [(left - matched - 1) % kPageCodes] == + pages[(right - matched - 1) / kPageCodes] + [(right - matched - 1) % kPageCodes]) + ++matched; + return matched; + } + return matched; + } + + Pages packed_pages_; + Pages byte_pages_; + Pages narrow_pages_; + Pages wide_pages_; + size_t live_size_ = 0; + uint32_t width_ = 0; + }; + + // BWT, PA and LCS share one blocked rank space. Leaves are stable arena + // entries and a weighted B+ directory (fanout 16) owns their order. Every + // directory node carries the exact aggregate needed by rank/LCS/PA queries; + // the linked leaves are used only by snapshot and run_count. + class UnifiedSequence { + public: + // Scratch remains sized for the largest route. Logical leaf geometry is + // selected once from PA width by the constructor below. + static constexpr uint32_t kMaxLeafCapacity = 2048; + static constexpr uint32_t kFanout = 16; + static constexpr uint32_t kNil = std::numeric_limits::max(); + + UnifiedSequence() = default; + explicit UnifiedSequence(size_t capacity) + : capacity_(capacity), maximum_pa_bits_(pa_bit_width(capacity - 1)), + pa_bits_(1), + leaf_limit_(maximum_pa_bits_ <= 20 ? 256u : 2048u), + split_left_size_(leaf_limit_ / 2), + split_right_size_(split_left_size_ + 1), + split_leaf_capacity_(maximum_pa_bits_ <= 20 ? 136u : 1088u), + leaf_growth_(maximum_pa_bits_ <= 20 ? 32u : 256u), + lcs_width_(capacity - 1 > 65535 ? 4u : 2u) { + // Arena capacity is deliberately unrelated to the logical sequence + // limit. prepare_insert grows both index-addressed arenas before commit. + leaves_.reserve(2); + nodes_.reserve(2); + pending_nodes_.reserve(4); + auto initial_leaf = make_leaf(0, leaf_limit_ / 2); + leaves_.push_back(std::move(*initial_leaf)); + Leaf &leaf = leaves_[0]; + leaf.size = 1; + set_pa_bits(leaf, 0, 0); + const uint32_t initial_lcs = 0; + encode_lcs_into(&initial_lcs, 1, leaf.lcs, leaf.payload_capacity); + sentinel_leaf_ = 0; + sentinel_slot_ = 0; + refresh(leaf); + first_leaf_ = last_leaf_ = root_ = 0; + root_is_leaf_ = true; + size_ = 1; + } + + UnifiedSequence(UnifiedSequence &&) noexcept = default; + UnifiedSequence &operator=(UnifiedSequence &&) noexcept = default; + UnifiedSequence(const UnifiedSequence &) = delete; + UnifiedSequence &operator=(const UnifiedSequence &) = delete; + + size_t size() const noexcept { return size_; } + uint32_t code_width() const noexcept { return code_width_; } + + uint32_t pa(size_t rank) const noexcept { + const Location location = locate(rank); + return get_pa(leaves_[location.leaf], location.slot); + } + uint32_t lcs(size_t rank) const noexcept { + const Location location = locate(rank); + return get_lcs(leaves_[location.leaf], location.slot); + } + + uint32_t rank(uint32_t code) const noexcept { + return rank_prefix(prefix(sentinel_leaf_) + sentinel_slot_, code); + } + + // PA width follows the largest live endpoint rather than the configured + // capacity. Preparing every replacement first makes the representation + // change transactional; the following commit is allocation-free. The + // sentinel remains physically implicit, while get_pa() supplies its + // logical endpoint during repack. + void promote_pa_for_endpoint(uint32_t endpoint) { + const uint32_t width = pa_bit_width(endpoint); + if (width <= pa_bits_) + return; + if (width > maximum_pa_bits_) + throw std::runtime_error("PA endpoint exceeds configured capacity"); + if (pending_left_payload_ || pending_leaf_ || !pending_nodes_.empty() || + pending_lcs_active_ || pending_successor_lcs_active_ || + pending_raw8_in_place_ || pending_raw8_successor_in_place_) + throw std::runtime_error("PA promotion requires no pending insertion"); + + std::vector> payloads; + payloads.reserve(leaves_.size()); + for (uint32_t leaf_index = 0; leaf_index < leaves_.size(); ++leaf_index) { + const Leaf &leaf = leaves_[leaf_index]; + const size_t bytes = pa_storage_bytes(leaf.payload_capacity, width); + auto payload = std::make_unique(bytes); + std::memset(payload.get(), 0, bytes); + for (uint32_t slot = 0; slot < leaf.size; ++slot) { + const uint32_t value = get_pa(leaf, slot); + if (leaf_index != sentinel_leaf_ || slot != sentinel_slot_) + set_pa_bits(payload.get(), slot, value, width); + } + payloads.push_back(std::move(payload)); + } + for (size_t index = 0; index < leaves_.size(); ++index) + leaves_[index].pa.swap(payloads[index]); + pa_bits_ = width; + } + + void prepare_insert(size_t rank, uint32_t replacement_code, uint32_t x, + bool has_successor, uint32_t y) { + pending_left_payload_.reset(); + pending_leaf_.reset(); + pending_lcs_active_ = false; + pending_successor_lcs_active_ = false; + pending_raw8_in_place_ = false; + pending_raw8_successor_in_place_ = false; + pending_successor_leaf_ = kNil; + pending_nodes_.clear(); + if (rank > size_ || size_ >= capacity_) + throw std::runtime_error("unified insertion position is out of range"); + Location location = rank == size_ ? locate(size_ - 1) : locate(rank); + if (rank == size_) + location.slot = leaves_[location.leaf].size; + const bool splits_leaf = leaves_[location.leaf].size == leaf_limit_; + size_t node_count = 0; + if (splits_leaf) { + if (root_is_leaf_) { + node_count = 1; + } else { + uint32_t parent = leaves_[location.leaf].parent; + while (parent != kNil && nodes_[parent].count == kFanout) { + ++node_count; + parent = nodes_[parent].parent; + } + if (parent == kNil) + ++node_count; + } + } + // Materialize only the exact chunks needed by the pending commit. The + // objects already stored in prior chunks never move; indices remain the + // arena authority throughout preparation and promotion. + // No Leaf/Node reference is retained across these reserves. Indices are + // the arena authority and remain valid when vector storage relocates. + ensure_arena_capacity(leaves_, leaves_.size() + (splits_leaf ? 1 : 0)); + ensure_arena_capacity(nodes_, nodes_.size() + node_count); + if (pending_nodes_.capacity() < node_count) + pending_nodes_.reserve(node_count); + const size_t histogram_capacity = root_histogram_size() + 1; + reserve_leaf_histogram( + sentinel_leaf_, + leaf_histogram_size(leaves_[sentinel_leaf_], code_width_) + 1); + reserve_leaf_histogram( + location.leaf, + leaf_histogram_size(leaves_[location.leaf], code_width_) + 1); + reserve_path(sentinel_leaf_, histogram_capacity); + reserve_path(location.leaf, histogram_capacity); + if (rank < size_) { + const Location successor = locate(rank); + reserve_leaf_histogram( + successor.leaf, + leaf_histogram_size(leaves_[successor.leaf], code_width_) + 1); + reserve_path(successor.leaf, histogram_capacity); + } + const bool adds_code = + leaf_histogram_count(leaves_[sentinel_leaf_], replacement_code, + code_width_) == 0; + (void)adds_code; // capacity above covers the one possible new symbol. + Leaf &target = leaves_[location.leaf]; + const uint32_t required_width = + replacement_code > std::numeric_limits::max() + ? 4u + : (replacement_code > std::numeric_limits::max() + ? 2u + : (replacement_code > 15u ? 1u : 0u)); + const uint32_t target_width = std::max(code_width_, required_width); + if (target.size == leaf_limit_) { + // Both halves replace physical payloads at the split commit. Keeping + // a small insertion runway avoids an immediate reallocation while + // still releasing the full leaf's old payload. + pending_left_payload_ = make_leaf(target_width, split_leaf_capacity_); + pending_leaf_ = make_leaf(target_width, split_leaf_capacity_); + reserve_leaf_histogram( + *pending_leaf_, leaf_histogram_size(target, code_width_) + 1, + target_width); + for (size_t index = 0; index < node_count; ++index) { + auto node = std::make_unique(); + histogram_reserve(node->histogram, histogram_capacity, code_width_); + pending_nodes_.push_back(std::move(node)); + } + } else if (target.size == target.payload_capacity) { + const uint32_t grown_capacity = + std::min(leaf_limit_, target.payload_capacity + leaf_growth_); + pending_left_payload_ = make_leaf(target_width, grown_capacity); + copy_payload(target, *pending_left_payload_); + } + + Location successor{kNil, 0}; + if (has_successor) + successor = locate(rank); + const bool changes_payload = + target.size == leaf_limit_ || target.size == target.payload_capacity; + const bool target_is_packed4 = target.lcs.kind == LcsKind::Packed4; + const bool target_is_raw8 = target.lcs.kind == LcsKind::Raw8; + bool raw8_can_shrink = target_is_raw8 && x <= 15u; + if (raw8_can_shrink && has_successor && successor.leaf == location.leaf) + raw8_can_shrink = y <= 15u; + if (raw8_can_shrink) { + for (uint32_t slot = 0; slot < target.size; ++slot) { + const uint32_t value = + has_successor && successor.leaf == location.leaf && + slot == successor.slot + ? y + : target.lcs.payload[slot]; + if (value > 15u) { + raw8_can_shrink = false; + break; + } + } + } + bool successor_raw8_can_shrink = + has_successor && successor.leaf != location.leaf && y <= 15u && + leaves_[successor.leaf].lcs.kind == LcsKind::Raw8; + if (successor_raw8_can_shrink) { + const Leaf &successor_leaf = leaves_[successor.leaf]; + for (uint32_t slot = 0; slot < successor_leaf.size; ++slot) { + const uint32_t value = slot == successor.slot + ? y + : successor_leaf.lcs.payload[slot]; + if (value > 15u) { + successor_raw8_can_shrink = false; + break; + } + } + } + const bool target_stays_mutable = + !changes_payload && + ((target_is_packed4 && x <= 15u && + (!has_successor || successor.leaf != location.leaf || y <= 15u)) || + (target_is_raw8 && x <= 255u && + (!has_successor || successor.leaf != location.leaf || y <= 255u) && + !raw8_can_shrink)); + const bool successor_stays_mutable = + !has_successor || successor.leaf == location.leaf || + (leaves_[successor.leaf].lcs.kind == LcsKind::Packed4 + ? y <= 15u + : leaves_[successor.leaf].lcs.kind == LcsKind::Raw8 && + y <= 255u && !successor_raw8_can_shrink); + if (target_stays_mutable && successor_stays_mutable) { + // PACKED4 and RAW8 payloads are physically sized to leaf capacity, so + // insertion and the optional point update need no allocation. Growth, + // split and any value outside the active codec are staged below. + pending_raw8_in_place_ = true; + pending_raw8_successor_in_place_ = + has_successor && successor.leaf != location.leaf; + pending_successor_leaf_ = + pending_raw8_successor_in_place_ ? successor.leaf : kNil; + pending_successor_slot_ = successor.slot; + return; + } + + // Decode only the leaves changed by this insertion. The replacement + // codecs are fully allocated before any sequence state is committed. + std::array old_values{}; + decode_lcs(target, old_values.data()); + std::array values{}; + for (uint32_t out = 0; out <= target.size; ++out) { + if (out == location.slot) + values[out] = x; + else + values[out] = old_values[out - (out > location.slot ? 1u : 0u)]; + } + if (has_successor) { + if (successor.leaf == location.leaf) + values[successor.slot + (successor.slot >= location.slot ? 1u : 0u)] = y; + } + if (target.size == leaf_limit_) { + encode_lcs_into(values.data(), split_left_size_, staging_lcs_, + pending_left_payload_->payload_capacity); + pending_lcs_active_ = true; + // A split's new right leaf has no old codec to recycle. Encode its + // one-off payload directly into the already-prepared Leaf. + encode_lcs_into(values.data() + split_left_size_, split_right_size_, + pending_leaf_->lcs, + pending_leaf_->payload_capacity); + } else { + const uint32_t physical_capacity = pending_left_payload_ + ? pending_left_payload_->payload_capacity + : target.payload_capacity; + encode_lcs_into(values.data(), target.size + 1, staging_lcs_, + physical_capacity); + pending_lcs_active_ = true; + } + if (has_successor && successor.leaf != location.leaf) { + const Leaf &successor_leaf = leaves_[successor.leaf]; + std::array successor_values{}; + decode_lcs(successor_leaf, successor_values.data()); + successor_values[successor.slot] = y; + encode_lcs_into(successor_values.data(), successor_leaf.size, + staging_successor_lcs_, + successor_leaf.payload_capacity); + pending_successor_lcs_active_ = true; + pending_successor_leaf_ = successor.leaf; + } + } + + std::vector> prepare_codes16() const { + std::vector> result; + result.reserve(leaves_.size()); + for (const Leaf &leaf : leaves_) { + auto values = std::make_unique(leaf.payload_capacity); + for (uint32_t slot = 0; slot < leaf.size; ++slot) + values[slot] = static_cast(get_code(leaf, slot)); + result.push_back(std::move(values)); + } + return result; + } + + std::vector> prepare_codes8() const { + std::vector> result; + result.reserve(leaves_.size()); + for (const Leaf &leaf : leaves_) { + auto values = std::make_unique(leaf.payload_capacity); + if (code_width_ == 0) { + const uint32_t pairs = leaf.size >> 1; + for (uint32_t pair = 0; pair < pairs; ++pair) { + const uint8_t packed = leaf.codes4[pair]; + values[pair * 2] = packed & 0x0fu; + values[pair * 2 + 1] = packed >> 4; + } + if ((leaf.size & 1u) != 0) + values[leaf.size - 1] = leaf.codes4[leaf.size >> 1] & 0x0fu; + } else { + std::memcpy(values.get(), leaf.codes8.get(), leaf.size); + } + result.push_back(std::move(values)); + } + return result; + } + + std::vector> prepare_codes32() const { + std::vector> result; + result.reserve(leaves_.size()); + for (const Leaf &leaf : leaves_) { + auto values = std::make_unique(leaf.payload_capacity); + for (uint32_t slot = 0; slot < leaf.size; ++slot) + values[slot] = get_code(leaf, slot); + result.push_back(std::move(values)); + } + return result; + } + + struct HistogramRepack { + std::vector> leaves; + std::vector> nodes; + std::vector> pending_nodes; + std::vector pending_leaf; + std::vector pending_left; + bool has_pending_leaf = false; + bool has_pending_left = false; + bool repack_leaves = false; + }; + + HistogramRepack prepare_histograms(uint32_t width) const { + HistogramRepack result; + result.repack_leaves = width > 1; + if (result.repack_leaves) { + result.leaves.reserve(leaves_.size()); + for (const Leaf &leaf : leaves_) + result.leaves.push_back( + repack_leaf_histogram(leaf, code_width_, width)); + } + result.nodes.reserve(nodes_.size()); + for (const Node &node : nodes_) + result.nodes.push_back(repack_histogram(node.histogram, code_width_, + width)); + result.pending_nodes.reserve(pending_nodes_.size()); + for (const auto &node : pending_nodes_) + result.pending_nodes.push_back(repack_histogram( + node->histogram, code_width_, width)); + if (pending_leaf_ && result.repack_leaves) { + result.pending_leaf = repack_leaf_histogram( + *pending_leaf_, code_width_, width); + result.has_pending_leaf = true; + } + if (pending_left_payload_ && result.repack_leaves) { + result.pending_left = repack_leaf_histogram( + *pending_left_payload_, code_width_, width); + result.has_pending_left = true; + } + return result; + } + + void commit_histograms(HistogramRepack values) noexcept { + if (values.repack_leaves) + for (size_t index = 0; index < leaves_.size(); ++index) + commit_leaf_histogram(leaves_[index], + std::move(values.leaves[index])); + for (size_t index = 0; index < nodes_.size(); ++index) + nodes_[index].histogram.swap(values.nodes[index]); + for (size_t index = 0; index < pending_nodes_.size(); ++index) + pending_nodes_[index]->histogram.swap(values.pending_nodes[index]); + if (values.has_pending_leaf) + commit_leaf_histogram(*pending_leaf_, std::move(values.pending_leaf)); + if (values.has_pending_left) + commit_leaf_histogram(*pending_left_payload_, + std::move(values.pending_left)); + } + + void commit_codes16(std::vector> values, + HistogramRepack histograms) noexcept { + for (size_t index = 0; index < leaves_.size(); ++index) { + leaves_[index].codes16 = std::move(values[index]); + leaves_[index].codes4.reset(); + leaves_[index].codes8.reset(); + leaves_[index].codes32.reset(); + } + commit_histograms(std::move(histograms)); + code_width_ = 2; + } + + void commit_codes8(std::vector> values, + HistogramRepack histograms) noexcept { + for (size_t index = 0; index < leaves_.size(); ++index) { + leaves_[index].codes8 = std::move(values[index]); + leaves_[index].codes4.reset(); + leaves_[index].codes16.reset(); + leaves_[index].codes32.reset(); + } + commit_histograms(std::move(histograms)); + code_width_ = 1; + } + + void commit_codes32(std::vector> values, + HistogramRepack histograms) noexcept { + for (size_t index = 0; index < leaves_.size(); ++index) { + leaves_[index].codes32 = std::move(values[index]); + leaves_[index].codes4.reset(); + leaves_[index].codes8.reset(); + leaves_[index].codes16.reset(); + } + commit_histograms(std::move(histograms)); + code_width_ = 4; + } + + // Allocation-free after prepare_insert(). The old sentinel is first + // replaced by code, then the new aligned sentinel/PA/LCS row is inserted. + void replace_and_insert(uint32_t code, size_t rank, uint32_t endpoint, + uint32_t x, bool has_successor, + uint32_t y) noexcept { + const uint32_t replaced_index = sentinel_leaf_; + Location location = rank == size_ ? locate(size_ - 1) : locate(rank); + if (rank == size_) + location.slot = leaves_[location.leaf].size; + + const uint32_t left_index = location.leaf; + + // The overwhelmingly common path does not split. Shifting rows does + // not change any aggregate: the former sentinel contributes exactly + // one `code`, while the inserted sentinel contributes no histogram + // entry. Update those deltas directly and rebuild only the tiny + // (fanout 16) extrema summaries on each distinct ancestor path. + if (leaves_[location.leaf].size < leaf_limit_) { + Leaf &left = leaves_[left_index]; + const bool mutable_lcs_in_place = pending_raw8_in_place_; + if (pending_left_payload_) { + commit_payload(left, std::move(pending_left_payload_)); + } + uint32_t old_successor_lcs = 0; + if (mutable_lcs_in_place && has_successor) { + old_successor_lcs = pending_raw8_successor_in_place_ + ? decode_lcs_value( + leaves_[pending_successor_leaf_], + pending_successor_slot_) + : decode_lcs_value(left, location.slot); + } + if (mutable_lcs_in_place) { + move_mutable_lcs_right(left, location.slot, + left.size - location.slot); + set_mutable_lcs(left, location.slot, x); + if (has_successor && !pending_raw8_successor_in_place_) + set_mutable_lcs(left, location.slot + 1, y); + if (pending_raw8_successor_in_place_) + set_mutable_lcs(leaves_[pending_successor_leaf_], + pending_successor_slot_, y); + } else { + swap_lcs(left.lcs, staging_lcs_); + pending_lcs_active_ = false; + } + if (pending_successor_lcs_active_) { + swap_lcs(leaves_[pending_successor_leaf_].lcs, + staging_successor_lcs_); + pending_successor_lcs_active_ = false; + } + // The current sentinel endpoint is implicit. Materialize it before + // this physical slot is shifted and becomes an ordinary PA row. + set_pa_bits(leaves_[replaced_index], sentinel_slot_, + static_cast(size_ - 1)); + set_code(leaves_[replaced_index], sentinel_slot_, code); + increment_leaf_histogram(leaves_[replaced_index], code, code_width_); + increment_histogram_up(leaves_[replaced_index].parent, code); + + insert_local(left, location.slot, 0, 0, x); + add_max(endpoint, left.max1_pa, left.max2_pa); + increment_weight_up(left_index); + sentinel_leaf_ = left_index; + sentinel_slot_ = location.slot; + ++size_; + + uint32_t changed[2] = {left_index, kNil}; + uint32_t changed_count = 1; + if (mutable_lcs_in_place) { + // The local multiset replaces old_successor with {x, y}; by the + // adjacent-LCP identity min(x, y) == old_successor, its minimum + // cannot increase. Update the exact count without scanning. + if (has_successor && !pending_raw8_successor_in_place_) { + if (old_successor_lcs == left.min_lcs) + --left.min_lcs_count; + add_leaf_lcs(left, y); + } + add_leaf_lcs(left, x); + if (pending_raw8_successor_in_place_) { + Leaf &successor_leaf = leaves_[pending_successor_leaf_]; + if (old_successor_lcs == successor_leaf.min_lcs) { + if (successor_leaf.min_lcs_count > 1) { + --successor_leaf.min_lcs_count; + add_leaf_lcs(successor_leaf, y); + } else if (y <= old_successor_lcs) { + successor_leaf.min_lcs = y; + successor_leaf.min_lcs_count = 1; + } else { + // This geometry is not produced by locate(rank) today, but + // keep the aggregate exact if leaf-boundary policy changes. + recompute_leaf_lcs(successor_leaf); + } + } + if (pending_successor_leaf_ != left_index) + changed[changed_count++] = pending_successor_leaf_; + } + pending_raw8_in_place_ = false; + pending_raw8_successor_in_place_ = false; + } else if (has_successor) { + const Location successor = locate(rank + 1); + Leaf &successor_leaf = leaves_[successor.leaf]; + // The replacement codec is already committed; derive the exact + // aggregate from it rather than retaining stale raw-slot metadata. + recompute_leaf_lcs(successor_leaf); + if (successor.leaf != left_index) + changed[changed_count++] = successor.leaf; + } + if (!mutable_lcs_in_place) { + // Insertion changed a staged codec wholesale; derive its exact + // aggregate from decoded values rather than stale raw slots. + recompute_leaf_lcs(left); + } + refresh_extrema_paths(changed, changed_count); + return; + } + + set_pa_bits(leaves_[replaced_index], sentinel_slot_, + static_cast(size_ - 1)); + set_code(leaves_[replaced_index], sentinel_slot_, code); + refresh(leaves_[replaced_index]); + refresh_up(leaves_[replaced_index].parent); + + { + std::array codes, pas; + for (uint32_t out = 0; out <= leaf_limit_; ++out) { + if (out == location.slot) { + codes[out] = 0; + pas[out] = 0; + } else { + const uint32_t old = out - (out > location.slot ? 1u : 0u); + codes[out] = get_code(leaves_[left_index], old); + pas[out] = get_pa(leaves_[left_index], old); + } + } + std::unique_ptr prepared_left = + std::move(pending_left_payload_); + std::unique_ptr prepared = std::move(pending_leaf_); + const uint32_t right_index = static_cast(leaves_.size()); + leaves_.push_back(std::move(*prepared)); + // push_back may relocate the Leaf arena. Reacquire by authoritative + // index rather than retaining a reference across the operation. + Leaf &left = leaves_[left_index]; + Leaf &right = leaves_[right_index]; + right.parent = left.parent; + right.previous = left_index; + right.next = left.next; + if (left.next != kNil) + leaves_[left.next].previous = right_index; + else + last_leaf_ = right_index; + left.next = right_index; + commit_payload(left, std::move(prepared_left)); + swap_lcs(left.lcs, staging_lcs_); + pending_lcs_active_ = false; + write_leaf(left, codes.data(), pas.data(), nullptr, split_left_size_); + write_leaf(right, codes.data() + split_left_size_, + pas.data() + split_left_size_, nullptr, split_right_size_); + if (location.slot < split_left_size_) { + sentinel_leaf_ = left_index; + sentinel_slot_ = location.slot; + } else { + sentinel_leaf_ = right_index; + sentinel_slot_ = location.slot - split_left_size_; + } + // refresh() reads the current sentinel endpoint implicitly from + // size_, so publish the new logical size before rebuilding maxima. + ++size_; + refresh(left); + refresh(right); + insert_leaf_after(left_index, right_index); + } + // Once the sentinel moves, its former slot becomes an ordinary code. + // If insertion happened in another leaf, refresh that old leaf again; + // the pre-insertion refresh intentionally excluded the old sentinel. + if (replaced_index != left_index) { + refresh(leaves_[replaced_index]); + refresh_up(leaves_[replaced_index].parent); + } + if (has_successor) { + const Location successor = locate(rank + 1); + if (pending_successor_lcs_active_) { + swap_lcs(leaves_[pending_successor_leaf_].lcs, + staging_successor_lcs_); + pending_successor_lcs_active_ = false; + } + refresh(leaves_[successor.leaf]); + refresh_up(leaves_[successor.leaf].parent); + } + } + + size_t nearest_previous_lcs_less(size_t rank, + uint32_t threshold) const noexcept { + Location location = locate(rank); + for (;;) { + std::array values{}; + decode_lcs(leaves_[location.leaf], values.data()); + for (uint32_t slot = location.slot + 1; slot-- > 0;) + if (values[slot] < threshold) + return prefix(location.leaf) + slot; + const uint32_t candidate = previous_leaf_with_min(location.leaf, threshold); + if (candidate == kNil) + return 0; + location.leaf = candidate; + location.slot = leaves_[candidate].size - 1; + } + } + + size_t nearest_next_lcs_less(size_t rank, + uint32_t threshold) const noexcept { + if (rank >= size_) + return size_; + Location location = locate(rank); + for (;;) { + std::array values{}; + decode_lcs(leaves_[location.leaf], values.data()); + for (uint32_t slot = location.slot; slot < leaves_[location.leaf].size; ++slot) + if (values[slot] < threshold) + return prefix(location.leaf) + slot; + const uint32_t candidate = next_leaf_with_min(location.leaf, threshold); + if (candidate == kNil) + return size_; + location.leaf = candidate; + location.slot = 0; + } + } + + uint32_t range_max_excluding(size_t first, size_t last, + uint32_t excluded) const noexcept { + if (first >= last) + return 0; + return root_is_leaf_ + ? query_leaf_max(root_, first, last, 0, excluded) + : query_node_max(root_, first, last, 0, excluded); + } + + int64_t run_count() const noexcept { + int64_t runs = 0; + bool previous_sentinel = false; + uint32_t previous_code = 0; + size_t rank = 0; + for (uint32_t leaf_index = first_leaf_; leaf_index != kNil; + leaf_index = leaves_[leaf_index].next) { + const Leaf &leaf = leaves_[leaf_index]; + for (uint32_t slot = 0; slot < leaf.size; ++slot, ++rank) { + const bool sentinel = leaf_index == sentinel_leaf_ && + slot == sentinel_slot_; + const uint32_t code = get_code(leaf, slot); + if (rank == 0 || sentinel != previous_sentinel || + (!sentinel && code != previous_code)) ++runs; + previous_sentinel = sentinel; + previous_code = code; + } + } + return runs; + } + + void snapshot(const std::vector &values, bool identity_codes, + int64_t *pa_output, int64_t *lcs_output, + int64_t *bwt_output, bool *sentinel_output) const noexcept { + size_t output = 0; + for (uint32_t leaf_index = first_leaf_; leaf_index != kNil; + leaf_index = leaves_[leaf_index].next) { + const Leaf &leaf = leaves_[leaf_index]; + std::array lcs_values{}; + decode_lcs(leaf, lcs_values.data()); + for (uint32_t slot = 0; slot < leaf.size; ++slot, ++output) { + const bool sentinel = leaf_index == sentinel_leaf_ && + slot == sentinel_slot_; + pa_output[output] = get_pa(leaf, slot); + lcs_output[output] = lcs_values[slot]; + sentinel_output[output] = sentinel; + const uint32_t code = get_code(leaf, slot); + bwt_output[output] = + sentinel ? 0 : identity_codes ? static_cast(code) + : values[code]; + } + } + } + + size_t storage_bytes() const { + size_t bytes = checked_product(leaves_.capacity(), sizeof(Leaf)); + checked_add(bytes, checked_product(nodes_.capacity(), sizeof(Node))); + checked_add(bytes, checked_product(pending_nodes_.capacity(), + sizeof(std::unique_ptr))); + for (const Leaf &leaf : leaves_) { + checked_add(bytes, code_storage_bytes(leaf.payload_capacity, + code_width_)); + checked_add(bytes, pa_storage_bytes(leaf.payload_capacity)); + checked_add(bytes, leaf.lcs.payload.capacity()); + checked_add(bytes, leaf.lcs.checkpoints.capacity()); + checked_add(bytes, leaf.histogram_counts.capacity()); + checked_add(bytes, leaf.histogram.capacity()); + } + for (const Node &node : nodes_) { + checked_add(bytes, node.histogram.capacity()); + } + if (pending_leaf_) { + checked_add(bytes, sizeof(Leaf)); + checked_add(bytes, code_storage_bytes( + pending_leaf_->payload_capacity, + payload_code_width(*pending_leaf_))); + checked_add(bytes, pa_storage_bytes(pending_leaf_->payload_capacity)); + checked_add(bytes, pending_leaf_->lcs.payload.capacity()); + checked_add(bytes, pending_leaf_->lcs.checkpoints.capacity()); + checked_add(bytes, pending_leaf_->histogram_counts.capacity()); + checked_add(bytes, pending_leaf_->histogram.capacity()); + } + if (pending_left_payload_) { + checked_add(bytes, sizeof(Leaf)); + checked_add(bytes, code_storage_bytes( + pending_left_payload_->payload_capacity, + payload_code_width(*pending_left_payload_))); + checked_add(bytes, + pa_storage_bytes(pending_left_payload_->payload_capacity)); + checked_add(bytes, pending_left_payload_->lcs.payload.capacity()); + checked_add(bytes, pending_left_payload_->lcs.checkpoints.capacity()); + checked_add(bytes, + pending_left_payload_->histogram_counts.capacity()); + checked_add(bytes, pending_left_payload_->histogram.capacity()); + } + for (const auto &node : pending_nodes_) { + checked_add(bytes, sizeof(Node)); + checked_add(bytes, node->histogram.capacity()); + } + checked_add(bytes, staging_lcs_.payload.capacity()); + checked_add(bytes, staging_lcs_.checkpoints.capacity()); + checked_add(bytes, staging_successor_lcs_.payload.capacity()); + checked_add(bytes, staging_successor_lcs_.checkpoints.capacity()); + return bytes; + } + + std::array storage_components() const { + std::array result{}; // arenas, codes, PA, LCS, histograms + result[0] = checked_product(leaves_.capacity(), sizeof(Leaf)); + checked_add(result[0], checked_product(nodes_.capacity(), sizeof(Node))); + checked_add(result[0], checked_product(pending_nodes_.capacity(), + sizeof(std::unique_ptr))); + for (const Leaf &leaf : leaves_) { + checked_add(result[1], code_storage_bytes(leaf.payload_capacity, + code_width_)); + checked_add(result[2], pa_storage_bytes(leaf.payload_capacity)); + checked_add(result[3], leaf.lcs.payload.capacity()); + checked_add(result[3], leaf.lcs.checkpoints.capacity()); + checked_add(result[4], leaf.histogram_counts.capacity()); + checked_add(result[4], leaf.histogram.capacity()); + } + for (const Node &node : nodes_) + checked_add(result[4], node.histogram.capacity()); + checked_add(result[3], staging_lcs_.payload.capacity()); + checked_add(result[3], staging_lcs_.checkpoints.capacity()); + checked_add(result[3], staging_successor_lcs_.payload.capacity()); + checked_add(result[3], staging_successor_lcs_.checkpoints.capacity()); + return result; + } + + private: + enum class LcsKind : uint8_t { + Packed4, + Raw8, + Raw16, + Raw32, + For8, + Delta4, + Delta8 + }; + struct LcsCodec { + std::vector payload; + std::vector checkpoints; + uint32_t anchor = 0; + LcsKind kind = LcsKind::Raw8; + }; + struct Leaf { + std::unique_ptr codes4; + std::unique_ptr codes8; + std::unique_ptr codes16; + std::unique_ptr codes32; + std::unique_ptr pa; + LcsCodec lcs; + // Codes in the byte alphabet use one count byte per present code. Zero + // encodes 256 and a small inline table stores every exact excess. + std::array histogram_bitmap{}; + std::vector histogram_counts; + std::array histogram_overflows{}; + uint8_t histogram_overflow_size = 0; + std::vector histogram; + bool histogram_wide = false; + uint32_t size = 0; + uint32_t payload_capacity = 0; + uint32_t min_lcs = std::numeric_limits::max(); + uint32_t min_lcs_count = 0; + uint32_t max1_pa = 0, max2_pa = 0; + uint32_t parent = kNil, parent_slot = 0; + uint32_t previous = kNil, next = kNil; + }; + struct Node { + std::array children{}; + std::array weights{}; + std::vector histogram; + uint32_t parent = kNil, parent_slot = 0; + uint32_t count = 0, weight = 0; + uint32_t min_lcs = std::numeric_limits::max(); + uint32_t max1_pa = 0, max2_pa = 0; + bool children_are_leaves = false; + }; + struct Location { uint32_t leaf; uint32_t slot; }; + + template + static void ensure_arena_capacity(std::vector &arena, + size_t required) { + if (required <= arena.capacity()) + return; + size_t capacity = std::max(arena.capacity(), 2); + while (capacity < required) { + // Bound dead arena slots more tightly for long contexts. Leaf and + // node objects retain stable logical indices across relocation, so a + // 1.125x geometric factor trades a handful of rare moves for a much + // smaller worst-case owned-memory sawtooth. + const size_t grown = capacity + std::max(capacity / 8, 1); + if (grown <= capacity) { + capacity = required; + break; + } + capacity = grown; + } + arena.reserve(capacity); + } + + static uint32_t histogram_code_width(uint32_t width) noexcept { + return width <= 1 ? 1u : width; + } + static size_t histogram_record_width(uint32_t width) noexcept { + return static_cast(histogram_code_width(width)) + 4; + } + static size_t histogram_size(const std::vector &histogram, + uint32_t width) noexcept { + return histogram.size() / histogram_record_width(width); + } + static uint32_t load_little_endian_exact(const uint8_t *source, + uint32_t width) noexcept { + uint32_t value = 0; + std::memcpy(&value, source, width); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + value = __builtin_bswap32(value); +#endif + return value; + } + static void store_little_endian_exact(uint8_t *destination, + uint32_t value, + uint32_t width) noexcept { +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + value = __builtin_bswap32(value); +#endif + std::memcpy(destination, &value, width); + } + static uint32_t histogram_code(const std::vector &histogram, + size_t index, + uint32_t width) noexcept { + const uint32_t code_width = histogram_code_width(width); + return load_little_endian_exact( + histogram.data() + index * histogram_record_width(width), + code_width); + } + static uint32_t histogram_entry_count( + const std::vector &histogram, size_t index, + uint32_t width) noexcept { + const uint32_t code_width = histogram_code_width(width); + return load_little_endian_exact( + histogram.data() + index * histogram_record_width(width) + + code_width, + 4); + } + static void histogram_set_count(std::vector &histogram, + size_t index, uint32_t count, + uint32_t width) noexcept { + const uint32_t code_width = histogram_code_width(width); + store_little_endian_exact( + histogram.data() + index * histogram_record_width(width) + + code_width, + count, 4); + } + static void histogram_set(std::vector &histogram, size_t index, + uint32_t code, uint32_t count, + uint32_t width) noexcept { + const uint32_t code_width = histogram_code_width(width); + uint8_t *const record = + histogram.data() + index * histogram_record_width(width); + store_little_endian_exact(record, code, code_width); + store_little_endian_exact(record + code_width, count, 4); + } + static size_t histogram_lower_bound( + const std::vector &histogram, uint32_t code, + uint32_t width) noexcept { + const size_t entries = histogram_size(histogram, width); + // Identity-coded compact streams quickly form a dense low-ID prefix. + // Once present, the sorted entry index is the code itself and avoids + // four branchy unaligned loads on every B+ rank/update. + if (width <= 1 && code < entries && + histogram[code * histogram_record_width(width)] == code) + return code; + size_t first = 0; + size_t count = entries; + while (count != 0) { + const size_t step = count / 2; + const size_t middle = first + step; + if (histogram_code(histogram, middle, width) < code) { + first = middle + 1; + count -= step + 1; + } else { + count = step; + } + } + return first; + } + static void histogram_reserve(std::vector &histogram, + size_t entries, uint32_t width) { + const size_t bytes = checked_product(entries, + histogram_record_width(width)); + if (histogram.capacity() < bytes) + histogram.reserve(bytes); + } + static void histogram_insert(std::vector &histogram, + size_t index, uint32_t code, + uint32_t count, uint32_t width) noexcept { + const size_t record_width = histogram_record_width(width); + const size_t offset = index * record_width; + const size_t old_size = histogram.size(); + histogram.resize(old_size + record_width); + std::memmove(histogram.data() + offset + record_width, + histogram.data() + offset, old_size - offset); + histogram_set(histogram, index, code, count, width); + } + static std::vector repack_histogram( + const std::vector &histogram, uint32_t source_width, + uint32_t destination_width) { + const size_t source_record_width = histogram_record_width(source_width); + const size_t destination_record_width = + histogram_record_width(destination_width); + const size_t entries = histogram_size(histogram, source_width); + const size_t capacity_entries = + histogram.capacity() / source_record_width; + std::vector result; + result.reserve(checked_product(capacity_entries, + destination_record_width)); + result.resize(checked_product(entries, destination_record_width)); + for (size_t index = 0; index < entries; ++index) + histogram_set(result, index, + histogram_code(histogram, index, source_width), + histogram_entry_count(histogram, index, source_width), + destination_width); + return result; + } + + static uint32_t popcount64(uint64_t value) noexcept { + return static_cast(__builtin_popcountll(value)); + } + static size_t compact_histogram_rank(const Leaf &leaf, + uint32_t code) noexcept { + const uint32_t word = code >> 6; + size_t rank = 0; + for (uint32_t index = 0; index < word; ++index) + rank += popcount64(leaf.histogram_bitmap[index]); + const uint32_t bit = code & 63u; + const uint64_t before = bit == 0 ? 0 : ((uint64_t{1} << bit) - 1); + return rank + popcount64(leaf.histogram_bitmap[word] & before); + } + static bool compact_histogram_contains(const Leaf &leaf, + uint32_t code) noexcept { + return code <= 255u && + (leaf.histogram_bitmap[code >> 6] & + (uint64_t{1} << (code & 63u))) != 0; + } + static size_t leaf_histogram_size(const Leaf &leaf, + uint32_t width) noexcept { + if (leaf.histogram_wide) + return histogram_size(leaf.histogram, width); + size_t result = 0; + for (uint64_t word : leaf.histogram_bitmap) + result += popcount64(word); + return result; + } + static uint32_t leaf_histogram_count(const Leaf &leaf, uint32_t code, + uint32_t width) noexcept { + if (leaf.histogram_wide) + { + const size_t found = histogram_lower_bound(leaf.histogram, code, width); + return found != histogram_size(leaf.histogram, width) && + histogram_code(leaf.histogram, found, width) == code + ? histogram_entry_count(leaf.histogram, found, width) + : 0; + } + if (!compact_histogram_contains(leaf, code)) + return 0; + const uint8_t encoded = + leaf.histogram_counts[compact_histogram_rank(leaf, code)]; + if (encoded != 0) + return encoded; + for (uint32_t index = 0; index < leaf.histogram_overflow_size; ++index) { + const uint32_t overflow = leaf.histogram_overflows[index]; + if ((overflow & 255u) == code) + return 256u + (overflow >> 8); + } + return 256u; + } + static void reserve_leaf_histogram(Leaf &leaf, size_t entries, + uint32_t width) { + if (leaf.histogram_wide) { + histogram_reserve(leaf.histogram, entries, width); + } else if (leaf.histogram_counts.capacity() < entries) { + leaf.histogram_counts.reserve(std::min(256, (entries + 15) & ~size_t{15})); + } + } + static void clear_leaf_histogram(Leaf &leaf) noexcept { + if (leaf.histogram_wide) { + leaf.histogram.clear(); + return; + } + leaf.histogram_bitmap.fill(0); + leaf.histogram_counts.clear(); + leaf.histogram_overflow_size = 0; + } + static void increment_leaf_histogram(Leaf &leaf, uint32_t code, + uint32_t width) noexcept { + if (leaf.histogram_wide) { + const size_t found = histogram_lower_bound(leaf.histogram, code, width); + if (found != histogram_size(leaf.histogram, width) && + histogram_code(leaf.histogram, found, width) == code) { + histogram_set_count( + leaf.histogram, found, + histogram_entry_count(leaf.histogram, found, width) + 1u, + width); + } else { + histogram_insert(leaf.histogram, found, code, 1, width); + } + return; + } + // The width-promotion transaction converts all leaves before a code + // above 255 can become live. + if (code > 255u) + std::terminate(); + const size_t rank = compact_histogram_rank(leaf, code); + const uint64_t bit = uint64_t{1} << (code & 63u); + uint64_t &word = leaf.histogram_bitmap[code >> 6]; + if ((word & bit) == 0) { + const size_t old_size = leaf.histogram_counts.size(); + leaf.histogram_counts.resize(old_size + 1); + std::memmove(leaf.histogram_counts.data() + rank + 1, + leaf.histogram_counts.data() + rank, old_size - rank); + leaf.histogram_counts[rank] = 1; + word |= bit; + return; + } + uint8_t &encoded = leaf.histogram_counts[rank]; + if (encoded == 255) { + encoded = 0; + return; + } + if (encoded != 0) { + ++encoded; + return; + } + for (uint32_t index = 0; index < leaf.histogram_overflow_size; ++index) { + uint32_t &overflow = leaf.histogram_overflows[index]; + if ((overflow & 255u) == code) { + overflow += 1u << 8; + return; + } + } + if (leaf.histogram_overflow_size >= leaf.histogram_overflows.size()) + std::terminate(); + leaf.histogram_overflows[leaf.histogram_overflow_size++] = + code | (1u << 8); + } + static std::vector repack_leaf_histogram( + const Leaf &leaf, uint32_t source_width, uint32_t destination_width) { + if (leaf.histogram_wide) + return repack_histogram(leaf.histogram, source_width, + destination_width); + std::vector result; + const size_t entries = leaf_histogram_size(leaf, source_width); + result.reserve(checked_product( + std::max(entries, leaf.histogram_counts.capacity()), + histogram_record_width(destination_width))); + result.resize(checked_product(entries, + histogram_record_width(destination_width))); + size_t index = 0; + for (uint32_t word_index = 0; word_index < 4; ++word_index) { + uint64_t word = leaf.histogram_bitmap[word_index]; + while (word != 0) { + const uint32_t bit = static_cast(__builtin_ctzll(word)); + const uint32_t code = word_index * 64u + bit; + histogram_set(result, index++, code, + leaf_histogram_count(leaf, code, source_width), + destination_width); + word &= word - 1; + } + } + return result; + } + static void commit_leaf_histogram(Leaf &leaf, + std::vector wide) noexcept { + leaf.histogram.swap(wide); + std::vector().swap(leaf.histogram_counts); + leaf.histogram_bitmap.fill(0); + leaf.histogram_overflow_size = 0; + leaf.histogram_wide = true; + } + + static void store_bytes(uint8_t *destination, uint32_t value, + uint32_t width) noexcept { + for (uint32_t byte = 0; byte < width; ++byte) + destination[byte] = static_cast(value >> (byte * 8)); + } + static uint32_t load_bytes(const uint8_t *source, + uint32_t width) noexcept { + uint32_t value = 0; + for (uint32_t byte = 0; byte < width; ++byte) + value |= static_cast(source[byte]) << (byte * 8); + return value; + } + static bool zigzag_delta(uint32_t previous, uint32_t value, + uint32_t &encoded) noexcept { + const int64_t delta = static_cast(value) - previous; + const uint64_t zigzag = delta >= 0 + ? static_cast(delta) * 2 + : static_cast(-delta) * 2 - 1; + if (zigzag > std::numeric_limits::max()) + return false; + encoded = static_cast(zigzag); + return true; + } + static uint32_t undo_zigzag(uint32_t previous, + uint32_t encoded) noexcept { + const int64_t delta = (encoded & 1u) + ? -static_cast((encoded >> 1) + 1u) + : static_cast(encoded >> 1); + return static_cast(static_cast(previous) + delta); + } + void encode_lcs_into(const uint32_t *values, uint32_t count, + LcsCodec &codec, + uint32_t raw8_physical_capacity) const { + uint32_t raw_max = 0; + uint32_t minimum = count == 0 ? 0 : values[0]; + for (uint32_t slot = 0; slot < count; ++slot) { + raw_max = std::max(raw_max, values[slot]); + minimum = std::min(minimum, values[slot]); + } + const uint32_t raw_width = raw_max <= 255u + ? 1u + : (raw_max <= 65535u ? 2u : 4u); + const LcsKind raw_kind = raw_width == 1 + ? LcsKind::Raw8 + : (raw_width == 2 ? LcsKind::Raw16 + : LcsKind::Raw32); + const size_t raw_cost = static_cast(count) * raw_width; + const bool packed4_valid = raw_max <= 15u; + const size_t packed4_cost = (static_cast(count) + 1) / 2; + + const bool for_valid = raw_max - minimum <= 255u; + const size_t for_cost = count; + + bool delta4_valid = true; + bool delta8_valid = true; + size_t delta_count = 0; + for (uint32_t slot = 0; slot < count; ++slot) { + if ((slot & 15u) == 0) + continue; + uint32_t encoded = 0; + if (!zigzag_delta(values[slot - 1], values[slot], encoded) || + encoded > 255u) { + delta4_valid = false; + delta8_valid = false; + } else if (encoded > 15u) { + delta4_valid = false; + } + ++delta_count; + } + const size_t checkpoint_count = (count + 15u) / 16u; + const size_t checkpoint_cost = checkpoint_count * lcs_width_; + const size_t delta4_payload = (delta_count + 1) / 2; + const size_t delta4_cost = checkpoint_cost + delta4_payload; + const size_t delta8_cost = checkpoint_cost + delta_count; + + // Select by exact allocated payload size. RAW wins ties; FOR wins a tie + // with DELTA after it has already beaten RAW. + LcsKind kind = raw_kind; + size_t payload_size = raw_cost; + size_t best_cost = raw_cost; + if (packed4_valid && packed4_cost < best_cost) { + kind = LcsKind::Packed4; + payload_size = packed4_cost; + best_cost = packed4_cost; + } + // RAW8 is deliberately sticky: it enables allocation-free in-place + // mutation, and compressed alternatives save too little to repay a + // full leaf rebuild on the low-LCS random workload. + if (raw_width != 1) { + if (for_valid && for_cost < best_cost) { + kind = LcsKind::For8; + payload_size = for_cost; + best_cost = for_cost; + } + if (delta4_valid && delta4_cost < best_cost) { + kind = LcsKind::Delta4; + payload_size = delta4_payload; + best_cost = delta4_cost; + } else if (delta8_valid && delta8_cost < best_cost) { + kind = LcsKind::Delta8; + payload_size = delta_count; + best_cost = delta8_cost; + } + } + + // Mutable codecs keep vector size (not just allocation capacity) equal + // to their physical leaf payload, so insertion may touch the next byte + // without allocation. Selection above remains based on live bytes. + if (kind == LcsKind::Packed4) + payload_size = (static_cast(raw8_physical_capacity) + 1) / 2; + if (kind == LcsKind::Raw8) + payload_size = raw8_physical_capacity; + + // This is the only payload preparation: resize, clear, then pack the + // selected representation directly into the reusable destination. + // reserve(exact) avoids resize's geometric growth, keeping the three + // retained scratch capacities below the long-context memory budget. + if (kind == LcsKind::Packed4 && + codec.payload.capacity() != payload_size) { + std::vector exact_payload(payload_size); + codec.payload.swap(exact_payload); + } else if (codec.payload.capacity() < payload_size) { + codec.payload.reserve(payload_size); + } + codec.payload.resize(payload_size); + std::fill(codec.payload.begin(), codec.payload.end(), uint8_t{0}); + if (kind == LcsKind::Packed4) { + // Packed absolute nibbles have neither an anchor nor checkpoints; do + // not retain stale DELTA scratch in a live Packed4 leaf. + std::vector().swap(codec.checkpoints); + } else { + codec.checkpoints.clear(); + } + codec.anchor = kind == LcsKind::For8 ? minimum : 0; + codec.kind = kind; + if (kind == LcsKind::Packed4) { + for (uint32_t slot = 0; slot < count; ++slot) { + const uint32_t shift = (slot & 1u) * 4; + codec.payload[slot >> 1] |= + static_cast(values[slot] << shift); + } + } else if (kind == LcsKind::Raw8) { + for (uint32_t slot = 0; slot < count; ++slot) + codec.payload[slot] = static_cast(values[slot]); + } else if (kind == LcsKind::Raw16 || kind == LcsKind::Raw32) { + for (uint32_t slot = 0; slot < count; ++slot) + store_bytes(codec.payload.data() + static_cast(slot) * raw_width, + values[slot], raw_width); + } else if (kind == LcsKind::For8) { + for (uint32_t slot = 0; slot < count; ++slot) + codec.payload[slot] = static_cast(values[slot] - minimum); + } else { + const size_t checkpoint_bytes = checkpoint_count * lcs_width_; + if (codec.checkpoints.capacity() < checkpoint_bytes) + codec.checkpoints.reserve(checkpoint_bytes); + codec.checkpoints.resize(checkpoint_bytes); + size_t delta_index = 0; + for (uint32_t slot = 0; slot < count; ++slot) { + if ((slot & 15u) == 0) { + store_bytes(codec.checkpoints.data() + + static_cast(slot >> 4) * lcs_width_, + values[slot], lcs_width_); + } else { + uint32_t encoded = 0; + (void)zigzag_delta(values[slot - 1], values[slot], encoded); + if (kind == LcsKind::Delta4) { + uint8_t &byte = codec.payload[delta_index >> 1]; + if ((delta_index & 1u) == 0) + byte = static_cast(encoded); + else + byte |= static_cast(encoded << 4); + ++delta_index; + } else { + codec.payload[delta_index++] = static_cast(encoded); + } + } + } + } + } + static void swap_lcs(LcsCodec &left, LcsCodec &right) noexcept { + left.payload.swap(right.payload); + left.checkpoints.swap(right.checkpoints); + std::swap(left.anchor, right.anchor); + std::swap(left.kind, right.kind); + } + uint32_t decode_lcs_value(const Leaf &leaf, + uint32_t slot) const noexcept { + const LcsCodec &codec = leaf.lcs; + if (codec.kind == LcsKind::Packed4) + return (codec.payload[slot >> 1] >> ((slot & 1u) * 4)) & 15u; + if (codec.kind == LcsKind::Raw8) + return codec.payload[slot]; + if (codec.kind == LcsKind::Raw16) + return load_bytes(codec.payload.data() + static_cast(slot) * 2, + 2); + if (codec.kind == LcsKind::Raw32) + return load_bytes(codec.payload.data() + static_cast(slot) * 4, + 4); + if (codec.kind == LcsKind::For8) + return codec.anchor + codec.payload[slot]; + const uint32_t block = slot >> 4; + const uint32_t block_start = block << 4; + uint32_t value = load_bytes( + codec.checkpoints.data() + static_cast(block) * lcs_width_, + lcs_width_); + size_t delta_index = static_cast(block) * 15; + for (uint32_t current = block_start + 1; current <= slot; ++current) { + const uint32_t encoded = codec.kind == LcsKind::Delta4 + ? (codec.payload[delta_index >> 1] >> + ((delta_index & 1u) * 4)) & 15u + : codec.payload[delta_index]; + value = undo_zigzag(value, encoded); + ++delta_index; + } + return value; + } + void decode_lcs(const Leaf &leaf, uint32_t *values) const noexcept { + const LcsCodec &codec = leaf.lcs; + if (codec.kind == LcsKind::Packed4) { + const uint32_t pairs = leaf.size >> 1; + for (uint32_t pair = 0; pair < pairs; ++pair) { + const uint8_t packed = codec.payload[pair]; + values[pair * 2] = packed & 15u; + values[pair * 2 + 1] = packed >> 4; + } + if ((leaf.size & 1u) != 0) + values[leaf.size - 1] = codec.payload[leaf.size >> 1] & 15u; + return; + } + if (codec.kind == LcsKind::Raw8) { + for (uint32_t slot = 0; slot < leaf.size; ++slot) + values[slot] = codec.payload[slot]; + return; + } + if (codec.kind == LcsKind::Raw16 || codec.kind == LcsKind::Raw32) { + const uint32_t width = codec.kind == LcsKind::Raw16 ? 2u : 4u; + for (uint32_t slot = 0; slot < leaf.size; ++slot) + values[slot] = load_bytes( + codec.payload.data() + static_cast(slot) * width, width); + return; + } + if (codec.kind == LcsKind::For8) { + for (uint32_t slot = 0; slot < leaf.size; ++slot) + values[slot] = codec.anchor + codec.payload[slot]; + return; + } + size_t delta_index = 0; + for (uint32_t slot = 0; slot < leaf.size; ++slot) { + if ((slot & 15u) == 0) { + values[slot] = load_bytes( + codec.checkpoints.data() + + static_cast(slot >> 4) * lcs_width_, + lcs_width_); + continue; + } + const uint32_t encoded = codec.kind == LcsKind::Delta4 + ? (codec.payload[delta_index >> 1] >> + ((delta_index & 1u) * 4)) & 15u + : codec.payload[delta_index]; + values[slot] = undo_zigzag(values[slot - 1], encoded); + ++delta_index; + } + } + + static uint32_t payload_code_width(const Leaf &leaf) noexcept { + return leaf.codes32 ? 4u : (leaf.codes16 ? 2u : (leaf.codes8 ? 1u : 0u)); + } + static size_t code_storage_bytes(uint32_t capacity, + uint32_t width) { + if (width == 0) + return (static_cast(capacity) + 1) / 2; + return checked_product(capacity, width); + } + static uint32_t pa_bit_width(size_t maximum_endpoint) noexcept { + uint32_t bits = 1; + while (maximum_endpoint > 1) { + ++bits; + maximum_endpoint >>= 1; + } + return bits; + } + static size_t pa_payload_bytes(uint32_t payload_capacity, + uint32_t width) { + const size_t bits = checked_product(payload_capacity, width); + return bits / 8 + (bits % 8 != 0); + } + size_t pa_payload_bytes(uint32_t payload_capacity) const { + return pa_payload_bytes(payload_capacity, pa_bits_); + } + static size_t pa_storage_bytes(uint32_t payload_capacity, + uint32_t width) { + size_t bytes = pa_payload_bytes(payload_capacity, width); + checked_add(bytes, 8); + return bytes; + } + size_t pa_storage_bytes(uint32_t payload_capacity) const { + return pa_storage_bytes(payload_capacity, pa_bits_); + } + std::unique_ptr make_leaf(uint32_t width, + uint32_t payload_capacity) const { + auto result = std::make_unique(); + result->payload_capacity = payload_capacity; + result->histogram_wide = width > 1; + if (width == 4) + result->codes32 = std::make_unique(payload_capacity); + else if (width == 2) + result->codes16 = std::make_unique(payload_capacity); + else if (width == 1) + result->codes8 = std::make_unique(payload_capacity); + else + result->codes4 = std::make_unique( + code_storage_bytes(payload_capacity, 0)); + result->pa = + std::make_unique(pa_storage_bytes(payload_capacity)); + std::memset(result->pa.get(), 0, pa_storage_bytes(payload_capacity)); + return result; + } + void copy_payload(const Leaf &source, Leaf &destination) const noexcept { + const uint32_t destination_width = payload_code_width(destination); + if (destination_width == code_width_) { + if (code_width_ == 0) + std::memcpy(destination.codes4.get(), source.codes4.get(), + (static_cast(source.size) + 1) / 2); + else if (code_width_ == 1) + std::memcpy(destination.codes8.get(), source.codes8.get(), source.size); + else if (code_width_ == 2) + std::memcpy(destination.codes16.get(), source.codes16.get(), + static_cast(source.size) * 2); + else + std::memcpy(destination.codes32.get(), source.codes32.get(), + static_cast(source.size) * 4); + } else if (code_width_ == 0 && destination_width == 1) { + const uint32_t pairs = source.size >> 1; + for (uint32_t pair = 0; pair < pairs; ++pair) { + const uint8_t packed = source.codes4[pair]; + destination.codes8[pair * 2] = packed & 0x0fu; + destination.codes8[pair * 2 + 1] = packed >> 4; + } + if ((source.size & 1u) != 0) + destination.codes8[source.size - 1] = + source.codes4[source.size >> 1] & 0x0fu; + } else { + // Width-changing growth is a rare transactional staging operation. + for (uint32_t slot = 0; slot < source.size; ++slot) { + const uint32_t code = get_code(source, slot); + if (destination.codes32) + destination.codes32[slot] = code; + else if (destination.codes16) + destination.codes16[slot] = static_cast(code); + else + destination.codes8[slot] = static_cast(code); + } + } + std::memcpy(destination.pa.get(), source.pa.get(), + pa_payload_bytes(source.payload_capacity)); + } + static void commit_payload(Leaf &leaf, + std::unique_ptr prepared) noexcept { + leaf.codes4.swap(prepared->codes4); + leaf.codes16.swap(prepared->codes16); + leaf.codes32.swap(prepared->codes32); + leaf.codes8.swap(prepared->codes8); + leaf.pa.swap(prepared->pa); + leaf.payload_capacity = prepared->payload_capacity; + } + uint32_t get_code(const Leaf &leaf, uint32_t slot) const noexcept { + if (code_width_ == 4) return leaf.codes32[slot]; + if (code_width_ == 2) return leaf.codes16[slot]; + if (code_width_ == 1) return leaf.codes8[slot]; + const uint8_t packed = leaf.codes4[slot >> 1]; + return (packed >> ((slot & 1u) * 4)) & 0x0fu; + } + void set_code(Leaf &leaf, uint32_t slot, uint32_t value) noexcept { + if (code_width_ == 4) leaf.codes32[slot] = value; + else if (code_width_ == 2) leaf.codes16[slot] = static_cast(value); + else if (code_width_ == 1) leaf.codes8[slot] = static_cast(value); + else { + uint8_t &packed = leaf.codes4[slot >> 1]; + const uint32_t shift = (slot & 1u) * 4; + packed = static_cast((packed & ~(0x0fu << shift)) | + ((value & 0x0fu) << shift)); + } + } + static uint64_t little_endian_window(const uint8_t *source) noexcept { + uint64_t value; + std::memcpy(&value, source, sizeof(value)); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + value = __builtin_bswap64(value); +#endif + return value; + } + static void store_little_endian_window(uint8_t *destination, + uint64_t value) noexcept { +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + value = __builtin_bswap64(value); +#endif + std::memcpy(destination, &value, sizeof(value)); + } + uint32_t get_pa_bits(const Leaf &leaf, uint32_t slot) const noexcept { + return get_pa_bits(leaf.pa.get(), slot, pa_bits_); + } + static uint32_t get_pa_bits(const uint8_t *payload, uint32_t slot, + uint32_t width) noexcept { + const size_t first_bit = static_cast(slot) * width; + const uint32_t bit_offset = static_cast(first_bit & 7u); + const uint64_t mask = (uint64_t{1} << width) - 1; + const uint64_t window = + little_endian_window(payload + (first_bit >> 3)); + return static_cast((window >> bit_offset) & mask); + } + uint32_t get_pa(const Leaf &leaf, uint32_t slot) const noexcept { + if (sentinel_leaf_ < leaves_.size() && + &leaf == &leaves_[sentinel_leaf_] && slot == sentinel_slot_) + return static_cast(size_ - 1); + return get_pa_bits(leaf, slot); + } + uint32_t get_lcs(const Leaf &leaf, uint32_t slot) const noexcept { + return decode_lcs_value(leaf, slot); + } + static void set_packed4(uint8_t *payload, uint32_t slot, + uint32_t value) noexcept { + uint8_t &packed = payload[slot >> 1]; + const uint32_t shift = (slot & 1u) * 4; + packed = static_cast((packed & ~(15u << shift)) | + ((value & 15u) << shift)); + } + static void move_packed4_right(uint8_t *values, uint32_t slot, + uint32_t count) noexcept { + if (count == 0) + return; + // Shift one nibble with a reverse byte pass. Each destination byte is + // assembled from adjacent source bytes; no per-value or per-bit loop. + const uint32_t first = slot + 1; + const uint32_t last = slot + count; + const uint32_t first_byte = first >> 1; + const uint32_t last_byte = last >> 1; + for (uint32_t byte = last_byte + 1; byte-- > first_byte;) { + const uint8_t original = values[byte]; + const uint8_t previous = byte == 0 ? 0 : values[byte - 1]; + uint8_t shifted = + static_cast((previous >> 4) | (original << 4)); + if (byte == first_byte && (first & 1u) != 0) + shifted = static_cast((shifted & 0xf0u) | + (original & 0x0fu)); + if (byte == last_byte && (last & 1u) == 0) + shifted = static_cast((shifted & 0x0fu) | + (original & 0xf0u)); + values[byte] = shifted; + } + } + static void move_mutable_lcs_right(Leaf &leaf, uint32_t slot, + uint32_t count) noexcept { + if (leaf.lcs.kind == LcsKind::Packed4) { + move_packed4_right(leaf.lcs.payload.data(), slot, count); + } else if (count != 0) { + std::memmove(leaf.lcs.payload.data() + slot + 1, + leaf.lcs.payload.data() + slot, count); + } + } + static void set_mutable_lcs(Leaf &leaf, uint32_t slot, + uint32_t value) noexcept { + if (leaf.lcs.kind == LcsKind::Packed4) + set_packed4(leaf.lcs.payload.data(), slot, value); + else + leaf.lcs.payload[slot] = static_cast(value); + } + void set_pa_bits(Leaf &leaf, uint32_t slot, uint32_t value) const noexcept { + set_pa_bits(leaf.pa.get(), slot, value, pa_bits_); + } + static void set_pa_bits(uint8_t *payload, uint32_t slot, uint32_t value, + uint32_t width) noexcept { + const size_t first_bit = static_cast(slot) * width; + const uint32_t bit_offset = static_cast(first_bit & 7u); + const uint64_t value_mask = (uint64_t{1} << width) - 1; + const uint64_t field_mask = value_mask << bit_offset; + uint8_t *const destination = payload + (first_bit >> 3); + uint64_t window = little_endian_window(destination); + window = (window & ~field_mask) | + ((static_cast(value) & value_mask) << bit_offset); + store_little_endian_window(destination, window); + } + static void move_pa_bits_right(uint8_t *payload, uint32_t slot, + uint32_t count, uint32_t width) noexcept { + if (count == 0) + return; + const size_t source_first = static_cast(slot) * width; + const size_t bit_count = static_cast(count) * width; + const size_t destination_first = source_first + width; + const size_t destination_last = destination_first + bit_count; + if ((width & 7u) == 0) { + std::memmove(payload + (destination_first >> 3), + payload + (source_first >> 3), bit_count >> 3); + return; + } + + // Shift the packed stream right by one field. The reverse byte pass is + // overlap-safe: every source byte is at or below the destination byte, + // and the current destination is read before it is replaced. Boundary + // masks preserve every bit outside the exact destination range. + const size_t byte_shift = width >> 3; + const uint32_t bit_shift = width & 7u; + const size_t first_byte = destination_first >> 3; + const size_t last_byte = (destination_last - 1) >> 3; + for (size_t byte = last_byte + 1; byte-- > first_byte;) { + const size_t upper_source = byte - byte_shift; + uint8_t shifted = + static_cast(payload[upper_source] << bit_shift); + if (upper_source != 0) + shifted = static_cast( + shifted | (payload[upper_source - 1] >> (8u - bit_shift))); + + const size_t byte_first = byte << 3; + const uint32_t first = static_cast( + destination_first > byte_first ? destination_first - byte_first + : 0); + const uint32_t last = static_cast( + destination_last < byte_first + 8 + ? destination_last - byte_first + : 8); + const uint8_t mask = static_cast( + ((uint16_t{1} << last) - 1u) & + ~((uint16_t{1} << first) - 1u)); + payload[byte] = static_cast( + (payload[byte] & static_cast(~mask)) | (shifted & mask)); + } + } + Location locate(size_t rank) const noexcept { + if (root_is_leaf_) + return {root_, static_cast(rank)}; + uint32_t node_index = root_; + for (;;) { + const Node &node = nodes_[node_index]; + uint32_t slot = 0; + while (slot + 1 < node.count && rank >= node.weights[slot]) { + rank -= node.weights[slot++]; + } + if (node.children_are_leaves) + return {node.children[slot], static_cast(rank)}; + node_index = node.children[slot]; + } + } + size_t prefix(uint32_t leaf_index) const noexcept { + size_t result = 0; + uint32_t parent = leaves_[leaf_index].parent; + uint32_t slot = leaves_[leaf_index].parent_slot; + while (parent != kNil) { + const Node &node = nodes_[parent]; + for (uint32_t index = 0; index < slot; ++index) + result += node.weights[index]; + slot = node.parent_slot; + parent = node.parent; + } + return result; + } + void move_right(Leaf &leaf, uint32_t slot, uint32_t count) noexcept { + if (!count) return; + if (code_width_ == 4) std::memmove(leaf.codes32.get() + slot + 1, leaf.codes32.get() + slot, count * 4); + else if (code_width_ == 2) std::memmove(leaf.codes16.get() + slot + 1, leaf.codes16.get() + slot, count * 2); + else if (code_width_ == 1) std::memmove(leaf.codes8.get() + slot + 1, leaf.codes8.get() + slot, count); + else { + move_packed4_right(leaf.codes4.get(), slot, count); + } + if ((pa_bits_ & 7u) == 0) { + move_pa_bits_right(leaf.pa.get(), slot, count, pa_bits_); + } else { + // For non-byte widths, a field loop touches fewer cache lines than a + // byte-stream shift on large leaves. Each field remains one pair of + // unaligned uint64 windows, never a per-bit loop. + for (uint32_t offset = count; offset != 0; --offset) + set_pa_bits(leaf, slot + offset, + get_pa_bits(leaf, slot + offset - 1)); + } + } + void insert_local(Leaf &leaf, uint32_t slot, uint32_t code, + uint32_t pa_value, uint32_t lcs_value) noexcept { + move_right(leaf, slot, leaf.size - slot); + set_code(leaf, slot, code); set_pa_bits(leaf, slot, pa_value); + (void)lcs_value; // The prepared codec already contains the inserted row. + ++leaf.size; + } + void write_leaf(Leaf &leaf, const uint32_t *codes, const uint32_t *pas, + const uint32_t *lcss, uint32_t count) noexcept { + leaf.size = count; + if (code_width_ == 0) { + const uint32_t pairs = count >> 1; + for (uint32_t pair = 0; pair < pairs; ++pair) + leaf.codes4[pair] = static_cast( + codes[pair * 2] | (codes[pair * 2 + 1] << 4)); + if ((count & 1u) != 0) + leaf.codes4[count >> 1] = static_cast(codes[count - 1]); + } else { + // Split rebuilding is rare transactional staging; live insertion + // shifts contiguous byte/word payloads in move_right(). + for (uint32_t slot = 0; slot < count; ++slot) + set_code(leaf, slot, codes[slot]); + } + for (uint32_t slot = 0; slot < count; ++slot) + set_pa_bits(leaf, slot, pas[slot]); + (void)lcss; // LCS was encoded transactionally during prepare_insert(). + } + void refresh(Leaf &leaf) noexcept { + clear_leaf_histogram(leaf); + leaf.min_lcs = std::numeric_limits::max(); + leaf.min_lcs_count = 0; + leaf.max1_pa = leaf.max2_pa = 0; + std::array lcs_values{}; + decode_lcs(leaf, lcs_values.data()); + for (uint32_t slot = 0; slot < leaf.size; ++slot) { + if (!(&leaf == &leaves_[sentinel_leaf_] && slot == sentinel_slot_)) { + const uint32_t code = get_code(leaf, slot); + increment_leaf_histogram(leaf, code, code_width_); + } + const uint32_t lcs = lcs_values[slot]; + if (lcs < leaf.min_lcs) { + leaf.min_lcs = lcs; + leaf.min_lcs_count = 1; + } else if (lcs == leaf.min_lcs) { + ++leaf.min_lcs_count; + } + const uint32_t value = get_pa(leaf, slot); + if (value >= leaf.max1_pa) { leaf.max2_pa = leaf.max1_pa; leaf.max1_pa = value; } + else if (value > leaf.max2_pa) leaf.max2_pa = value; + } + } + + static void increment_histogram(std::vector &histogram, + uint32_t code, + uint32_t width) noexcept { + const size_t found = histogram_lower_bound(histogram, code, width); + if (found != histogram_size(histogram, width) && + histogram_code(histogram, found, width) == code) { + histogram_set_count( + histogram, found, + histogram_entry_count(histogram, found, width) + 1u, width); + } else { + histogram_insert(histogram, found, code, 1, width); + } + } + + void increment_histogram_up(uint32_t node, uint32_t code) noexcept { + while (node != kNil) { + increment_histogram(nodes_[node].histogram, code, code_width_); + node = nodes_[node].parent; + } + } + + void increment_weight_up(uint32_t leaf) noexcept { + uint32_t node = leaves_[leaf].parent; + uint32_t slot = leaves_[leaf].parent_slot; + while (node != kNil) { + Node &entry = nodes_[node]; + ++entry.weights[slot]; + ++entry.weight; + slot = entry.parent_slot; + node = entry.parent; + } + } + + void add_leaf_lcs(Leaf &leaf, uint32_t value) noexcept { + if (value < leaf.min_lcs) { + leaf.min_lcs = value; + leaf.min_lcs_count = 1; + } else if (value == leaf.min_lcs) { + ++leaf.min_lcs_count; + } + } + + void recompute_leaf_lcs(Leaf &leaf) noexcept { + leaf.min_lcs = std::numeric_limits::max(); + leaf.min_lcs_count = 0; + std::array values{}; + decode_lcs(leaf, values.data()); + for (uint32_t slot = 0; slot < leaf.size; ++slot) { + const uint32_t value = values[slot]; + if (value < leaf.min_lcs) { + leaf.min_lcs = value; + leaf.min_lcs_count = 1; + } else if (value == leaf.min_lcs) { + ++leaf.min_lcs_count; + } + } + } + + static uint32_t histogram_count(const std::vector &histogram, + uint32_t code, + uint32_t width) noexcept { + const size_t found = histogram_lower_bound(histogram, code, width); + return found != histogram_size(histogram, width) && + histogram_code(histogram, found, width) == code + ? histogram_entry_count(histogram, found, width) + : 0; + } + static void add_histogram_entry(std::vector &histogram, + uint32_t code, uint32_t count, + uint32_t width) noexcept { + const size_t found = histogram_lower_bound(histogram, code, width); + if (found != histogram_size(histogram, width) && + histogram_code(histogram, found, width) == code) { + histogram_set_count( + histogram, found, + histogram_entry_count(histogram, found, width) + count, width); + } else { + histogram_insert(histogram, found, code, count, width); + } + } + static void merge_leaf_histogram(std::vector &destination, + const Leaf &leaf, + uint32_t width) noexcept { + if (leaf.histogram_wide) { + const size_t entries = histogram_size(leaf.histogram, width); + for (size_t index = 0; index < entries; ++index) + add_histogram_entry( + destination, histogram_code(leaf.histogram, index, width), + histogram_entry_count(leaf.histogram, index, width), width); + return; + } + for (uint32_t word_index = 0; word_index < 4; ++word_index) { + uint64_t word = leaf.histogram_bitmap[word_index]; + while (word != 0) { + const uint32_t bit = static_cast(__builtin_ctzll(word)); + const uint32_t code = word_index * 64u + bit; + add_histogram_entry(destination, code, + leaf_histogram_count(leaf, code, width), width); + word &= word - 1; + } + } + } + size_t root_histogram_size() const noexcept { + return root_is_leaf_ + ? leaf_histogram_size(leaves_[root_], code_width_) + : histogram_size(nodes_[root_].histogram, code_width_); + } + void reserve_leaf_histogram(uint32_t leaf, size_t capacity) { + reserve_leaf_histogram(leaves_[leaf], capacity, code_width_); + } + void reserve_path(uint32_t leaf, size_t capacity) { + for (uint32_t node = leaves_[leaf].parent; node != kNil; + node = nodes_[node].parent) + histogram_reserve(nodes_[node].histogram, capacity, code_width_); + } + uint32_t child_weight(const Node &node, uint32_t slot) const noexcept { + return node.children_are_leaves ? leaves_[node.children[slot]].size + : nodes_[node.children[slot]].weight; + } + uint32_t child_min(const Node &node, uint32_t slot) const noexcept { + return node.children_are_leaves ? leaves_[node.children[slot]].min_lcs + : nodes_[node.children[slot]].min_lcs; + } + void child_max(const Node &node, uint32_t slot, uint32_t &max1, + uint32_t &max2) const noexcept { + if (node.children_are_leaves) { + max1 = leaves_[node.children[slot]].max1_pa; + max2 = leaves_[node.children[slot]].max2_pa; + } else { + max1 = nodes_[node.children[slot]].max1_pa; + max2 = nodes_[node.children[slot]].max2_pa; + } + } + static void add_max(uint32_t value, uint32_t &max1, + uint32_t &max2) noexcept { + if (value >= max1) { max2 = max1; max1 = value; } + else if (value > max2) max2 = value; + } + void refresh_node_extrema(uint32_t node_index) noexcept { + Node &node = nodes_[node_index]; + node.min_lcs = std::numeric_limits::max(); + node.max1_pa = node.max2_pa = 0; + for (uint32_t slot = 0; slot < node.count; ++slot) { + node.min_lcs = std::min(node.min_lcs, child_min(node, slot)); + uint32_t first, second; + child_max(node, slot, first, second); + add_max(first, node.max1_pa, node.max2_pa); + add_max(second, node.max1_pa, node.max2_pa); + } + } + void refresh_extrema_paths(const uint32_t *leaves, + uint32_t leaf_count) noexcept { + uint32_t current[2]; + uint32_t current_count = 0; + for (uint32_t index = 0; index < leaf_count; ++index) { + const uint32_t parent = leaves_[leaves[index]].parent; + bool seen = parent == kNil; + for (uint32_t prior = 0; prior < current_count; ++prior) + seen = seen || current[prior] == parent; + if (!seen) + current[current_count++] = parent; + } + while (current_count != 0) { + uint32_t next[2]; + uint32_t next_count = 0; + for (uint32_t index = 0; index < current_count; ++index) { + const uint32_t node = current[index]; + refresh_node_extrema(node); + const uint32_t parent = nodes_[node].parent; + bool seen = parent == kNil; + for (uint32_t prior = 0; prior < next_count; ++prior) + seen = seen || next[prior] == parent; + if (!seen) + next[next_count++] = parent; + } + current_count = next_count; + for (uint32_t index = 0; index < next_count; ++index) + current[index] = next[index]; + } + } + void set_parent(const Node &node, uint32_t slot, + uint32_t parent_index) noexcept { + if (node.children_are_leaves) { + leaves_[node.children[slot]].parent = parent_index; + leaves_[node.children[slot]].parent_slot = slot; + } else { + nodes_[node.children[slot]].parent = parent_index; + nodes_[node.children[slot]].parent_slot = slot; + } + } + void refresh_node(uint32_t node_index) noexcept { + Node &node = nodes_[node_index]; + node.weight = 0; + node.min_lcs = std::numeric_limits::max(); + node.max1_pa = node.max2_pa = 0; + node.histogram.clear(); + for (uint32_t slot = 0; slot < node.count; ++slot) { + set_parent(node, slot, node_index); + node.weights[slot] = child_weight(node, slot); + node.weight += node.weights[slot]; + node.min_lcs = std::min(node.min_lcs, child_min(node, slot)); + uint32_t first, second; + child_max(node, slot, first, second); + add_max(first, node.max1_pa, node.max2_pa); + add_max(second, node.max1_pa, node.max2_pa); + if (node.children_are_leaves) { + merge_leaf_histogram(node.histogram, leaves_[node.children[slot]], + code_width_); + } else { + const std::vector &child = + nodes_[node.children[slot]].histogram; + const size_t child_size = histogram_size(child, code_width_); + for (size_t index = 0; index < child_size; ++index) + add_histogram_entry( + node.histogram, histogram_code(child, index, code_width_), + histogram_entry_count(child, index, code_width_), + code_width_); + } + } + } + void refresh_up(uint32_t node) noexcept { + while (node != kNil) { + refresh_node(node); + node = nodes_[node].parent; + } + } + uint32_t take_pending_node() noexcept { + std::unique_ptr prepared = std::move(pending_nodes_.back()); + pending_nodes_.pop_back(); + const uint32_t index = static_cast(nodes_.size()); + nodes_.push_back(std::move(*prepared)); + return index; + } + void make_root(uint32_t left, uint32_t right, + bool children_are_leaves) noexcept { + const uint32_t root = take_pending_node(); + Node &node = nodes_[root]; + node.children_are_leaves = children_are_leaves; + node.count = 2; + node.children[0] = left; + node.children[1] = right; + node.parent = kNil; + refresh_node(root); + root_ = root; + root_is_leaf_ = false; + } + void insert_child_after(uint32_t parent, uint32_t left_slot, + uint32_t child) noexcept { + Node &node = nodes_[parent]; + if (node.count < kFanout) { + for (uint32_t slot = node.count; slot > left_slot + 1; --slot) + node.children[slot] = node.children[slot - 1]; + node.children[left_slot + 1] = child; + ++node.count; + refresh_node(parent); + refresh_up(node.parent); + return; + } + std::array children; + for (uint32_t out = 0; out <= kFanout; ++out) { + if (out == left_slot + 1) children[out] = child; + else children[out] = node.children[out - (out > left_slot + 1 ? 1u : 0u)]; + } + const uint32_t old_parent = node.parent; + const uint32_t old_parent_slot = node.parent_slot; + const uint32_t sibling_index = take_pending_node(); + Node &sibling = nodes_[sibling_index]; + sibling.children_are_leaves = node.children_are_leaves; + sibling.parent = old_parent; + node.count = 8; + sibling.count = 9; + for (uint32_t slot = 0; slot < 8; ++slot) node.children[slot] = children[slot]; + for (uint32_t slot = 0; slot < 9; ++slot) sibling.children[slot] = children[slot + 8]; + refresh_node(parent); + refresh_node(sibling_index); + if (old_parent == kNil) + make_root(parent, sibling_index, false); + else + insert_child_after(old_parent, old_parent_slot, sibling_index); + } + void insert_leaf_after(uint32_t left, uint32_t right) noexcept { + if (root_is_leaf_) { + make_root(left, right, true); + return; + } + insert_child_after(leaves_[left].parent, leaves_[left].parent_slot, right); + } + uint32_t rank_prefix(size_t rank, uint32_t code) const noexcept { + if (root_is_leaf_) { + const Leaf &leaf = leaves_[root_]; + uint32_t result = 0; + for (uint32_t slot = 0; slot < rank; ++slot) + result += get_code(leaf, slot) == code; + return result; + } + uint32_t result = 0, node_index = root_; + for (;;) { + const Node &node = nodes_[node_index]; + uint32_t slot = 0; + while (slot < node.count && rank >= node.weights[slot]) { + result += node.children_are_leaves + ? leaf_histogram_count(leaves_[node.children[slot]], + code, code_width_) + : histogram_count(nodes_[node.children[slot]].histogram, + code, code_width_); + rank -= node.weights[slot++]; + } + if (node.children_are_leaves) { + if (slot == node.count) return result; + const Leaf &leaf = leaves_[node.children[slot]]; + for (uint32_t leaf_slot = 0; leaf_slot < rank; ++leaf_slot) + result += get_code(leaf, leaf_slot) == code; + return result; + } + node_index = node.children[slot]; + } + } + uint32_t previous_leaf_with_min(uint32_t leaf, uint32_t threshold) const noexcept { + uint32_t parent = leaves_[leaf].parent, slot = leaves_[leaf].parent_slot; + while (parent != kNil) { + const Node &node = nodes_[parent]; + while (slot > 0) { + --slot; + if (child_min(node, slot) < threshold) + return rightmost_leaf_below(node.children[slot], node.children_are_leaves, threshold); + } + slot = node.parent_slot; + parent = node.parent; + } + return kNil; + } + uint32_t next_leaf_with_min(uint32_t leaf, uint32_t threshold) const noexcept { + uint32_t parent = leaves_[leaf].parent, slot = leaves_[leaf].parent_slot; + while (parent != kNil) { + const Node &node = nodes_[parent]; + while (++slot < node.count) + if (child_min(node, slot) < threshold) + return leftmost_leaf_below(node.children[slot], node.children_are_leaves, threshold); + slot = node.parent_slot; + parent = node.parent; + } + return kNil; + } + uint32_t rightmost_leaf_below(uint32_t child, bool leaf, + uint32_t threshold) const noexcept { + while (!leaf) { + const Node &node = nodes_[child]; + uint32_t slot = node.count; + do { --slot; } while (child_min(node, slot) >= threshold); + child = node.children[slot]; + leaf = node.children_are_leaves; + } + return child; + } + uint32_t leftmost_leaf_below(uint32_t child, bool leaf, + uint32_t threshold) const noexcept { + while (!leaf) { + const Node &node = nodes_[child]; + uint32_t slot = 0; + while (child_min(node, slot) >= threshold) ++slot; + child = node.children[slot]; + leaf = node.children_are_leaves; + } + return child; + } + static uint32_t aggregate_max(uint32_t max1, uint32_t max2, + uint32_t excluded) noexcept { + return max1 == excluded ? max2 : max1; + } + uint32_t query_leaf_max(uint32_t leaf_index, size_t first, size_t last, + size_t base, uint32_t excluded) const noexcept { + const Leaf &leaf = leaves_[leaf_index]; + const size_t end = base + leaf.size; + if (last <= base || end <= first) return 0; + if (first <= base && end <= last) + return aggregate_max(leaf.max1_pa, leaf.max2_pa, excluded); + uint32_t result = 0; + const uint32_t begin = first > base ? static_cast(first - base) : 0; + const uint32_t finish = last < end ? static_cast(last - base) : leaf.size; + for (uint32_t slot = begin; slot < finish; ++slot) { + const uint32_t value = get_pa(leaf, slot); + if (value != excluded) result = std::max(result, value); + } + return result; + } + uint32_t query_node_max(uint32_t node_index, size_t first, size_t last, + size_t base, uint32_t excluded) const noexcept { + const Node &node = nodes_[node_index]; + if (first <= base && base + node.weight <= last) + return aggregate_max(node.max1_pa, node.max2_pa, excluded); + uint32_t result = 0; + size_t child_base = base; + for (uint32_t slot = 0; slot < node.count; ++slot) { + const size_t child_end = child_base + node.weights[slot]; + if (child_end > first && child_base < last) { + uint32_t value; + if (first <= child_base && child_end <= last) { + uint32_t first_max, second_max; + child_max(node, slot, first_max, second_max); + value = aggregate_max(first_max, second_max, excluded); + } else if (node.children_are_leaves) { + value = query_leaf_max(node.children[slot], first, last, + child_base, excluded); + } else { + value = query_node_max(node.children[slot], first, last, + child_base, excluded); + } + result = std::max(result, value); + } + child_base = child_end; + if (child_base >= last) break; + } + return result; + } + + std::vector leaves_; + std::vector nodes_; + std::unique_ptr pending_left_payload_; + std::unique_ptr pending_leaf_; + LcsCodec staging_lcs_; + LcsCodec staging_successor_lcs_; + bool pending_lcs_active_ = false; + bool pending_successor_lcs_active_ = false; + bool pending_raw8_in_place_ = false; + bool pending_raw8_successor_in_place_ = false; + uint32_t pending_successor_leaf_ = kNil; + uint32_t pending_successor_slot_ = 0; + std::vector> pending_nodes_; + uint32_t root_ = kNil, first_leaf_ = kNil, last_leaf_ = kNil; + uint32_t sentinel_leaf_ = kNil; + uint32_t sentinel_slot_ = 0; + size_t capacity_ = 0, size_ = 0; + uint32_t maximum_pa_bits_ = 1; + uint32_t pa_bits_ = 0; + uint32_t leaf_limit_ = 2048; + uint32_t split_left_size_ = 1024; + uint32_t split_right_size_ = 1025; + uint32_t split_leaf_capacity_ = 1088; + uint32_t leaf_growth_ = 256; + uint32_t lcs_width_ = 2; + uint32_t code_width_ = 0; + bool root_is_leaf_ = true; + }; + + struct Row { + struct IdentityCounts { + std::array values{}; + std::array blocks{}; + }; + + explicit Row(int64_t capacity, bool identity_codes) + : sequence(static_cast(capacity) + 1), + identity_counts(identity_codes ? std::make_unique() + : nullptr) {} + + void reset(UnifiedSequence reset_sequence) noexcept { + sequence = std::move(reset_sequence); + history.reset(); + std::vector().swap(counts); + std::vector().swap(code_values); + if (identity_counts) *identity_counts = IdentityCounts{}; + source = -1; + lrs = 0; + } + + CompactArray history; + UnifiedSequence sequence; + std::vector counts; + std::vector code_values; + std::unique_ptr identity_counts; + int64_t source = -1; + int64_t lrs = 0; + }; + + py::array_t + checked_tokens(py::array object, int dimensions) const { + if (!py::isinstance>(object)) + throw py::type_error("tokens must have dtype int64"); + if ((object.flags() & py::array::c_style) == 0) + throw py::value_error("tokens must be C-contiguous"); + auto tokens = + py::cast>(object); + if (tokens.ndim() != dimensions || tokens.shape(0) != batch_) { + if (dimensions == 1) + throw py::value_error( + "tokens must be contiguous int64 [batch_size]"); + throw py::value_error( + "tokens must be contiguous int64 [batch_size, sequence_length]"); + } + if (identity_codes_) { + const int64_t count = static_cast(tokens.size()); + for (int64_t index = 0; index < count; ++index) { + const int64_t token = tokens.data()[index]; + if (token < 0 || token >= static_cast(vocabulary_size_)) + throw py::value_error("compact RLBWT tokens must be in [0, vocabulary_size)"); + } + } + return tokens; + } + + static uint64_t identity_prefix(const Row &row, uint32_t code) noexcept { + uint64_t total = 0; + const uint32_t full_blocks = code >> 4; + for (uint32_t block = 0; block < full_blocks; ++block) + total += row.identity_counts->blocks[block]; + const uint32_t first = full_blocks << 4; + for (uint32_t index = first; index < code; ++index) + total += row.identity_counts->values[index]; + return total; + } + + static void identity_increment(Row &row, uint32_t code) noexcept { + ++row.identity_counts->values[code]; + ++row.identity_counts->blocks[code >> 4]; + } + + static std::vector::iterator count_location(Row &row, + int64_t symbol) { + return std::lower_bound( + row.counts.begin(), row.counts.end(), symbol, + [](const SymbolCount &entry, int64_t value) { + return entry.symbol < value; + }); + } + + static void prepare_dictionary_capacity( + const Row &row, bool is_new, std::vector &grown_counts, + std::vector &grown_codes, bool &replace_counts, + bool &replace_codes) { + const size_t count_needed = row.counts.size() + (is_new ? 1 : 0); + const size_t code_needed = row.code_values.size() + (is_new ? 1 : 0); + replace_counts = count_needed > row.counts.capacity(); + replace_codes = code_needed > row.code_values.capacity(); + if (replace_counts) { + grown_counts = row.counts; + grown_counts.reserve(std::max(count_needed, + row.counts.capacity() * 2)); + } + if (replace_codes) { + grown_codes = row.code_values; + grown_codes.reserve(std::max(code_needed, + row.code_values.capacity() * 2)); + } + } + + bool equal_suffix(const Row &row, size_t left, size_t right, + size_t length) const noexcept { + const auto &hashes = row_hashes_[static_cast(&row - rows_.data())]; + for (uint32_t lane = 0; lane < lanes_; ++lane) { + const auto &prefix = hashes[lane]; + const uint64_t left_hash = + prefix[left] - prefix[left - length] * powers_[lane][length]; + const uint64_t right_hash = + prefix[right] - prefix[right - length] * powers_[lane][length]; + if (left_hash != right_hash) + return false; + } + return true; + } + + int64_t common_suffix(const Row &row, int64_t left, int64_t right) const { + const size_t left_endpoint = static_cast(left); + const size_t right_endpoint = static_cast(right); + const size_t available = std::min(left_endpoint, right_endpoint); + if (lanes_ == 0 || available <= 64) + return static_cast( + row.history.common_suffix(left_endpoint, right_endpoint)); + size_t low = 0, high = available + 1; + while (low + 1 < high) { + const size_t middle = low + (high - low) / 2; + if (equal_suffix(row, left_endpoint, right_endpoint, middle)) + low = middle; + else + high = middle; + } + // Short LCEs remain exact. Long results are intentionally not verified: + // this is the Monte-Carlo contract and the source of the speedup. + if (low <= 64) + return static_cast( + row.history.common_suffix(left_endpoint, right_endpoint)); + return static_cast(low); + } + + void compute_pa_lcs(const Row &row, int64_t old_length, + int64_t insertion_index, int64_t &x, + int64_t &y) const { + const int64_t old_size = old_length + 1; + const int64_t new_endpoint = old_length + 1; + if (insertion_index < 0 || insertion_index > old_size) + throw std::runtime_error("PA insertion position is out of range"); + const bool has_predecessor = insertion_index > 0; + const bool has_successor = insertion_index < old_size; + x = has_predecessor + ? common_suffix(row, new_endpoint, + row.sequence.pa( + static_cast(insertion_index - 1))) + : 0; + y = has_successor + ? common_suffix(row, new_endpoint, + row.sequence.pa(static_cast(insertion_index))) + : 0; + } + + static int64_t select_source(const Row &row, int64_t new_rank, + int64_t new_size, int64_t lrs) { + if (lrs == 0) + return -1; + const size_t left = row.sequence.nearest_previous_lcs_less( + static_cast(new_rank), static_cast(lrs)); + const size_t right = row.sequence.nearest_next_lcs_less( + static_cast(new_rank + 1), static_cast(lrs)); + const int64_t new_endpoint = new_size - 1; + // Exact recent-occurrence tie break: choose the largest old endpoint in + // the maximal PA interval, skipping complete leaves via cached maxima. + const uint32_t previous_endpoint = row.sequence.range_max_excluding( + left, right, static_cast(new_endpoint)); + if (previous_endpoint == static_cast(new_endpoint)) + throw std::runtime_error("RLBWT source interval has no old endpoint"); + return static_cast(previous_endpoint) - 1; + } + + int64_t step_row(Row &row, int64_t old_length, int64_t token) { + const auto token_location = identity_codes_ ? row.counts.end() + : count_location(row, token); + const bool new_symbol = !identity_codes_ && + (token_location == row.counts.end() || + token_location->symbol != token); + if (new_symbol && row.code_values.size() >= kSentinelCode) + throw std::runtime_error("RLBWT symbol dictionary is exhausted"); + const uint32_t code = identity_codes_ + ? static_cast(token) + : new_symbol + ? static_cast(row.code_values.size()) + : token_location->code; + const bool promote8 = row.history.width() == 0 && + (identity_codes_ ? code >= 16 + : new_symbol && row.code_values.size() == 16); + const bool promote16 = !identity_codes_ && new_symbol && row.history.width() == 1 && + row.code_values.size() == + static_cast(std::numeric_limits::max()) + 1; + const bool promote32 = new_symbol && row.history.width() == 2 && + row.code_values.size() == + static_cast(std::numeric_limits::max()) + 1; + CompactArray::Pages byte_history; + CompactArray::Pages narrow_history; + CompactArray::Pages wide_history; + std::vector> byte_sequence; + std::vector> narrow_sequence; + std::vector> wide_sequence; + UnifiedSequence::HistogramRepack sequence_histograms; + // Prepare the destination before history mutation. PACKED4 promotion + // stages both conversion and a possible new page in the replacement; + // later preparation failures therefore leave the live history untouched. + if (promote8) { + byte_history = row.history.prepare_promotion_append(); + } else + row.history.prepare_append(static_cast(old_length)); + if (promote16) { + narrow_history = row.history.prepare(); + } else if (promote32) { + wide_history = row.history.prepare(); + } + std::vector grown_counts; + std::vector grown_codes; + bool replace_counts = false, replace_codes = false; + if (!identity_codes_) + prepare_dictionary_capacity(row, new_symbol, grown_counts, grown_codes, + replace_counts, replace_codes); + uint64_t less = identity_codes_ ? 1 + identity_prefix(row, code) : 1; + if (!identity_codes_) + for (const SymbolCount &entry : row.counts) { + if (entry.symbol >= token) break; + less += entry.count; + } + const uint32_t rank = static_cast(less + row.sequence.rank(code)); + // The history slot is not live until the outer position advances, so it + // may be populated before preparation and overwritten after a failure. + // At a width promotion the token is necessarily new and therefore both + // exact suffix lengths are zero without storing its not-yet-representable + // code in the old narrow array. + int64_t x = 0, y = 0; + row.sequence.promote_pa_for_endpoint( + static_cast(old_length + 1)); + if (!promote8 && !promote16 && !promote32) { + row.history.set(static_cast(old_length), code); + append_hash(row, static_cast(old_length), code); + compute_pa_lcs(row, old_length, rank, x, y); + } + row.sequence.prepare_insert( + rank, code, static_cast(rank > 0 ? x : 0), + rank < static_cast(old_length + 1), + static_cast(y)); + // Width promotion is one transaction across BWT payloads and every + // live or pending histogram. Prepare all replacement allocations only + // after prepare_insert() has materialized the pending split objects. + if (promote8) { + byte_sequence = row.sequence.prepare_codes8(); + sequence_histograms = row.sequence.prepare_histograms(1); + } else if (promote16) { + narrow_sequence = row.sequence.prepare_codes16(); + sequence_histograms = row.sequence.prepare_histograms(2); + } else if (promote32) { + wide_sequence = row.sequence.prepare_codes32(); + sequence_histograms = row.sequence.prepare_histograms(4); + } + + // Every codec and growth payload has succeeded. From here all commits + // are vector/unique_ptr swaps and fixed-capacity moves. + if (promote8) { + row.history.commit(std::move(byte_history)); + row.sequence.commit_codes8(std::move(byte_sequence), + std::move(sequence_histograms)); + } else if (promote16) { + row.history.commit(std::move(narrow_history)); + row.sequence.commit_codes16(std::move(narrow_sequence), + std::move(sequence_histograms)); + } else if (promote32) { + row.history.commit(std::move(wide_history)); + row.sequence.commit_codes32(std::move(wide_sequence), + std::move(sequence_histograms)); + } + if (replace_counts) + row.counts.swap(grown_counts); + if (replace_codes) + row.code_values.swap(grown_codes); + if (promote8 || promote16 || promote32) { + row.history.set(static_cast(old_length), code); + append_hash(row, static_cast(old_length), code); + } + auto location = row.counts.end(); + if (!identity_codes_) { + location = count_location(row, token); + if (new_symbol) { + row.code_values.push_back(token); + location = row.counts.insert(location, SymbolCount{token, code, 0}); + } + } + row.sequence.replace_and_insert( + code, rank, static_cast(old_length + 1), + static_cast(rank > 0 ? x : 0), + rank < static_cast(old_length + 1), + static_cast(y)); + row.history.finish_append(); + if (identity_codes_) + identity_increment(row, code); + else + ++location->count; + row.lrs = std::max(x, y); + row.source = select_source(row, rank, old_length + 2, row.lrs); + if (row.source < 0) return -1; + const uint32_t output_code = + row.history.get(static_cast(row.source + 1)); + return identity_codes_ ? static_cast(output_code) + : row.code_values[output_code]; + } + + void append_hash(Row &row, size_t index, uint32_t code) noexcept { + const uint64_t value = static_cast(code) + 1; + auto &hashes = row_hashes_[static_cast(&row - rows_.data())]; + for (uint32_t lane = 0; lane < lanes_; ++lane) + hashes[lane][index + 1] = hashes[lane][index] * bases_[lane] + value; + } + + void ensure_pool(int64_t minimum_parallel_rows) { + if (batch_ >= minimum_parallel_rows && !row_pool_) + row_pool_ = std::make_unique(batch_); + } + + template + void parallel_for_rows(int64_t minimum_parallel_rows, Function &&function) { + if (batch_ < minimum_parallel_rows) { + for (int64_t b = 0; b < batch_; ++b) + function(b); + return; + } + row_pool_->parallel_for_rows(batch_, minimum_parallel_rows, + std::forward(function)); + } + + std::vector rows_; + std::vector, 3>> row_hashes_; + std::array, 3> powers_; + std::array bases_{}; + std::unique_ptr row_pool_; + mutable std::mutex call_mutex_; + int64_t batch_, max_length_, position_ = 0; + uint32_t lanes_ = 0; + uint64_t seed_ = 0; + uint32_t vocabulary_size_ = 0; + bool identity_codes_ = false; +}; + +class NativeRLBWTCompactState : public NativeRLBWTState { +public: + NativeRLBWTCompactState(int64_t batch_size, int64_t max_length, + uint32_t vocabulary_size = 256) + : NativeRLBWTState(batch_size, max_length, 0, 0, + checked_vocabulary(vocabulary_size)), + vocabulary_size_(vocabulary_size) {} + + uint32_t vocabulary_size() const noexcept { return vocabulary_size_; } + +private: + static uint32_t checked_vocabulary(uint32_t vocabulary_size) { + if (vocabulary_size < 1 || vocabulary_size > 256) + throw py::value_error("vocabulary_size must be in [1, 256]"); + return vocabulary_size; + } + + uint32_t vocabulary_size_; +}; + +class NativeRLBWTStateMC : public NativeRLBWTState { +public: + NativeRLBWTStateMC(int64_t batch_size, int64_t max_length, uint32_t lanes, + uint64_t seed) + : NativeRLBWTState(batch_size, max_length, checked_lanes(lanes), seed) {} + +private: + static uint32_t checked_lanes(uint32_t lanes) { + if (lanes != 2 && lanes != 3) + throw py::value_error("lanes must be 2 or 3"); + return lanes; + } +}; + // Allocating candidate entry points delegate to their caller-owned variants. PYBIND11_MODULE(rosa_native_step, m) { - m.doc() = "Exact CPU SAM+LCT step prototype (no libtorch calls in core)"; + m.doc() = "Exact CPU SAM+LCT and RLBWT backends (no libtorch calls)"; py::class_(m, "NativeState") .def(py::init(), py::keep_alive<1, 2>()) .def("step", &NativeState::step) @@ -1784,5 +4971,40 @@ PYBIND11_MODULE(rosa_native_step, m) { .def_property_readonly("positions", &NativeCandidateState::positions) .def_property_readonly("worker_count", &NativeCandidateState::worker_count); + py::class_(m, "NativeRLBWTState") + .def(py::init(), py::arg("batch_size"), + py::arg("max_length")) + .def("step", &NativeRLBWTState::step, py::arg("tokens").noconvert()) + .def("prefill", &NativeRLBWTState::prefill, + py::arg("tokens").noconvert()) + .def("prefill_append", &NativeRLBWTState::prefill_append, + py::arg("tokens").noconvert()) + .def("reset", &NativeRLBWTState::reset) + .def("row_snapshot", &NativeRLBWTState::row_snapshot, + py::arg("batch_index")) + .def_property_readonly("position", &NativeRLBWTState::position) + .def_property_readonly("batch_size", &NativeRLBWTState::batch_size) + .def_property_readonly("max_length", &NativeRLBWTState::max_length) + .def_property_readonly("sources", &NativeRLBWTState::sources) + .def_property_readonly("lrs_lengths", &NativeRLBWTState::lrs_lengths) + .def_property_readonly("run_counts", &NativeRLBWTState::run_counts) + .def_property_readonly("storage_bytes", &NativeRLBWTState::storage_bytes) + .def_property_readonly("storage_breakdown", + &NativeRLBWTState::storage_breakdown); + py::class_(m, "NativeRLBWTStateMC") + .def(py::init(), + py::arg("batch_size"), py::arg("max_length"), py::arg("lanes"), + py::arg("seed")) + .def_property_readonly("lanes", &NativeRLBWTStateMC::lanes) + .def_property_readonly("seed", &NativeRLBWTStateMC::seed); + py::class_( + m, "NativeRLBWTCompactState") + .def(py::init(), py::arg("batch_size"), + py::arg("max_length"), py::arg("vocabulary_size") = 256) + .def_property_readonly("vocabulary_size", + &NativeRLBWTCompactState::vocabulary_size); m.attr("candidate_abi_version") = py::int_(1); + m.attr("rlbwt_abi_version") = py::int_(1); + m.attr("rlbwt_mc_abi_version") = py::int_(1); + m.attr("rlbwt_compact_abi_version") = py::int_(1); } diff --git a/native/tests/rlbwt_smoke.py b/native/tests/rlbwt_smoke.py new file mode 100644 index 0000000..3bc4a99 --- /dev/null +++ b/native/tests/rlbwt_smoke.py @@ -0,0 +1,188 @@ +from __future__ import annotations + +from itertools import product + +import numpy as np +import rosa_native_step +import torch + +from rosa import reference_rosa +from rosa._rlbwt_backend import ( + _init_rlbwt_state, + _prefill, + _reconstruct_rlbwt, +) + + +def main() -> None: + assert rosa_native_step.rlbwt_abi_version == 1 + assert rosa_native_step.rlbwt_mc_abi_version == 1 + assert rosa_native_step.rlbwt_compact_abi_version == 1 + + exhaustive = torch.tensor(list(product(range(3), repeat=8)), dtype=torch.long) + expected, sources, lengths = reference_rosa(exhaustive) + native = rosa_native_step.NativeRLBWTState(exhaustive.shape[0], exhaustive.shape[1]) + actual = torch.from_numpy(native.prefill(np.ascontiguousarray(exhaustive.numpy()))) + assert torch.equal(actual, expected) + assert native.sources.tolist() == sources[:, -1].tolist() + assert native.lrs_lengths.tolist() == lengths[:, -1].tolist() + + tokens = torch.tensor( + [[-(2**63), 2**63 - 1, -(2**63), 0, 7, 0, 7]], dtype=torch.long + ) + python_state = _init_rlbwt_state(1, tokens.shape[1]) + expected = _prefill(python_state, tokens) + native = rosa_native_step.NativeRLBWTState(1, tokens.shape[1]) + actual = torch.from_numpy(native.prefill(np.ascontiguousarray(tokens.numpy()))) + assert torch.equal(actual, expected) + pa, lcs, bwt, sentinel = native.row_snapshot(0) + assert pa.tolist() == python_state.pa[0][: tokens.shape[1] + 1] + assert lcs.tolist() == python_state.lcs[0][: tokens.shape[1] + 1] + reconstructed = [] + for value, is_sentinel in zip(bwt.tolist(), sentinel.tolist(), strict=True): + reconstructed.append("$" if is_sentinel else value) + expected_bwt = [ + "$" if repr(value) == "$" else value + for value in _reconstruct_rlbwt(python_state) + ] + assert reconstructed == expected_bwt + + generator = torch.Generator().manual_seed(20260811) + random_tokens = torch.randint(16, (3, 300), generator=generator) + expected, _, _ = reference_rosa(random_tokens) + adaptive = rosa_native_step.NativeRLBWTState(3, 300) + actual = torch.from_numpy( + adaptive.prefill(np.ascontiguousarray(random_tokens.numpy())) + ) + assert torch.equal(actual, expected) + # Random alphabet-16 rows cross the adaptive RLE-to-literal threshold. + assert all(count > 75 for count in adaptive.run_counts.tolist()) + + compact_values = np.ascontiguousarray(random_tokens.numpy()) + compact = rosa_native_step.NativeRLBWTCompactState(3, 300, 256) + compact_output = np.concatenate( + ( + compact.prefill_append(np.ascontiguousarray(compact_values[:, :137])), + compact.prefill_append(np.ascontiguousarray(compact_values[:, 137:])), + ), + axis=1, + ) + assert np.array_equal(compact_output, actual.numpy()) + assert compact.vocabulary_size == 256 + assert sum(compact.storage_breakdown.values()) <= compact.storage_bytes + try: + compact.step(np.array([0, 1, 256], dtype=np.int64)) + except ValueError: + pass + else: + raise AssertionError("compact RLBWT accepted an out-of-range token") + + wide_tokens = torch.randint(256, (1, 2048), generator=generator) + wide_expected, _, _ = reference_rosa(wide_tokens) + wide_compact = rosa_native_step.NativeRLBWTCompactState(1, 2048, 256) + wide_actual = wide_compact.prefill(np.ascontiguousarray(wide_tokens.numpy())) + assert np.array_equal(wide_actual, wide_expected.numpy()) + + repeated = torch.tensor([[index & 1 for index in range(1200)]], dtype=torch.long) + repeated_expected, _, _ = reference_rosa(repeated) + repeated_compact = rosa_native_step.NativeRLBWTCompactState(1, 100_000_000, 256) + repeated_actual = repeated_compact.prefill_append( + np.ascontiguousarray(repeated.numpy()) + ) + assert np.array_equal(repeated_actual, repeated_expected.numpy()) + + native.reset() + assert native.position == 0 + assert native.run_counts.tolist() == [1] + assert native.step(np.array([5], dtype=np.int64)).tolist() == [-1] + + monte_carlo_inputs = [ + torch.tensor(list(product(range(2), repeat=8)), dtype=torch.long), + torch.randint(23, (4, 257), generator=generator), + torch.zeros((2, 257), dtype=torch.long), + torch.tensor([[index % 7 for index in range(257)]], dtype=torch.long), + ] + for lanes in (2, 3): + for seed in (0, 20260811, 2**64 - 1): + for values in monte_carlo_inputs: + exact = rosa_native_step.NativeRLBWTState( + values.shape[0], values.shape[1] + ) + mc = rosa_native_step.NativeRLBWTStateMC( + values.shape[0], values.shape[1], lanes, seed + ) + array = np.ascontiguousarray(values.numpy()) + assert np.array_equal(mc.prefill(array), exact.prefill(array)) + assert np.array_equal(mc.sources, exact.sources) + assert np.array_equal(mc.lrs_lengths, exact.lrs_lengths) + for row in range(values.shape[0]): + for mc_field, exact_field in zip( + mc.row_snapshot(row), exact.row_snapshot(row), strict=True + ): + assert np.array_equal(mc_field, exact_field) + assert mc.lanes == lanes + assert mc.seed == seed + assert mc.storage_bytes > exact.storage_bytes + + continuation = torch.randint(5, (2, 129), generator=generator) + mc = rosa_native_step.NativeRLBWTStateMC(2, 129, lanes, 20260811) + exact = rosa_native_step.NativeRLBWTState(2, 129) + first = np.ascontiguousarray(continuation[:, :100].numpy()) + assert np.array_equal(mc.prefill(first), exact.prefill(first)) + for position in range(100, 129): + column = np.ascontiguousarray(continuation[:, position].numpy()) + assert np.array_equal(mc.step(column), exact.step(column)) + try: + mc.step(np.zeros(2, dtype=np.int64)) + except RuntimeError: + pass + else: + raise AssertionError("MC state accepted a token beyond capacity") + before_reset_bytes = mc.storage_bytes + mc.reset() + assert mc.position == 0 + assert mc.storage_bytes <= before_reset_bytes + assert mc.step(np.array([9, 9], dtype=np.int64)).tolist() == [-1, -1] + + for invalid_lanes in (0, 1, 4): + try: + rosa_native_step.NativeRLBWTStateMC(1, 4, invalid_lanes, 0) + except ValueError: + pass + else: + raise AssertionError("invalid MC lane count was accepted") + + try: + rosa_native_step.NativeRLBWTState(1, 2**32 - 1) + except ValueError: + pass + else: + raise AssertionError("RLBWT accepted a max_length that overflows tree weights") + + for invalid_vocabulary in (0, 257): + try: + rosa_native_step.NativeRLBWTCompactState(1, 4, invalid_vocabulary) + except ValueError: + pass + else: + raise AssertionError("invalid compact vocabulary size was accepted") + + for invalid in ( + np.zeros((1, 1), dtype=np.int32), + np.zeros((2,), dtype=np.int64), + np.zeros((1, 4), dtype=np.int64)[:, ::2], + ): + candidate = rosa_native_step.NativeRLBWTState(1, 2) + try: + if invalid.ndim == 1: + candidate.step(invalid) + else: + candidate.prefill(invalid) + except (TypeError, ValueError): + pass + else: + raise AssertionError("invalid native RLBWT input was accepted") + + +if __name__ == "__main__": + main() diff --git a/native/uv.lock b/native/uv.lock new file mode 100644 index 0000000..4d3b479 --- /dev/null +++ b/native/uv.lock @@ -0,0 +1,808 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[[package]] +name = "cuda-bindings" +version = "13.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/21/8464d133752951c154feafb3b65c297e7d80f301183d220bec4c830f1441/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120fcc53d57903df529c3486962c56528cba5b7d6c57c99537320ed9922c8b86", size = 6073403, upload-time = "2026-05-29T23:11:36.22Z" }, + { url = "https://files.pythonhosted.org/packages/a8/1f/5ef51f5fbaa5d4d3201bb3d7555af028ec1aa4416275ccbf73c9e34e3d2d/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9851b0caa8bfd3bc6fa054eaf57bea7c8e9c3a62db2d2621224677f49f3c53d0", size = 6675244, upload-time = "2026-05-29T23:11:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/95/7a/c5e3c34a409b148f5c0f5a4ea374158f95d488862c1dffedf9aa5c639df9/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04436a9364059c84b8f9636f359eccda1cf814341f5b670c71d80d2f79dbc708", size = 6674166, upload-time = "2026-05-29T23:11:45.478Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/5e7dba1ba576dd73da5dee894ca076ca5e959450dfff66d6d510a255d1f7/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7855c4868aabc0cfae28abbe83d56734bdfbd08f08fc234ac1912a12858bf49", size = 6025351, upload-time = "2026-05-29T23:11:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/6d2e9047d1fb243dbaa364b01e0297534b9ed7fd27dba1c9f361519cf69b/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e32d08f71ebcdf00f0f41eab2eb37e8da94c8ed411cc9f7f7a019ce6b34abe3a", size = 6657965, upload-time = "2026-05-29T23:11:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504, upload-time = "2026-05-29T23:11:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/ef9b6a63f7dc432712a462c816662e662e00d38caa9b861c8c2588195d03/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2732904099e0a4d4db774a5fc6d91ee95fae065b4d2ecabb4968c5fe2406c9d7", size = 6476660, upload-time = "2026-05-29T23:11:59.188Z" }, + { url = "https://files.pythonhosted.org/packages/b1/81/bff68ce829999c1e4209c761bbf903b1c06ec570416ddb25020864ad5907/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ab2f74ed65bfef4163ba07a8db16f1085e0729291db12a2423aff84ee8278b8", size = 6013639, upload-time = "2026-05-29T23:12:03.509Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e0/c8a1f0c8f9ffdea4f5fe6dbab89b326cef4d85caf489dad39e209da89416/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd4c814d311ec08c981f6dded1dbe7d4b371067ee4f6c14cccec4bde9590f80", size = 6534419, upload-time = "2026-05-29T23:12:05.633Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/83b1f563925b290f2d11a01a77a84013ba56052fe3653a5bef3ccfbb43d6/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3c772dfff49681541d59630c90f858e173ac926b9c593a2b7123f2a1043cc76", size = 5809771, upload-time = "2026-05-29T23:12:10.422Z" }, + { url = "https://files.pythonhosted.org/packages/12/20/e79b4bfe98f075195afb6343d41c498f9dbd2d161d7021d4d28bceb83581/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36febb7c1079d68a981dbbd8d5a67235b399802b82075c9388624719607e52b9", size = 6358584, upload-time = "2026-05-29T23:12:12.767Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/b4/d088047afe39827556df21118cac9ffd20cc3f968c99a7681494d1eb333c/cuda_pathfinder-1.6.0-py3-none-any.whl", hash = "sha256:1503af579d8379c24bdd65528379bc57039b0455be9f5f9686cf8e473a1fce51", size = 54591, upload-time = "2026-07-21T15:03:56.224Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.3.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512, upload-time = "2026-04-14T00:50:08.173Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +curand = [ + { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +cusolver = [ + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] + +[[package]] +name = "filelock" +version = "3.32.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/64/a02e6765de08964ed371eca577870593245afc9dfac16d037de7c10d18e6/filelock-3.32.3.tar.gz", hash = "sha256:0ffa185a3540854c95caa7fa76b76cb219d907415e2c5dc9af25fd970563487f", size = 218135, upload-time = "2026-08-13T16:00:05.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/8e/50f46a9c0ce8d2861a394c1347caae037ea0431d2f67d7feb151cbc4649a/filelock-3.32.3-py3-none-any.whl", hash = "sha256:7f0ca4bcc0e181c60dbbd8aa9ab5b120ebb99e4e064e83636340056f833a1f09", size = 98901, upload-time = "2026-08-13T16:00:03.974Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040, upload-time = "2026-07-28T16:34:51.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.49.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/27/72ae94ea5c8f7349ec1c229d4cd058feb799cbd0833ad6d1b47c919b37b7/llvmlite-0.49.0.tar.gz", hash = "sha256:00f16db782f4a13c78c5804aedc434e46794a77e89999a168f9401106270e50a", size = 194467, upload-time = "2026-08-11T16:26:00.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/0d/daceb212c44cad1115b2d05dd55beafe23ff06627344adb4ded0c661bb1a/llvmlite-0.49.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ee81e96c15a6f870918f1eb60c913551c16aa23defb4f5f1acfa660d6a0aaac2", size = 40479229, upload-time = "2026-08-11T16:22:56.104Z" }, + { url = "https://files.pythonhosted.org/packages/72/2c/eb42378b4f3afc71f9fe172d01f30135dc1d54c7fd95cf76d5445d6f7809/llvmlite-0.49.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:854941c2267fd4fc5b2ce02b8af8ecdffa79fb7784591d3a89370322039ea09f", size = 59890659, upload-time = "2026-08-11T16:23:03.359Z" }, + { url = "https://files.pythonhosted.org/packages/4f/dc/fe880ac1eb93c09b6c9a0539ad18c98778386978a0e20a13a55788044ad2/llvmlite-0.49.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da7b64474ac15ca595efa2644d5c6836638ccf70709fad3aba3fc56a55966928", size = 58344482, upload-time = "2026-08-11T16:23:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/59/f6/5c18be29145cfca1d9e859e55a3c586a8c5a821825017b04c7999cd166c9/llvmlite-0.49.0-cp310-cp310-win_amd64.whl", hash = "sha256:b352c14353330c879e339b8f8d7491d565fe94242697714a24e80bd757202384", size = 41865252, upload-time = "2026-08-11T16:23:20.532Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/ab52de2328e97ca96cdf0331a5f774796bddc420a51768f4501193f80cbb/llvmlite-0.49.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:4b0e710880b7cc910392bd6b9f1bbf468fed99b182e4420d51598f36114b3dce", size = 40479230, upload-time = "2026-08-11T16:23:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/1f/80/0989432d12b7c86a6f5f380eb92eca7de779af9b34dedbd311b694d7da8d/llvmlite-0.49.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a8c0fc9d624bdc30a3d2db11eb2fb98f80fb209d20b37604eda516cd9b699cf4", size = 59890659, upload-time = "2026-08-11T16:23:37.346Z" }, + { url = "https://files.pythonhosted.org/packages/58/e9/76859ca36aaa460b6ae0508e01637f0e9bdb9b59faaa4637ade3b94bbcca/llvmlite-0.49.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20496a5c9fdb8179fb9300e7d19f6782555d98aeeb4a322264aa7fd99f980618", size = 58344482, upload-time = "2026-08-11T16:23:44.199Z" }, + { url = "https://files.pythonhosted.org/packages/7d/49/47cd23e05d52d117b6119871ec299adedc9d8d332a2296964d9b2adc06d9/llvmlite-0.49.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a5b06c1b5fc4ae4c9b169b065f42b719448ef1f873687ef224ef69969b75ec3", size = 41865253, upload-time = "2026-08-11T16:23:50.198Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/3f699ebe3590e15e023a6372dd147526fd8ec398aacf9ceb844e854964a8/llvmlite-0.49.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b541c8fac3450db7574d1f53cf9dff83f285bfed9d69bf81fe71fc2a7d4f97fe", size = 40479231, upload-time = "2026-08-11T16:23:56.773Z" }, + { url = "https://files.pythonhosted.org/packages/be/3c/e97f69c62a2d972066d9a2612ce1f3de313035ac897a5b9f787cad8b55f7/llvmlite-0.49.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6acba646d88abbc87d5c113a3d62c1fbf8b8fee11c6493f516803e30f21ae870", size = 59890658, upload-time = "2026-08-11T16:24:05.451Z" }, + { url = "https://files.pythonhosted.org/packages/69/e6/e942ee08605fc0526ff3854260c384d8315a5830e16c4c2a5aebc14dc9bf/llvmlite-0.49.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ec8ad805e7515cb8440a690eb3cef4d34acb29eef80b705ec4e1c1ad3c43c68", size = 58344481, upload-time = "2026-08-11T16:24:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/84/49/2a44871cac6b5a2fd4aabd68cfdaf6de9a5c7edb36dee5d47b77bda4b50f/llvmlite-0.49.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a9c9e3af4e214acfefa4f73ebe7bc3fb35854a62b654edb3953f5ae33c08ba3", size = 41865543, upload-time = "2026-08-11T16:24:20.41Z" }, + { url = "https://files.pythonhosted.org/packages/7d/85/0b536a3c59f2636d9dd51dda832b6c1d0ffec37608429dedf128664918f1/llvmlite-0.49.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:039fa4054a06f537fb39248d4472284ca96be311a142ec09e69f95630ab469cc", size = 40479230, upload-time = "2026-08-11T16:24:27.295Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1c/ca8ba47b057b793099784475499771780ec46839f2782f753a7079d23520/llvmlite-0.49.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ddc7aecd4f56397ed6e8f120ec5dcd5a1a8f0e6032ca4af413462792d4dca2e3", size = 59890659, upload-time = "2026-08-11T16:24:35.595Z" }, + { url = "https://files.pythonhosted.org/packages/de/af/9526dfdd33a923f33e29a18b8f9801ee7ee4b7397e88d28192c1024c4a75/llvmlite-0.49.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3dee64784201b64c13a8df62c48a4f4218858faaa65889866bb29bdc243c038", size = 58344482, upload-time = "2026-08-11T16:24:45.79Z" }, + { url = "https://files.pythonhosted.org/packages/96/7f/9f5afcf6476b228d6b170408f377a0c4f91477fc1fc91f8141088b45bf46/llvmlite-0.49.0-cp313-cp313-win_amd64.whl", hash = "sha256:a1b414dc6b164738ec39dd8987cea73829057b7dd92fc6d91b52838385fc1dd2", size = 41865544, upload-time = "2026-08-11T16:24:53.962Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/16599b8c9f21802448059482eab48a9e74086dc56b901a677ba355565e64/llvmlite-0.49.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:80a84683d04516bb51da1bbeebddaf2c2f558809c93078a8f91807909ae331f8", size = 40479230, upload-time = "2026-08-11T16:25:01.513Z" }, + { url = "https://files.pythonhosted.org/packages/3a/61/0b23849141a4c4e7091fcd158ebb45195896974bebca3e58fee7cad4b4f4/llvmlite-0.49.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4281a0171d66d2098adce4ba706b8c550b1b10718650f682d64cde16e84e4de5", size = 59890659, upload-time = "2026-08-11T16:25:08.733Z" }, + { url = "https://files.pythonhosted.org/packages/c4/92/628692b74b31e27af9ba7e8ba651941ee4956959d5478123c453f59aad4a/llvmlite-0.49.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b095f15fb12c4d90495df5b1a3772b4732cc408398b204a787dbedd370e09c69", size = 58344479, upload-time = "2026-08-11T16:25:15.731Z" }, + { url = "https://files.pythonhosted.org/packages/96/8a/412fc273521b02cbfe0b5f8ad56cc696385f6eaeecdb9e9ae6a90111d98d/llvmlite-0.49.0-cp314-cp314-win_amd64.whl", hash = "sha256:294e2f0b70aef8f92d0ae7b203e2609f08beb39437eee73de59a21669331aae9", size = 42986588, upload-time = "2026-08-11T16:25:22.534Z" }, + { url = "https://files.pythonhosted.org/packages/fc/15/f47cf45c00c8b165ac3d268502dcb21d900e86f27fd338268a66ce922ab0/llvmlite-0.49.0-cp314-cp314-win_arm64.whl", hash = "sha256:95d1071023ed858b79f6971954fd7cc1f5dbcbab987718a4ccbe1411e47d0b81", size = 37441881, upload-time = "2026-08-11T16:25:28.324Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2e/eafd488766d1c02413cba24f7b22acb9b3ccdfd8407e98d30eb16bac4e2a/llvmlite-0.49.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:f3f2ff0aeb17d34fcce9f79b99baac441cfd3efa41b83e233ca4530a72381f72", size = 40479230, upload-time = "2026-08-11T16:25:35.125Z" }, + { url = "https://files.pythonhosted.org/packages/98/07/a2c4f04e2111ccc274b4d5e3331398a9dcf6d6e5e55d6444b1ad9d6381cf/llvmlite-0.49.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5555ea1d63928481cbf7fcb1d67452b216c7e5b393a4eb7aa1401e67f2a4fc4", size = 59890658, upload-time = "2026-08-11T16:25:43.294Z" }, + { url = "https://files.pythonhosted.org/packages/80/f9/7b7b50f80b4585bcd78675ff3110c256877b11df32a8cde284f851762f57/llvmlite-0.49.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32adb84fdaae28aeb86fdb6253084ee707ee157289a2e98fe3caf48a62bee82", size = 58344482, upload-time = "2026-08-11T16:25:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c6/32d68bfbf1d0c36888530ef6fd72864861af23dc546302b41033471a8c3a/llvmlite-0.49.0-cp314-cp314t-win_amd64.whl", hash = "sha256:be637e465010bc9c50f070468f7f1cf5385e92fee364d192dd5e6cea790ecba9", size = 42986602, upload-time = "2026-08-11T16:25:57.69Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "numba" +version = "0.67.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/90/2544f4e3a61e501d6c9a5418fd4b905323222693d54a02cab0106a0af865/numba-0.67.0.tar.gz", hash = "sha256:cd75aa535b33fa05d9d930b1ae8af9f97a2881e96d72dfb38ec9b78284d9f851", size = 2836515, upload-time = "2026-08-11T23:04:00.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/2e/6e72b3edbb7c7d6b44b2ca9e1b62e91997415d181541ef47fc6957c59bf2/numba-0.67.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:8c0e88acd4341ddf40779db3c0228b9188aca7fcab5f5f3ce9949a1fc71e9a02", size = 2745135, upload-time = "2026-08-11T23:03:08.321Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/5358f24235ef1a5a80b7e28f3e1baa886c0bcf07dc68557009284e6ba698/numba-0.67.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6c8e9ba3f9602471e8c6f563ffcce8db8046741f0bafb782a052e41dc6b6861", size = 3821881, upload-time = "2026-08-11T23:03:11.172Z" }, + { url = "https://files.pythonhosted.org/packages/0e/18/2f00694248e32c53812baf3d36a7c656dbdd667c6993087b3da068f74b02/numba-0.67.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:694c81c6560b2b47e5fc1dc39c29175b907adf862d9af0af801453400a022a61", size = 3528397, upload-time = "2026-08-11T23:03:13.107Z" }, + { url = "https://files.pythonhosted.org/packages/7f/39/4175b074929938011bd4b564beb4e0fcffd46252e01f60602b57ffb02b06/numba-0.67.0-cp310-cp310-win_amd64.whl", hash = "sha256:ed333e0af4386294e7f03e550e01411856b6935e717d859225e0a7338c6b6795", size = 2815861, upload-time = "2026-08-11T23:03:15.072Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ed/55ba4e54ee878396de6b18e6533cc4a92fa519e8c82d55cf40f98c0a6831/numba-0.67.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:3fa3d1b27f96f2c0d54513d953d7197886aa1eaa7d2439a0eedc44d993fb181a", size = 2744821, upload-time = "2026-08-11T23:03:17.321Z" }, + { url = "https://files.pythonhosted.org/packages/be/78/3f3c45dbaec3cf02bbb1825731beca50f591227e95143d6bd7a64897641c/numba-0.67.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c80c847301dc33dc8f84a97a952004023d9a05578ae4512b087176264cc1960", size = 3827182, upload-time = "2026-08-11T23:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/a5/24/4e70cb86534283d859c3aea2302da523e41539b98dd6c3c4d0a42af95cda/numba-0.67.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7a7b0121466f1e9a8a074b0545fe90e16389623abf979b5d7c299dca1294d7e", size = 3532817, upload-time = "2026-08-11T23:03:22.06Z" }, + { url = "https://files.pythonhosted.org/packages/26/4d/23dab7f4233be0fc34f54a169ed85238467cd24d8adf2498e5c12ea19dc7/numba-0.67.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfba1ac34f0363fb1a250a10e97240780d11e05227892f7286b26fbfd0ad58ce", size = 2815700, upload-time = "2026-08-11T23:03:23.812Z" }, + { url = "https://files.pythonhosted.org/packages/0d/58/915cddba90010348ed0444451132fdde9b000bcbaff1582029b5bf115d11/numba-0.67.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6004d8d5f28d4028687fb2d972d629295b13685943bd2ed5cd8810c3b848e219", size = 2745050, upload-time = "2026-08-11T23:03:25.607Z" }, + { url = "https://files.pythonhosted.org/packages/bb/38/926757caaac18a66f057d7544a63620bf360a07d281c9f7ecadd2aa83963/numba-0.67.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f63d43db06b4756424d6d2484737c902e0ae944a0eec3e8b0b4de2c695b15caa", size = 3884596, upload-time = "2026-08-11T23:03:27.688Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6d/58291dc58da39d98b32db7f044729f6d8d4920cd9622fbab3179b54ff4c4/numba-0.67.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76d3335aaeffb9dc88309420890e73497a00be08a7530441bc2b58ffe025bfa5", size = 3585290, upload-time = "2026-08-11T23:03:29.684Z" }, + { url = "https://files.pythonhosted.org/packages/6e/63/ab21828b4056afed71f9ecb40f4de26c2c19de731cc001961aca74b79464/numba-0.67.0-cp312-cp312-win_amd64.whl", hash = "sha256:50e2b72406c18cda5dd7431b0082cb85ea94e06c64c33607248fc8bef92cfb81", size = 2815645, upload-time = "2026-08-11T23:03:31.732Z" }, + { url = "https://files.pythonhosted.org/packages/49/dd/bd9fe772f6c84597b76cac229b3f2890f01a2c64fd70e48ceaae10dd65cb/numba-0.67.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:77e1c7173fee57a0d84e006c7e70346689d6cb3e7db503489bae58646b4eff7b", size = 2744872, upload-time = "2026-08-11T23:03:33.649Z" }, + { url = "https://files.pythonhosted.org/packages/a1/1c/c05609739cc41116d36e30cb2b41fb00f126bb52e1b0bac907051ad8a35d/numba-0.67.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9c4953387c77864b596d8296e2cfbdef82b0eea4166ab4864b05d226c51143e0", size = 3892004, upload-time = "2026-08-11T23:03:35.797Z" }, + { url = "https://files.pythonhosted.org/packages/4a/77/a5276ad4178250403e0e2251f3e1f8ac18feac779b0474a8bcb08558490d/numba-0.67.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88f6e0f5cb6c545e158b6ef0496c01b6d6958a7ccc6634a1576a94bbbab29ff2", size = 3591878, upload-time = "2026-08-11T23:03:37.845Z" }, + { url = "https://files.pythonhosted.org/packages/0f/80/d48f0ba7442516ceb5a1585f0c81d3aa531bc96bfcabcd9f8f925768c426/numba-0.67.0-cp313-cp313-win_amd64.whl", hash = "sha256:b68ad5125fe245339cc8dcc036081fc1ea482c5063387b9612a76ccd83dc91cd", size = 2815504, upload-time = "2026-08-11T23:03:39.736Z" }, + { url = "https://files.pythonhosted.org/packages/d7/16/345b1e4774a08247aafcfdb93d4e8d24a3646366cbe72de33053fc0de1b5/numba-0.67.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f99f880ff25f418a67f9a1d00d0ddfbc63430f627b523e515085a592a7567f4b", size = 2745088, upload-time = "2026-08-11T23:03:41.864Z" }, + { url = "https://files.pythonhosted.org/packages/1b/36/e614ba2bc0f005ed0f37a6413f08fe705210297ddb9a37a475a8b9fdab61/numba-0.67.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269245a675abdd3e2c35ec6bb2f250355effa9032514d8f2354f0d2d10854bd", size = 3861040, upload-time = "2026-08-11T23:03:43.842Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/30c42a1dbc4176cf355e8e8be61803732c55597b1332925fe233912a43d9/numba-0.67.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f074a8e23db78490f11a3930c940be758316c10ac5985be83d2f298dc080acf7", size = 3561811, upload-time = "2026-08-11T23:03:46.037Z" }, + { url = "https://files.pythonhosted.org/packages/18/6d/21bd16f770476e394c5e5f504935817967442a71251d6b86c244a2767980/numba-0.67.0-cp314-cp314-win_amd64.whl", hash = "sha256:4d576e62bf2c9370f61312b51573c4bb1f3fe96798bbab56730847a368a316c4", size = 2817421, upload-time = "2026-08-11T23:03:47.922Z" }, + { url = "https://files.pythonhosted.org/packages/95/06/bb41b0e59b9ff52c94a2f01db24f6437df058caebb377b5f372fc343a6a2/numba-0.67.0-cp314-cp314-win_arm64.whl", hash = "sha256:7930748ce8355d2a5a28602abab056a61fdc676d17377f27d17993905428171f", size = 2788885, upload-time = "2026-08-11T23:03:49.967Z" }, + { url = "https://files.pythonhosted.org/packages/10/7c/aa07151fbd0f4283f78de437cc196f9084789be89a2b4de3fdc2f6a4b414/numba-0.67.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:4a2ed006635bbd0fe45681ed49f3b4f4bad1abf0c233bcc5842c9e3a34cabd61", size = 2748150, upload-time = "2026-08-11T23:03:51.755Z" }, + { url = "https://files.pythonhosted.org/packages/74/62/b8174ca95a4cc1a7ba1520767734e016991545590b8fbde521b681701a9f/numba-0.67.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa5f002f665bec321b950dacaa26ee009e1d720f6ac9d9856eed5efe1caa03a6", size = 3896986, upload-time = "2026-08-11T23:03:53.752Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f9/3a7b6dbf81e01a48958b45ad2239edbc64707522ab17f11f9f18c44bf6d1/numba-0.67.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83ab968b0e0fa744eba03351282dd8000796e6ec8e4518f47bd3ed86c0a20c7b", size = 3614644, upload-time = "2026-08-11T23:03:55.794Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5b/248f5681c121ca853a9f4e39d342a3e01b8a0261b0275853eb3d0d56aa20/numba-0.67.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00c964a5b94d3ae82d83ac162cd610755875b98dadb779fdde06e6bfcdbca47e", size = 2822870, upload-time = "2026-08-11T23:03:58.097Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693, upload-time = "2026-08-09T13:44:51.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109, upload-time = "2026-08-09T13:44:55.501Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202, upload-time = "2026-08-09T13:44:58.401Z" }, + { url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736, upload-time = "2026-08-09T13:45:00.813Z" }, + { url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696, upload-time = "2026-08-09T13:45:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264, upload-time = "2026-08-09T13:45:07.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396, upload-time = "2026-08-09T13:45:11.316Z" }, + { url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044, upload-time = "2026-08-09T13:45:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817, upload-time = "2026-08-09T13:45:17.867Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674, upload-time = "2026-08-09T13:45:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131, upload-time = "2026-08-09T13:45:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595, upload-time = "2026-08-09T13:45:27.323Z" }, + { url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845, upload-time = "2026-08-09T13:45:31.638Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880, upload-time = "2026-08-09T13:45:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264, upload-time = "2026-08-09T13:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566, upload-time = "2026-08-09T13:45:39.518Z" }, + { url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995, upload-time = "2026-08-09T13:45:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511, upload-time = "2026-08-09T13:45:47.094Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609, upload-time = "2026-08-09T13:45:50.808Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204, upload-time = "2026-08-09T13:45:54.111Z" }, + { url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532, upload-time = "2026-08-09T13:45:57.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725, upload-time = "2026-08-09T13:46:00.478Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f8/c3b222bf075b50afd8e949a07a15c4b312a4a84bd8102a332bcd953cbbb4/numpy-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1", size = 16885180, upload-time = "2026-08-09T13:46:03.939Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/2c1d4b1987795a92b5bbf7c24fe249ab96aa2573ab0d7604802c189d7b86/numpy-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8", size = 11907878, upload-time = "2026-08-09T13:46:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ee/d08226fc858044355983a6e5b94f08ff6f3969e0a2b160a4a89f0ddb3445/numpy-2.5.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323", size = 5354922, upload-time = "2026-08-09T13:46:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/6d3d933056440ebbc5e6bad92065fc6c26a48a84a36b1208580e94eea76c/numpy-2.5.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e", size = 6679168, upload-time = "2026-08-09T13:46:12.275Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/ecd49dd90033cceb2704d88ca905d4d7d89b0e8c739608754ffd325fa820/numpy-2.5.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65", size = 15624501, upload-time = "2026-08-09T13:46:15.322Z" }, + { url = "https://files.pythonhosted.org/packages/c7/99/461bd36dbdfac6c1c53efa370bd55a83227542d0d118f1677dbf1a3dacd5/numpy-2.5.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee", size = 16713701, upload-time = "2026-08-09T13:46:18.949Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9c/2b251df9e8a5d647b62b0cbc1b90a91850c1cf4859ecb532fd0b4eacff6c/numpy-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68", size = 16986065, upload-time = "2026-08-09T13:46:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/8f/25/20de43f53ff1390534a124475055a19f01fe10c920a0fd11b8e18d6d6052/numpy-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb", size = 18470031, upload-time = "2026-08-09T13:46:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/56/5e/0c577ca308d6da5eb79b546ba10bbe5b60148192194e2da060913b1de4f1/numpy-2.5.2-cp314-cp314-win32.whl", hash = "sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98", size = 6121028, upload-time = "2026-08-09T13:46:30.046Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/7bcbd5b11f94199073320410cddcbb80cee62415bfeb540874b265c2d922/numpy-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d", size = 12597627, upload-time = "2026-08-09T13:46:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/87/bc/4d0b06fba0da90ccc75af62823cb9dcedb6c9ea0cffa058cb2c9ee773a77/numpy-2.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf", size = 10680414, upload-time = "2026-08-09T13:46:36.036Z" }, + { url = "https://files.pythonhosted.org/packages/cd/17/f429aac9dc08833a0d0f188eba38c532a751b1a1f2ca6018a37b455cb321/numpy-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1", size = 12026967, upload-time = "2026-08-09T13:46:39.084Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9f/d0849de96a2a4ceaa16662f18ee13eaa9c0aa418269fdc8c4857c56b11da/numpy-2.5.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f", size = 5473874, upload-time = "2026-08-09T13:46:42.075Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/8df216d4a4a5422a3de045301cf7df8ea47286d76f5cb7160b0128ac26b7/numpy-2.5.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf", size = 6789276, upload-time = "2026-08-09T13:46:44.387Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/20d7e9891c4ddfadd6ff8d95bf4b29f353d8e1770553de2099880551dfb9/numpy-2.5.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4", size = 15659154, upload-time = "2026-08-09T13:46:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d6/f3aa3d2688bf501b858835c6bd087ae9b51a56ae6fca8e2b0990abd177af/numpy-2.5.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce", size = 16748909, upload-time = "2026-08-09T13:46:51.442Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8f/1c5cae8d2baf86ab802ae97a00be55bc7e21ebc11b12bbc33376c5f05342/numpy-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26", size = 17027685, upload-time = "2026-08-09T13:46:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/5c/27/71d3467404aedc1c24ce79610f91b52b0b0f466c43a701aa56fc75c145ab/numpy-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac", size = 18501181, upload-time = "2026-08-09T13:46:59.09Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/42921d27c40aea7e077f4a423ae509fd9220b028cd787bafefd8ab2b3a5f/numpy-2.5.2-cp314-cp314t-win32.whl", hash = "sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba", size = 6271085, upload-time = "2026-08-09T13:47:01.903Z" }, + { url = "https://files.pythonhosted.org/packages/75/e6/bad5f5d56de9b1971bac959963dda276d35c40f1854475005434bbe08692/numpy-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884", size = 12787971, upload-time = "2026-08-09T13:47:04.963Z" }, + { url = "https://files.pythonhosted.org/packages/df/05/f608795cb34391acd67e38d94a3c36abd8d8576293a3a80727d7595c372c/numpy-2.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e", size = 10750306, upload-time = "2026-08-09T13:47:07.976Z" }, + { url = "https://files.pythonhosted.org/packages/33/c6/28de0191c5f82b7d42a0a51390ba98587048aa93a39fafb05bdbe6e8d00c/numpy-2.5.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:078f9b027b478c9379b9677babbf0f8b8f1ecfada27636d7b9a93990c638739f", size = 16885274, upload-time = "2026-08-09T13:47:11.439Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/973ca116000d244897e468ea1aff30b589e5022e3c8744b71706fe33bd57/numpy-2.5.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:50a68f4bacd8a2b33d8da3d2269d0d78500f86ea582e4786dc10f5ef2c2c6842", size = 11907846, upload-time = "2026-08-09T13:47:15.128Z" }, + { url = "https://files.pythonhosted.org/packages/78/d9/8c4b3937ef204cb2fd88d389ccd0f265a2ffb11f35a01d2064cf46714bd6/numpy-2.5.2-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:e79aba74ffaf5f78a050d777c184cddf8fdffabab38acf5f3ef1fecbc17895d6", size = 5354892, upload-time = "2026-08-09T13:47:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/b6ee65ea2999fdb7023935e108e6fb776ee4082aa15f159acfa857e578c8/numpy-2.5.2-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:9a0731745a72a184490a582fb4af2533512bd071ace67785b5fdffc0ae58dce8", size = 6679309, upload-time = "2026-08-09T13:47:20.456Z" }, + { url = "https://files.pythonhosted.org/packages/43/f3/acb18d8b137a393c8e7803a8c994c9e64bde3930692a69d826993113a159/numpy-2.5.2-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ec954036759bcee3aa484f8603bd9c14f3e776293b85578b8734c2d72777c69", size = 15625850, upload-time = "2026-08-09T13:47:24.365Z" }, + { url = "https://files.pythonhosted.org/packages/a9/bf/a8e9bb0db815a0e265b5744ebedd3af0bd5faad8604e5b50a1cd012f3c91/numpy-2.5.2-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc649493697006bc90614a5f0bbc8cb3cb1866715c474e473694968d7e6b99ab", size = 16713664, upload-time = "2026-08-09T13:47:27.965Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/6e913736b3dd6582344af32418b5fb9dab34282e8a8174ae1d54ceb0fc13/numpy-2.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cf7de32f486e4ac9e2d93b810f9e9ac72a728dd46a32a0bb403222f27f653514", size = 16986749, upload-time = "2026-08-09T13:47:31.541Z" }, + { url = "https://files.pythonhosted.org/packages/80/09/7d3b23eff5c7428ef6c01e6f7052bb60d504c4d33e317b36b8959c24ad97/numpy-2.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2ffa7bacab3e2ee1b19ed31766bb60bb380b68c23f051e199c5cc598afd68710", size = 18470495, upload-time = "2026-08-09T13:47:35.364Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a4/68a321d825374f6eb677ffe8ef8c6b9a328304e6fd2e39d9530822776607/numpy-2.5.2-cp315-cp315-win32.whl", hash = "sha256:6b588cc8f902d6bff201c19fd00c43ab8545671e3554d014e12e14139e5e8617", size = 6120696, upload-time = "2026-08-09T13:47:38.561Z" }, + { url = "https://files.pythonhosted.org/packages/c8/23/deafbb1700f79fae9cd1e91220f133d124cc267de1b584da3fbf6db2f6cd/numpy-2.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:07d4e89f3a9ab0a9ba24264ccdb642b3dd951b2281e8883a5481a4aa79cc31a7", size = 12597324, upload-time = "2026-08-09T13:47:41.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/cd/3272ba105e3bbbdaeb11357eda31e7a6825ffe159e8171665660299a948f/numpy-2.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:a610dc7e3c52edd39c2bc2375ff9c3fd59cb3ad00e4472d36f83bc1457145788", size = 10680466, upload-time = "2026-08-09T13:47:44.873Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0e/58370637b1bb70a5c9ce2b43f4b521ccb224e36ccb76a6596b17ae4b447c/numpy-2.5.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:40f4d451aed46a8046a1aae41c4e55fb3612273df9c502480135e1501576a34b", size = 16993947, upload-time = "2026-08-09T13:47:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/93/2abcb807712b289d6d60fe4cf30532f98974a8396d885650f3ba5a13026e/numpy-2.5.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c081cbe16ba1ab53078e5ff29013621e33c509eedab055775d956427712c236e", size = 12025331, upload-time = "2026-08-09T13:47:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3a/2898e003a5fbaf87e76c039b4ee1f5eb390471b4ffe74887c1f34c4e791e/numpy-2.5.2-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:0090ccdd57ec2703e9b49d0bf554767370581c1dd0a6b2bb2b2d9def317d042a", size = 5472336, upload-time = "2026-08-09T13:47:55.403Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/23f69d07c544597b29758b31b55c27dc9d541012a2c1496189fef702aec2/numpy-2.5.2-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:6a9bb119fb8dd21ba30b3f0e555b7e2b081bd9883af21ec9c1c633d161cda3a8", size = 6788387, upload-time = "2026-08-09T13:47:58.192Z" }, + { url = "https://files.pythonhosted.org/packages/15/ea/c0dbdbcf22f43782510a3e492dd3da73c6112b69cac8929d16d127536fc4/numpy-2.5.2-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a839318485284a6fb31be4f8f2c91c8f2cb22f4543c4a8903f12b0671ffe07cc", size = 15667096, upload-time = "2026-08-09T13:48:01.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5e/29c73c31748cdb0f7566642125ba17fd5b56780cddf891b085dab27e4466/numpy-2.5.2-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba0a474801b8dc67b66bf465548abc90e82b44d2611b5770f33008dcabffe8ec", size = 16751730, upload-time = "2026-08-09T13:48:05.706Z" }, + { url = "https://files.pythonhosted.org/packages/47/95/02501e8454796bb58dadf7a99d3181e0b464bf264e1003039572f9779fac/numpy-2.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0a4035ae1129ff8777f08bfbd44f1e5d8e9c049ce0c2dd78fc0d92c13e7251c0", size = 17038686, upload-time = "2026-08-09T13:48:09.627Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b5/53a681d91b5c82687067d8ea5035e02d917b5509d6f334cb06484a954714/numpy-2.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:77843ca236b777e67f8d6b3660ea116e499612703a0ecd7093f316201eb9d8e2", size = 18507727, upload-time = "2026-08-09T13:48:13.744Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/6e11443f7b64ee376c860506091103bf68f92d2cab9e8d96d4501babf07c/numpy-2.5.2-cp315-cp315t-win32.whl", hash = "sha256:7354826bc6f8f69402e9b7fe28d15fcd34feebd74f856f111585c5b0c9fb0251", size = 6269775, upload-time = "2026-08-09T13:48:17.543Z" }, + { url = "https://files.pythonhosted.org/packages/f1/18/195d6b86cd72dbbc501edfa778005fa6b87afd34c153e46028cd3a0938f4/numpy-2.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:e5651f3f87add730ee6608d915009e19c911fba0cb000c7e3ea994b7d768eb12", size = 12782559, upload-time = "2026-08-09T13:48:21.023Z" }, + { url = "https://files.pythonhosted.org/packages/b4/07/458c344f0f0c178f4481dad5cca790626ffe4c34eabf9467069d06ee4999/numpy-2.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:5f8e00be2ec6f45f4e8a41a527f68d44a7d96fee92a650e4d8b1326f77f61e6e", size = 10748103, upload-time = "2026-08-09T13:48:24.21Z" }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.20.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.29.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" }, + { url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.3.33" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/ee/580ca6f29dcab0221db8706badca1bbbb084f1975c4d4e83329c3a7e31f0/nvidia_nvjitlink-13.3.33-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:26a6de7fb4c8fdaa7703d3dad720d6d427ddfea5c48a528fd97c11733ad830e5", size = 40742423, upload-time = "2026-05-26T16:54:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/69/30/45414e35ff2eee7db3da037e5707037ccf9d2b5218ffbdb055ea4d5aa98a/nvidia_nvjitlink-13.3.33-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce48b37dfeb3cb1eae4cf85adacb47d7a6539ea2272870c9a3628ce275c2037e", size = 39168635, upload-time = "2026-05-26T16:54:13.906Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, +] + +[[package]] +name = "rosa-torch" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/a7/f7fe036ff6f52e49f4a4a1a77abccb111d4545d9bf84e9968d301dc9a8e7/rosa_torch-0.2.0.tar.gz", hash = "sha256:86495aaa5102bcc89573e81386b292231e88493193e40f396838e3086dbfd815", size = 41687, upload-time = "2026-08-11T13:25:33.697Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/43/1ec2e812c6571a374ab1e9b17926ca96d250ffe9b10ad4052c86ad0f35ed/rosa_torch-0.2.0-py3-none-any.whl", hash = "sha256:992b66de7845b5fc3e537c5cf53e1f69aab2fb4b5ea5f292e00c5ea1c583cb10", size = 43645, upload-time = "2026-08-11T13:25:32.114Z" }, +] + +[package.optional-dependencies] +numba = [ + { name = "numba" }, +] + +[[package]] +name = "rosa-torch-native" +version = "0.2.0" +source = { editable = "." } +dependencies = [ + { name = "rosa-torch", extra = ["numba"] }, +] + +[package.metadata] +requires-dist = [{ name = "rosa-torch", extras = ["numba"], specifier = ">=0.2,<0.3" }] + +[[package]] +name = "setuptools" +version = "84.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "torch" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-bindings", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/e7/19894fdb51c7dbaf94f5a79bb0871da0992e8e4241e579cb006da46d2e58/torch-2.13.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:94f0de129916f77b8dc2c7a8eff644cfeddfe59e39c9f55e9f6e17543410281d", size = 111178962, upload-time = "2026-07-08T16:05:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/d1/5c/b1d5de470c54e339b30a92d96683a71bcebd78f5f2a7fc714cd6dc6bbd68/torch-2.13.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:0ab4b69f3ee03a62a002cfbf77b1ca5e88aceb4ea64cb4388bb28f638ddbb045", size = 427198333, upload-time = "2026-07-08T16:05:36.847Z" }, + { url = "https://files.pythonhosted.org/packages/50/c0/68a84105e1fcb8970144b388ff3d3e5dc15a3be28c1e247841f7d7247e41/torch-2.13.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c78b7b4d04461855a764cf01bae9a462bb88bc93defcfa11235cbc8fdf3e12c4", size = 526555154, upload-time = "2026-07-08T16:05:06.507Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c9/0bb9d097b03cbaf96bb75b15e867347b8e41bfcdfe0539452d17d9e63993/torch-2.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:2bd30b6b730d987fa386ce3898933762c5cb8cc82eb0535211d787cc3ce2dfeb", size = 122015602, upload-time = "2026-07-08T16:05:45.25Z" }, + { url = "https://files.pythonhosted.org/packages/5b/fe/cba54dc58523434919b66f13a667e36e436deddd77ca519e96553617d4ec/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", size = 111187938, upload-time = "2026-07-08T16:05:17.065Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/1e3160e18e12aa3038390efab3ce02b36a9d4d6a527ecdd8520dca2e68d8/torch-2.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:092790c696a760c729fd5722835f50b9d81fd7c8f141571f3f3cf4081a8f664c", size = 427199369, upload-time = "2026-07-08T16:04:51.054Z" }, + { url = "https://files.pythonhosted.org/packages/01/79/1f2d34ad7034ee1c7ffc1cf8bf0f8213af2a81df6ecdb3997ecec107c09d/torch-2.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60fcdcb2f3876e21146cb4524ef06397d727ca9ad5f020818547e25075fe3cb7", size = 526574961, upload-time = "2026-07-08T16:04:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fd/0f2ce40f58aefbdb3392f9acce3c8171940943ae2d661f70558bfa73befb/torch-2.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a0d8b11f16a48d60e2015d8213aa0390744cbebb98e58b62b3514dddc656e330", size = 122015870, upload-time = "2026-07-08T16:05:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3a/ed0f4d4d1dcde03bced7aac9a28e800abcdc0cbd06b6775044c9fbd877b7/torch-2.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2fe228aba290d14b9f31b049be550dbd469c3fd3013d7a19705b30454da97027", size = 111213045, upload-time = "2026-07-08T16:05:22.997Z" }, + { url = "https://files.pythonhosted.org/packages/df/a9/f6a2a4d763ff1df02e9a64c477029db614295bc9367f4131223791ccc243/torch-2.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:572df8be8ffb4599c88cbd6a0726f1f854f4da65d2e3c09f0e2c2283333cd6d4", size = 427210998, upload-time = "2026-07-08T16:04:37.708Z" }, + { url = "https://files.pythonhosted.org/packages/f3/82/fea946351658e6534db52d2cc12bc53087cbf87f9440c5f180f367c1950b/torch-2.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:796633c4cdf0fe2cdced72d8f88f22e73dbcfce83132763162f6d4bff13b820b", size = 526605292, upload-time = "2026-07-08T16:04:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/21/d6/e8f3c6f7e01f626f77259de9860d2a78bc84c40539e28e79b7e98b0bb659/torch-2.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:024c6cc0c1b085f2f91f20a3dc27b0471d021c31ce84b81be3afdc39f791fd9d", size = 122057313, upload-time = "2026-07-08T16:03:53.43Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fa/c1c10b7aff4a9a3e8956d4f0a5f468fa6db7abc3208805719076772b4833/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", size = 111213743, upload-time = "2026-07-08T16:03:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/11/18/9ecb37b56293a0be8d80f810bf672a72fe7e02f8b475d5ef1b9bf8a0d748/torch-2.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1e09d6a722504957c694faceca843acde562786df1144ebcc5a74075ec7f6005", size = 427213008, upload-time = "2026-07-08T16:03:44.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5a/7c50ba1b7b713d71d34669c6d13dab0a11531a3eceb0307a5162dbfec0f7/torch-2.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a3a9a21312872af8a26950b2c15680335a386a1f56ed03e780653d78b9607e9e", size = 526602329, upload-time = "2026-07-08T16:03:12.649Z" }, + { url = "https://files.pythonhosted.org/packages/91/3d/e7adcc6aaf36961cd18f56cf8ad0f3058c3a5c84ccf391762176c94581b8/torch-2.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:49b58f1e2c52440abb6f17c28f0335fe6c6d01ad1a7f55b0183b81e4b34d64e6", size = 122057920, upload-time = "2026-07-08T16:03:01.808Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/6dcc7f0c07052102dd36f83cbc5800842a909c8c3fbf1a7f8a5844954de9/torch-2.13.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d849b390e07d8d333ce8ecaf91b273c656c598379a19c9acf1318a883f6b391c", size = 111227066, upload-time = "2026-07-08T16:03:33.6Z" }, + { url = "https://files.pythonhosted.org/packages/e9/09/2c10e8cd0e00fa5d23c052df6ce467eaa7182399f5e0f824f1e4ff42ccae/torch-2.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a3893dc2da0a972a8ca5d698c85a9f967559ac5f8ee1797b77408aa8734d073c", size = 427226309, upload-time = "2026-07-08T16:02:53.127Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/22c2102bbef14ca6a6cb4c20e42f088e49c5f812be4e160ae57502e325f9/torch-2.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:49f1ea385c754e54919408a9bb3b5a72b0b755bbe2c916c1d6f70afbec4908a2", size = 526614507, upload-time = "2026-07-08T16:02:16.441Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0c/7d1deb6bce5bc3e6042caf39100ac768eba3b9a098e1dddd16f75bd6489b/torch-2.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f8573e3ce9ebcd53fe922f01077a6085ccdfbe5f12fd215883a9d87d7a744fd", size = 122051871, upload-time = "2026-07-08T16:03:23.521Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ce/aa8b7f9949d32e0f2f624f342bc3b48112c1b8a130288465938bc83bcbf9/torch-2.13.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c28def70706c2f9ecc752574766e8ae4da9b810ab6676b611166761a78a9f1e1", size = 111537025, upload-time = "2026-07-08T16:02:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/69/d1/491e3a0389430946145888b0203f2b6a759ce2a61481b96a85c2da4f2ced/torch-2.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:31061ff56ed8fbf26c749806905aeb749ebeb819810fd5d52508aa5afd90dddc", size = 427219769, upload-time = "2026-07-08T16:02:31.18Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1d/38006e045bf0a1fc28ef01e757c554e59e59a8770c284bc4f47b14e60441/torch-2.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:cc26eead4cf51d0b544e31e364dcf000846549c273bd148936fe9d24d29acb92", size = 526571320, upload-time = "2026-07-08T16:01:59.348Z" }, + { url = "https://files.pythonhosted.org/packages/56/94/655c91992a882bd5071aa0b5d22a07dbb130d801e872be97c0b627a7c693/torch-2.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a7de8a313090dc5c7d7ba4bfe5c3be222528f9a4dba1acc83bddb1157360c4b8", size = 122306773, upload-time = "2026-07-08T16:02:39.832Z" }, +] + +[[package]] +name = "triton" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/ea/629cc37436ca5df93ce98956d09cd2ca1498bfee8ef4972d2fe48b9f958c/triton-3.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3daf64305d6cea88d3334c65ebc9bcd0c64c9564a977084366aa768d57cbcf64", size = 184551013, upload-time = "2026-06-17T20:03:37.551Z" }, + { url = "https://files.pythonhosted.org/packages/15/76/c79c34311625227a288df3e483fc5cdf3d596624cbd4b4758c4cbdc14af3/triton-3.7.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee89fbf782ec2ad50391dd1cf26cbea4f4467154c37f4773026da8fc31c0f58e", size = 197596267, upload-time = "2026-06-17T19:53:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, + { url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629, upload-time = "2026-06-17T20:03:53.444Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241, upload-time = "2026-06-17T19:53:27.801Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/e01aa7ad573883ed9456f130226babdec70b005e098c4d6226a6238e761b/triton-3.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4ea396a06171f1f1f58cbd39c70b09294398f7dd7c620939bab54ad6f934fa", size = 184705764, upload-time = "2026-06-17T20:03:59.064Z" }, + { url = "https://files.pythonhosted.org/packages/a4/09/5683146fda6a2b569deb78ccfd8fbfea8bfe55f726b081c0a6bb18dd6f28/triton-3.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2020153b08280415ec0da6607834e79166442147e78e144df06b508c75b186d2", size = 197729537, upload-time = "2026-06-17T19:53:35.516Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/448220c3092019f9fdfab39ec47985968181d67da34b44f6a7f6280a5cbb/triton-3.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c58e4c61f0c73b5dba3b5d19b4a7093c32f90dc18b2a7f121a7c16ccd31107b7", size = 184814760, upload-time = "2026-06-17T20:04:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] diff --git a/src/rosa/__init__.py b/src/rosa/__init__.py index 803c33c..8d37823 100644 --- a/src/rosa/__init__.py +++ b/src/rosa/__init__.py @@ -1378,7 +1378,16 @@ def combine_losses( ) -InferenceBackend = Literal["auto", "python", "numba"] +InferenceBackend = Literal[ + "auto", + "python", + "numba", + "rlbwt", + "rlbwt_native", + "rlbwt_compact256", + "rlbwt_mc128", + "rlbwt_mc192", +] InferenceMode = Literal["top1", "rich"] @@ -1404,7 +1413,15 @@ class ROSAInferenceState: batch_size: int max_length: int - backend: Literal["python", "numba"] + backend: Literal[ + "python", + "numba", + "rlbwt", + "rlbwt_native", + "rlbwt_compact256", + "rlbwt_mc128", + "rlbwt_mc192", + ] mode: InferenceMode ragged: bool suffix_k: int @@ -1471,7 +1488,15 @@ def reset(self) -> None: def _make_inference_impl( batch_size: int, max_length: int, - backend: Literal["python", "numba"], + backend: Literal[ + "python", + "numba", + "rlbwt", + "rlbwt_native", + "rlbwt_compact256", + "rlbwt_mc128", + "rlbwt_mc192", + ], mode: InferenceMode = "top1", ragged: bool = False, suffix_k: int = 16, @@ -1506,6 +1531,26 @@ def _make_inference_impl( return init_ragged_state(batch_size, max_length) if backend == "python": return _init_python_inference_state(batch_size, max_length) + if backend == "rlbwt": + from ._rlbwt_backend import _init_rlbwt_state + + return _init_rlbwt_state(batch_size, max_length) + if backend == "rlbwt_native": + from ._rlbwt_backend import _init_native_rlbwt_state + + return _init_native_rlbwt_state(batch_size, max_length) + if backend == "rlbwt_compact256": + from ._rlbwt_backend import _init_native_rlbwt_compact_state + + return _init_native_rlbwt_compact_state(batch_size, max_length) + if backend in {"rlbwt_mc128", "rlbwt_mc192"}: + from ._rlbwt_backend import _init_native_rlbwt_mc_state + + return _init_native_rlbwt_mc_state( + batch_size, + max_length, + 2 if backend == "rlbwt_mc128" else 3, + ) try: from ._stateful_numba import _init_inference_state except ModuleNotFoundError as error: @@ -1625,19 +1670,46 @@ def init_inference_state( raise ValueError("batch_size must be > 0") if max_length <= 0: raise ValueError("max_length must be > 0") - if backend not in {"auto", "python", "numba"}: - raise ValueError("backend must be 'auto', 'python', or 'numba'") + if backend not in { + "auto", + "python", + "numba", + "rlbwt", + "rlbwt_native", + "rlbwt_compact256", + "rlbwt_mc128", + "rlbwt_mc192", + }: + raise ValueError( + "backend must be 'auto', 'python', 'numba', 'rlbwt', " + "'rlbwt_native', 'rlbwt_compact256', 'rlbwt_mc128', or 'rlbwt_mc192'" + ) if mode not in {"top1", "rich"}: raise ValueError("mode must be 'top1' or 'rich'") if suffix_k <= 0: raise ValueError("suffix_k must be > 0") if occurrences_r <= 0: raise ValueError("occurrences_r must be > 0") - if (mode == "rich" or ragged) and backend == "python": + if (mode == "rich" or ragged) and backend in { + "python", + "rlbwt", + "rlbwt_native", + "rlbwt_compact256", + "rlbwt_mc128", + "rlbwt_mc192", + }: feature = "rich" if mode == "rich" else "ragged" - raise ValueError(f"{feature} inference does not support backend='python'") - - selected: Literal["python", "numba"] + raise ValueError(f"{feature} inference does not support backend={backend!r}") + + selected: Literal[ + "python", + "numba", + "rlbwt", + "rlbwt_native", + "rlbwt_compact256", + "rlbwt_mc128", + "rlbwt_mc192", + ] if backend == "auto": if mode == "rich" or ragged: selected = "numba" @@ -1750,6 +1822,18 @@ def _inference_step( output = cast(Any, state._impl).step(token, active=active, reset=reset) elif state.backend == "python": output = _python_forward_step(cast(_PythonInferenceState, state._impl), token) + elif state.backend == "rlbwt": + from ._rlbwt_backend import _forward_step as rlbwt_step + + output = rlbwt_step(cast(Any, state._impl), token) + elif state.backend == "rlbwt_compact256": + from ._rlbwt_backend import _compact_forward_step + + output = _compact_forward_step(cast(Any, state._impl), token) + elif state.backend in {"rlbwt_native", "rlbwt_mc128", "rlbwt_mc192"}: + from ._rlbwt_backend import _native_forward_step + + output = _native_forward_step(cast(Any, state._impl), token) else: from ._stateful_numba import _forward_step @@ -1868,6 +1952,26 @@ def _inference_prefill(state: ROSAInferenceState, tokens: Tensor) -> InferenceOu ], dim=1, ) + elif state.mode == "top1" and not state.ragged and state.backend == "rlbwt": + from ._rlbwt_backend import _prefill as rlbwt_prefill + + output = rlbwt_prefill(cast(Any, state._impl), tokens) + elif ( + state.mode == "top1" + and not state.ragged + and state.backend == "rlbwt_compact256" + ): + from ._rlbwt_backend import _compact_prefill + + output = _compact_prefill(cast(Any, state._impl), tokens) + elif ( + state.mode == "top1" + and not state.ragged + and state.backend in {"rlbwt_native", "rlbwt_mc128", "rlbwt_mc192"} + ): + from ._rlbwt_backend import _native_prefill + + output = _native_prefill(cast(Any, state._impl), tokens) elif state.mode == "top1" and not state.ragged: from ._stateful_numba import _prefill diff --git a/src/rosa/_rlbwt_backend.py b/src/rosa/_rlbwt_backend.py new file mode 100644 index 0000000..f6d0b6a --- /dev/null +++ b/src/rosa/_rlbwt_backend.py @@ -0,0 +1,469 @@ +"""Exact CPU prototype based on the online RLBWT of reversed prefixes. + +This module intentionally favours a direct implementation of the definitions +over asymptotic performance. The BWT is genuinely stored as maximal runs, +while PA and LCS are stored explicitly. In particular, this is not the +sampled/compressed PA/LCS data structure from the paper. + +The state is uniform: every batch row has consumed ``state.position`` tokens, +and every row has the same immutable ``max_length`` capacity. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field + +import torch +from torch import Tensor + + +class _Sentinel: + """Type of the unique out-of-band BWT sentinel.""" + + __slots__ = () + + def __repr__(self) -> str: + return "$" + + +_SENTINEL = _Sentinel() +"""The sentinel object returned by :func:`_reconstruct_rlbwt`.""" + + +@dataclass +class _RLBWTRun: + """One maximal BWT run. + + ``symbol`` is meaningful only for an ordinary run. Keeping the sentinel + flag separate is essential because every signed int64 value is a valid + input token. + """ + + symbol: int = 0 + length: int = 1 + is_sentinel: bool = False + + +@dataclass +class _RLBWTRowState: + history: list[int] + pa: list[int] + lcs: list[int] + runs: list[_RLBWTRun] = field(default_factory=lambda: [_RLBWTRun(is_sentinel=True)]) + source: int = -1 + lrs: int = 0 + + +@dataclass +class _RLBWTState: + """Fixed-capacity, uniformly positioned batch state.""" + + batch_size: int + max_length: int + position: int + rows: list[_RLBWTRowState] + + @property + def history(self) -> list[list[int]]: + """Preallocated token histories, exposed for diagnostics/tests.""" + + return [row.history for row in self.rows] + + @property + def pa(self) -> list[list[int]]: + """Explicit PA storage; only ``position + 1`` entries are live.""" + + return [row.pa for row in self.rows] + + @property + def lcs(self) -> list[list[int]]: + """Explicit LCS storage; only ``position + 1`` entries are live.""" + + return [row.lcs for row in self.rows] + + @property + def sources(self) -> list[int]: + return [row.source for row in self.rows] + + @property + def lrs_lengths(self) -> list[int]: + return [row.lrs for row in self.rows] + + +def _init_rlbwt_state(batch_size: int, max_length: int) -> _RLBWTState: + """Allocate an empty uniform RLBWT state with fixed sequence capacity.""" + + if batch_size <= 0: + raise ValueError("batch_size must be > 0") + if max_length <= 0: + raise ValueError("max_length must be > 0") + + rows = [ + _RLBWTRowState( + history=[0] * max_length, + # PA includes the initial prefix consisting only of the conceptual + # leading sentinel, hence the extra slot. + pa=[0] * (max_length + 1), + lcs=[0] * (max_length + 1), + ) + for _ in range(batch_size) + ] + return _RLBWTState(batch_size, max_length, 0, rows) + + +def _init_native_rlbwt_state(batch_size: int, max_length: int) -> object: + """Create the optional native companion state after capability checks.""" + + try: + import rosa_native_step # type: ignore[reportMissingImports] + except ModuleNotFoundError as error: + if error.name != "rosa_native_step": + raise + raise ImportError( + "native RLBWT inference requires a compatible rosa-torch-native wheel" + ) from error + if ( + getattr(rosa_native_step, "rlbwt_abi_version", None) != 1 + or getattr(rosa_native_step, "NativeRLBWTState", None) is None + ): + raise ImportError("installed rosa-torch-native lacks RLBWT ABI 1") + return rosa_native_step.NativeRLBWTState(batch_size, max_length) + + +def _init_native_rlbwt_compact_state(batch_size: int, max_length: int) -> object: + """Create the explicit native identity-coded uint8-vocabulary state.""" + + try: + import rosa_native_step # type: ignore[reportMissingImports] + except ModuleNotFoundError as error: + if error.name != "rosa_native_step": + raise + raise ImportError( + "compact RLBWT inference requires a compatible rosa-torch-native wheel" + ) from error + if ( + getattr(rosa_native_step, "rlbwt_compact_abi_version", None) != 1 + or getattr(rosa_native_step, "NativeRLBWTCompactState", None) is None + ): + raise ImportError("installed rosa-torch-native lacks compact RLBWT ABI 1") + return rosa_native_step.NativeRLBWTCompactState(batch_size, max_length, 256) + + +def _init_native_rlbwt_mc_state( + batch_size: int, max_length: int, lanes: int, seed: int = 20260811 +) -> object: + """Create an explicit Monte-Carlo native RLBWT state. + + Long LCE queries trust ``lanes`` independent rolling hashes modulo 2^64; + collisions are possible by contract. LCEs of at most 64 tokens are exact. + """ + + try: + import rosa_native_step # type: ignore[reportMissingImports] + except ModuleNotFoundError as error: + if error.name != "rosa_native_step": + raise + raise ImportError( + "Monte-Carlo RLBWT inference requires a compatible rosa-torch-native wheel" + ) from error + if ( + getattr(rosa_native_step, "rlbwt_mc_abi_version", None) != 1 + or getattr(rosa_native_step, "NativeRLBWTStateMC", None) is None + ): + raise ImportError("installed rosa-torch-native lacks RLBWT MC ABI 1") + return rosa_native_step.NativeRLBWTStateMC(batch_size, max_length, lanes, seed) + + +def _same_run(run: _RLBWTRun, symbol: int) -> bool: + return not run.is_sentinel and run.symbol == symbol + + +def _merge_around(runs: list[_RLBWTRun], index: int) -> None: + """Restore maximality after changing one ordinary run.""" + + if index > 0 and _same_run(runs[index - 1], runs[index].symbol): + runs[index - 1].length += runs[index].length + del runs[index] + index -= 1 + if index + 1 < len(runs) and _same_run(runs[index + 1], runs[index].symbol): + runs[index].length += runs[index + 1].length + del runs[index + 1] + + +def _sentinel_location(runs: list[_RLBWTRun]) -> tuple[int, int]: + """Return ``(run_index, expanded_position)`` of the unique sentinel.""" + + position = 0 + found = -1 + sentinel_position = -1 + for run_index, run in enumerate(runs): + if run.is_sentinel: + if found != -1 or run.length != 1: + raise RuntimeError("invalid RLBWT sentinel representation") + found = run_index + sentinel_position = position + position += run.length + if found == -1: + raise RuntimeError("RLBWT sentinel is missing") + return found, sentinel_position + + +def _insertion_rank(runs: list[_RLBWTRun], symbol: int) -> int: + """Compute the zero-based insertion index from the paper's equation. + + The paper uses one-based ``ell = C[c] + rank_c(BWT, s) + 1``. Therefore + the returned zero-based index is ``C[c] + rank_c(BWT, s)``. The sentinel + contributes one to ``C[c]`` because it is ordered below every int64 token. + """ + + _, sentinel_position = _sentinel_location(runs) + less = 0 + rank_through_sentinel = 0 + expanded_position = 0 + for run in runs: + if run.is_sentinel: + less += 1 + else: + if run.symbol < symbol: + less += run.length + if run.symbol == symbol and expanded_position <= sentinel_position: + rank_through_sentinel += min( + run.length, sentinel_position - expanded_position + 1 + ) + expanded_position += run.length + return less + rank_through_sentinel + + +def _replace_sentinel(runs: list[_RLBWTRun], symbol: int) -> None: + run_index, _ = _sentinel_location(runs) + runs[run_index] = _RLBWTRun(symbol=symbol) + _merge_around(runs, run_index) + + +def _insert_sentinel(runs: list[_RLBWTRun], position: int) -> None: + """Insert the sentinel before expanded BWT position ``position``.""" + + total = sum(run.length for run in runs) + if position < 0 or position > total: + raise RuntimeError("RLBWT insertion position is out of range") + if position == total: + runs.append(_RLBWTRun(is_sentinel=True)) + return + + start = 0 + for run_index, run in enumerate(runs): + end = start + run.length + if position == start: + runs.insert(run_index, _RLBWTRun(is_sentinel=True)) + return + if start < position < end: + if run.is_sentinel: + raise RuntimeError("cannot split the RLBWT sentinel") + left_length = position - start + right_length = end - position + runs[run_index : run_index + 1] = [ + _RLBWTRun(run.symbol, left_length), + _RLBWTRun(is_sentinel=True), + _RLBWTRun(run.symbol, right_length), + ] + return + start = end + raise RuntimeError( # pragma: no cover - malformed non-partitioning run list + "failed to insert the RLBWT sentinel" + ) + + +def _common_suffix(row: _RLBWTRowState, left: int, right: int) -> int: + """Return LCS of prefixes ending at token-count endpoints left/right.""" + + length = 0 + while ( + length < left + and length < right + and row.history[left - length - 1] == row.history[right - length - 1] + ): + length += 1 + return length + + +def _update_pa_lcs( + row: _RLBWTRowState, old_length: int, insertion_index: int +) -> tuple[int, int, int]: + """Insert the new prefix and return ``(new_rank, x, y)``.""" + + old_size = old_length + 1 + new_endpoint = old_length + 1 + if insertion_index < 0 or insertion_index > old_size: + raise RuntimeError("PA insertion position is out of range") + + predecessor = row.pa[insertion_index - 1] if insertion_index > 0 else None + successor = row.pa[insertion_index] if insertion_index < old_size else None + x = _common_suffix(row, new_endpoint, predecessor) if predecessor is not None else 0 + y = _common_suffix(row, new_endpoint, successor) if successor is not None else 0 + + # Shift fixed-capacity storage in place. LCS[i] is the LCS of PA[i-1] + # and PA[i], with LCS[0] conventionally zero. + for index in range(old_size, insertion_index, -1): + row.pa[index] = row.pa[index - 1] + row.lcs[index] = row.lcs[index - 1] + row.pa[insertion_index] = new_endpoint + row.lcs[insertion_index] = x if insertion_index > 0 else 0 + if successor is not None: + row.lcs[insertion_index + 1] = y + return insertion_index, x, y + + +def _select_rosa_source( + row: _RLBWTRowState, new_rank: int, new_size: int, lrs: int +) -> int: + """Select the newest old endpoint in the maximal PA interval.""" + + if lrs == 0: + return -1 + left = new_rank + while left > 0 and row.lcs[left] >= lrs: + left -= 1 + right = new_rank + while right + 1 < new_size and row.lcs[right + 1] >= lrs: + right += 1 + + new_endpoint = new_size - 1 + previous_endpoint = max( + row.pa[index] + for index in range(left, right + 1) + if row.pa[index] != new_endpoint + ) + return previous_endpoint - 1 + + +def _step_row(row: _RLBWTRowState, old_length: int, token: int) -> int: + row.history[old_length] = token + + # Compute ell against the old BWT, then perform exactly the standard + # replace-terminator/insert-terminator update. + insertion_index = _insertion_rank(row.runs, token) + _replace_sentinel(row.runs, token) + _insert_sentinel(row.runs, insertion_index) + + new_rank, x, y = _update_pa_lcs(row, old_length, insertion_index) + row.lrs = max(x, y) + row.source = _select_rosa_source(row, new_rank, old_length + 2, row.lrs) + if row.source < 0: + return -1 + # source is the previous occurrence's inclusive zero-based endpoint. + return row.history[row.source + 1] + + +def _forward_step(state: _RLBWTState, tokens: Tensor) -> Tensor: + """Consume one int64-compatible token per row and return ROSA top-1.""" + + if not isinstance(tokens, Tensor): + raise TypeError("tokens must be a torch.Tensor") + if tokens.ndim == 0 and state.batch_size == 1: + tokens = tokens.unsqueeze(0) + if tokens.ndim != 1 or tokens.shape[0] != state.batch_size: + raise ValueError("tokens must have shape [batch_size]") + if state.position >= state.max_length: + raise RuntimeError("inference state capacity exceeded") + + device = tokens.device + cpu_tokens = tokens.detach().to(device="cpu", dtype=torch.long).contiguous() + predictions = [ + _step_row(row, state.position, int(token)) + for row, token in zip(state.rows, cpu_tokens.tolist(), strict=True) + ] + state.position += 1 + return torch.tensor(predictions, dtype=torch.long, device=device) + + +def _prefill(state: _RLBWTState, tokens: Tensor) -> Tensor: + """Replay a dense initial context through the incremental RLBWT update.""" + + if state.position != 0: + raise RuntimeError("prefill requires an empty inference state") + if not isinstance(tokens, Tensor): + raise TypeError("tokens must be a torch.Tensor") + if tokens.ndim != 2 or tokens.shape[0] != state.batch_size: + raise ValueError("tokens must have shape [batch_size, sequence_length]") + if tokens.shape[1] > state.max_length: + raise RuntimeError("inference state capacity exceeded") + if tokens.shape[1] == 0: + return torch.empty(tokens.shape, dtype=torch.long, device=tokens.device) + + output = torch.empty(tokens.shape, dtype=torch.long, device=tokens.device) + for position in range(tokens.shape[1]): + output[:, position] = _forward_step(state, tokens[:, position]) + return output + + +def _native_forward_step(state: object, tokens: Tensor) -> Tensor: + """Dispatch one batch token vector through the optional native state.""" + + device = tokens.device + cpu_tokens = tokens.detach().to(device="cpu", dtype=torch.long).contiguous() + output = state.step(cpu_tokens.numpy()) # type: ignore[attr-defined] + return torch.from_numpy(output).to(device) + + +def _native_prefill(state: object, tokens: Tensor) -> Tensor: + """Dispatch a dense context through one fused optional native call.""" + + device = tokens.device + if tokens.shape[1] == 0: + return torch.empty(tokens.shape, dtype=torch.long, device=device) + cpu_tokens = tokens.detach().to(device="cpu", dtype=torch.long).contiguous() + output = state.prefill(cpu_tokens.numpy()) # type: ignore[attr-defined] + return torch.from_numpy(output).to(device) + + +def _validate_compact_tokens(tokens: Tensor) -> None: + if bool(torch.any((tokens < 0) | (tokens > 255))): + raise ValueError("rlbwt_compact256 tokens must be in [0, 255]") + + +def _compact_forward_step(state: object, tokens: Tensor) -> Tensor: + _validate_compact_tokens(tokens) + return _native_forward_step(state, tokens) + + +def _compact_prefill(state: object, tokens: Tensor) -> Tensor: + _validate_compact_tokens(tokens) + return _native_prefill(state, tokens) + + +def _reconstruct_rlbwt( + state: _RLBWTState, batch_index: int = 0 +) -> list[int | _Sentinel]: + """Expand one row's RLBWT for tests, preserving the sentinel identity.""" + + if batch_index < 0 or batch_index >= state.batch_size: + raise IndexError("batch_index is out of range") + result: list[int | _Sentinel] = [] + for run in state.rows[batch_index].runs: + value: int | _Sentinel = _SENTINEL if run.is_sentinel else run.symbol + result.extend([value] * run.length) + return result + + +# A descriptive alias is convenient for diagnostics that do not rely on the +# private helper name. +reconstruct_rlbwt = _reconstruct_rlbwt + + +__all__ = [ + "_RLBWTRun", + "_RLBWTState", + "_SENTINEL", + "_forward_step", + "_init_rlbwt_state", + "_init_native_rlbwt_state", + "_init_native_rlbwt_compact_state", + "_init_native_rlbwt_mc_state", + "_native_forward_step", + "_native_prefill", + "_compact_forward_step", + "_compact_prefill", + "_prefill", + "_reconstruct_rlbwt", + "reconstruct_rlbwt", +] diff --git a/tests/test_rlbwt_backend.py b/tests/test_rlbwt_backend.py new file mode 100644 index 0000000..17c9149 --- /dev/null +++ b/tests/test_rlbwt_backend.py @@ -0,0 +1,415 @@ +from __future__ import annotations + +import unittest +from functools import cmp_to_key +from itertools import product +from types import SimpleNamespace +from typing import Any, cast +from unittest.mock import patch + +import numpy as np +import torch + +from rosa import forward_step, init_inference_state, prefill, reference_rosa +from rosa._rlbwt_backend import ( + _SENTINEL, + _forward_step, + _init_native_rlbwt_compact_state, + _init_native_rlbwt_mc_state, + _init_native_rlbwt_state, + _init_rlbwt_state, + _insert_sentinel, + _prefill, + _reconstruct_rlbwt, + _RLBWTRun, + _sentinel_location, + _update_pa_lcs, + reconstruct_rlbwt, +) + + +def _compare_reversed_prefixes(tokens: list[int], left: int, right: int) -> int: + left_index = left - 1 + right_index = right - 1 + while left_index >= 0 and right_index >= 0: + if tokens[left_index] != tokens[right_index]: + return -1 if tokens[left_index] < tokens[right_index] else 1 + left_index -= 1 + right_index -= 1 + if left_index == right_index: + return 0 + return -1 if left_index < right_index else 1 + + +def _common_suffix(tokens: list[int], left: int, right: int) -> int: + length = 0 + while ( + length < left + and length < right + and tokens[left - length - 1] == tokens[right - length - 1] + ): + length += 1 + return length + + +def _naive_index(tokens: list[int]) -> tuple[list[int], list[int], list[Any]]: + pa = sorted( + range(len(tokens) + 1), + key=cmp_to_key( + lambda left, right: _compare_reversed_prefixes(tokens, left, right) + ), + ) + lcs = [0] + lcs.extend( + _common_suffix(tokens, pa[index - 1], pa[index]) for index in range(1, len(pa)) + ) + bwt = [tokens[endpoint] if endpoint < len(tokens) else _SENTINEL for endpoint in pa] + return pa, lcs, bwt + + +class TestRLBWTBackend(unittest.TestCase): + def assert_index(self, state: Any, tokens: list[int]) -> None: + pa, lcs, bwt = _naive_index(tokens) + self.assertEqual(state.pa[0][: len(pa)], pa) + self.assertEqual(state.lcs[0][: len(lcs)], lcs) + self.assertEqual(_reconstruct_rlbwt(state), bwt) + self.assertEqual(reconstruct_rlbwt(state), bwt) + + def test_incremental_index_matches_naive_oracle(self) -> None: + sequences = [ + [0, 0, 0, 0, 0], + [0, 1, 0, 1, 2, 0, 1], + [5, -2, 5, -2, 4, 5], + [-(2**63), 2**63 - 1, -(2**63), 0], + ] + for sequence in sequences: + with self.subTest(sequence=sequence): + state = _init_rlbwt_state(1, len(sequence)) + for position, token in enumerate(sequence): + _forward_step(state, torch.tensor([token])) + self.assert_index(state, sequence[: position + 1]) + + def test_exhaustive_binary_matches_reference(self) -> None: + tokens = torch.tensor(list(product(range(2), repeat=10)), dtype=torch.long) + state = init_inference_state(1024, 10, backend="rlbwt") + actual = prefill(state, tokens) + expected, sources, lengths = reference_rosa(tokens) + self.assertTrue(torch.equal(actual, expected)) + self.assertEqual(state.backend, "rlbwt") + self.assertEqual(state.position, 10) + self.assertTrue(torch.equal(torch.tensor(state._impl.sources), sources[:, -1])) + self.assertTrue( + torch.equal(torch.tensor(state._impl.lrs_lengths), lengths[:, -1]) + ) + + def test_random_batch_prefill_and_continuation(self) -> None: + generator = torch.Generator().manual_seed(20260811) + tokens = torch.randint(17, (8, 96), generator=generator) + state = init_inference_state(8, 96, backend="rlbwt") + initial = prefill(state, tokens[:, :64]) + continuation = torch.stack( + [forward_step(state, tokens[:, position]) for position in range(64, 96)], + dim=1, + ) + expected, _, _ = reference_rosa(tokens) + self.assertTrue( + torch.equal(torch.cat((initial, continuation), dim=1), expected) + ) + self.assertEqual(state.positions.tolist(), [96] * 8) + + state.reset() + self.assertEqual(state.position, 0) + self.assertTrue(torch.equal(prefill(state, tokens), expected)) + + def test_one_hundred_random_rows_match_all_reference_fields(self) -> None: + generator = torch.Generator().manual_seed(20260811) + for case_index in range(100): + length = 1 + case_index % 32 + alphabet = 1 + case_index % 19 + tokens = torch.randint(alphabet, (1, length), generator=generator) + expected, sources, match_lengths = reference_rosa(tokens) + state = _init_rlbwt_state(1, length) + actual_steps: list[torch.Tensor] = [] + for position in range(length): + actual_steps.append(_forward_step(state, tokens[:, position])) + self.assertEqual(state.sources[0], int(sources[0, position])) + self.assertEqual(state.lrs_lengths[0], int(match_lengths[0, position])) + actual = torch.stack(actual_steps, dim=1) + with self.subTest(case=case_index): + self.assertTrue(torch.equal(actual, expected)) + + def test_scalar_empty_and_validation(self) -> None: + state = init_inference_state(1, 3, backend="rlbwt") + self.assertEqual(forward_step(state, torch.tensor(7)).item(), -1) + self.assertEqual(forward_step(state, torch.tensor(7)).item(), 7) + self.assertEqual(forward_step(state, torch.tensor(7)).item(), 7) + with self.assertRaisesRegex(RuntimeError, "capacity"): + forward_step(state, torch.tensor(7)) + + empty_state = init_inference_state(1, 1, backend="rlbwt") + empty = prefill(empty_state, torch.empty(0, dtype=torch.long)) + self.assertEqual(tuple(empty.shape), (0,)) + with self.assertRaisesRegex(RuntimeError, "empty"): + prefill(state, torch.tensor([], dtype=torch.long)) + + with self.assertRaisesRegex(ValueError, "rich"): + init_inference_state(1, 4, backend="rlbwt", mode="rich") + with self.assertRaisesRegex(ValueError, "ragged"): + init_inference_state(1, 4, backend="rlbwt", ragged=True) + + def test_native_capability_errors(self) -> None: + with patch.dict("sys.modules", {"rosa_native_step": None}): + with self.assertRaisesRegex(ImportError, "compatible"): + _init_native_rlbwt_state(1, 4) + incompatible = SimpleNamespace(rlbwt_abi_version=0) + with patch.dict("sys.modules", {"rosa_native_step": incompatible}): + with self.assertRaisesRegex(ImportError, "ABI 1"): + _init_native_rlbwt_state(1, 4) + missing_class = SimpleNamespace(rlbwt_abi_version=1) + with patch.dict("sys.modules", {"rosa_native_step": missing_class}): + with self.assertRaisesRegex(ImportError, "ABI 1"): + _init_native_rlbwt_state(1, 4) + + real_import = __import__ + + def unexpected_missing(name: str, *args: Any, **kwargs: Any) -> Any: + if name == "rosa_native_step": + raise ModuleNotFoundError("unexpected", name="unexpected") + return real_import(name, *args, **kwargs) + + with patch("builtins.__import__", side_effect=unexpected_missing): + with self.assertRaises(ModuleNotFoundError): + _init_native_rlbwt_state(1, 4) + with self.assertRaises(ModuleNotFoundError): + _init_native_rlbwt_mc_state(1, 4, 2) + with self.assertRaises(ModuleNotFoundError): + _init_native_rlbwt_compact_state(1, 4) + + with patch.dict("sys.modules", {"rosa_native_step": None}): + with self.assertRaisesRegex(ImportError, "Monte-Carlo"): + _init_native_rlbwt_mc_state(1, 4, 2) + for capability in ( + SimpleNamespace(rlbwt_mc_abi_version=0), + SimpleNamespace(rlbwt_mc_abi_version=1), + ): + with patch.dict("sys.modules", {"rosa_native_step": capability}): + with self.assertRaisesRegex(ImportError, "MC ABI 1"): + _init_native_rlbwt_mc_state(1, 4, 2) + + def test_native_dispatch_with_capability_stub(self) -> None: + class StubNativeRLBWTState: + def __init__(self, batch_size: int, max_length: int) -> None: + self.batch_size = batch_size + self.max_length = max_length + self.position = 0 + + def step(self, tokens: np.ndarray) -> np.ndarray: + self.position += 1 + return tokens.copy() + + def prefill(self, tokens: np.ndarray) -> np.ndarray: + self.position = tokens.shape[1] + return tokens.copy() + + capability = SimpleNamespace( + rlbwt_abi_version=1, NativeRLBWTState=StubNativeRLBWTState + ) + with patch.dict("sys.modules", {"rosa_native_step": capability}): + state = init_inference_state(2, 3, backend="rlbwt_native") + tokens = torch.tensor([[1, 2], [3, 4]], dtype=torch.long) + self.assertTrue(torch.equal(prefill(state, tokens), tokens)) + self.assertEqual(state.position, 2) + state.reset() + self.assertTrue( + torch.equal( + forward_step(state, torch.tensor([7, 9])), torch.tensor([7, 9]) + ) + ) + empty = init_inference_state(1, 1, backend="rlbwt_native") + self.assertEqual( + tuple(prefill(empty, torch.empty(0, dtype=torch.long)).shape), (0,) + ) + + def test_native_backend_matches_python_oracle_when_available(self) -> None: + try: + import rosa_native_step + except ModuleNotFoundError: + self.skipTest("native companion is unavailable") + if getattr(rosa_native_step, "rlbwt_abi_version", None) != 1: + self.skipTest("native companion lacks RLBWT ABI 1") + + tokens = torch.tensor( + [[0, 1, 0, 1, 2, -1, 2**31, -1], [7] * 8], dtype=torch.long + ) + expected, sources, lengths = reference_rosa(tokens) + state = init_inference_state(2, 8, backend="rlbwt_native") + self.assertTrue(torch.equal(prefill(state, tokens), expected)) + self.assertEqual(state.positions.tolist(), [8, 8]) + self.assertEqual(state._impl.sources.tolist(), sources[:, -1].tolist()) + self.assertEqual(state._impl.lrs_lengths.tolist(), lengths[:, -1].tolist()) + state.reset() + self.assertEqual(forward_step(state, tokens[:, 0]).tolist(), [-1, -1]) + + empty = init_inference_state(1, 1, backend="rlbwt_native") + self.assertEqual( + tuple(prefill(empty, torch.empty(0, dtype=torch.long)).shape), (0,) + ) + with self.assertRaisesRegex(ValueError, "rich"): + init_inference_state(1, 4, backend="rlbwt_native", mode="rich") + with self.assertRaisesRegex(ValueError, "ragged"): + init_inference_state(1, 4, backend="rlbwt_native", ragged=True) + + def test_native_compact_backend_when_available(self) -> None: + try: + import rosa_native_step + except ModuleNotFoundError: + self.skipTest("native companion is unavailable") + if getattr(rosa_native_step, "rlbwt_compact_abi_version", None) != 1: + self.skipTest("native companion lacks compact RLBWT ABI 1") + + tokens = torch.tensor( + [[0, 15, 0, 15, 7, 255, 7, 0], [255, 1, 255, 2, 255, 3, 4, 5]], + dtype=torch.long, + ) + expected, sources, lengths = reference_rosa(tokens) + state = init_inference_state(2, 8, backend="rlbwt_compact256") + self.assertTrue(torch.equal(prefill(state, tokens), expected)) + self.assertEqual(state.positions.tolist(), [8, 8]) + self.assertEqual(state._impl.sources.tolist(), sources[:, -1].tolist()) + self.assertEqual(state._impl.lrs_lengths.tolist(), lengths[:, -1].tolist()) + self.assertEqual(state._impl.vocabulary_size, 256) + state.reset() + self.assertEqual(forward_step(state, tokens[:, 0]).tolist(), [-1, -1]) + + for invalid in (-1, 256): + with self.subTest(invalid=invalid): + rejected = init_inference_state(1, 1, backend="rlbwt_compact256") + with self.assertRaisesRegex(ValueError, r"\[0, 255\]"): + forward_step(rejected, torch.tensor([invalid])) + with self.assertRaisesRegex(ValueError, r"\[0, 255\]"): + prefill(rejected, torch.tensor([[invalid]])) + + with self.assertRaisesRegex(ValueError, "rich"): + init_inference_state(1, 4, backend="rlbwt_compact256", mode="rich") + with self.assertRaisesRegex(ValueError, "ragged"): + init_inference_state(1, 4, backend="rlbwt_compact256", ragged=True) + + def test_compact_native_import_validation(self) -> None: + with patch.dict("sys.modules", {"rosa_native_step": None}): + with self.assertRaisesRegex(ImportError, "compact RLBWT"): + _init_native_rlbwt_compact_state(1, 4) + for capability in ( + SimpleNamespace(rlbwt_compact_abi_version=0), + SimpleNamespace(rlbwt_compact_abi_version=1), + ): + with patch.dict("sys.modules", {"rosa_native_step": capability}): + with self.assertRaisesRegex(ImportError, "compact RLBWT ABI 1"): + _init_native_rlbwt_compact_state(1, 4) + + def test_native_mc_backends_when_available(self) -> None: + try: + import rosa_native_step + except ModuleNotFoundError: + self.skipTest("native companion is unavailable") + if getattr(rosa_native_step, "rlbwt_mc_abi_version", None) != 1: + self.skipTest("native companion lacks RLBWT MC ABI 1") + + generator = torch.Generator().manual_seed(20260811) + workloads = ( + torch.tensor(list(product(range(2), repeat=8)), dtype=torch.long), + torch.randint(11, (4, 192), generator=generator), + torch.ones((2, 192), dtype=torch.long), + torch.tensor([[index % 5 for index in range(192)]], dtype=torch.long), + ) + for backend, lanes in (("rlbwt_mc128", 2), ("rlbwt_mc192", 3)): + for tokens in workloads: + with self.subTest(backend=backend, shape=tuple(tokens.shape)): + expected, _, _ = reference_rosa(tokens) + state = init_inference_state( + tokens.shape[0], tokens.shape[1], backend=backend + ) + split = min(96, tokens.shape[1]) + initial = prefill(state, tokens[:, :split]) + continuation = ( + torch.stack( + [ + forward_step(state, tokens[:, position]) + for position in range(split, tokens.shape[1]) + ], + dim=1, + ) + if split < tokens.shape[1] + else tokens[:, :0] + ) + self.assertTrue( + torch.equal(torch.cat((initial, continuation), dim=1), expected) + ) + self.assertEqual(state._impl.lanes, lanes) + self.assertEqual(state._impl.seed, 20260811) + with self.assertRaisesRegex(RuntimeError, "capacity"): + forward_step(state, tokens[:, 0]) + state.reset() + self.assertEqual(state.position, 0) + self.assertEqual(state._impl.lanes, lanes) + self.assertEqual( + forward_step(state, tokens[:, 0]).shape[0], tokens.shape[0] + ) + + with self.assertRaisesRegex(ValueError, "rich"): + init_inference_state(1, 4, backend=backend, mode="rich") + with self.assertRaisesRegex(ValueError, "ragged"): + init_inference_state(1, 4, backend=backend, ragged=True) + + def test_private_validation_and_corrupt_sentinels(self) -> None: + for batch_size, max_length, message in ( + (0, 1, "batch_size"), + (1, 0, "max_length"), + ): + with self.subTest(message=message): + with self.assertRaisesRegex(ValueError, message): + _init_rlbwt_state(batch_size, max_length) + + state = _init_rlbwt_state(2, 2) + self.assertEqual(repr(_SENTINEL), "$") + self.assertEqual(state.history, [[0, 0], [0, 0]]) + with self.assertRaisesRegex(TypeError, "Tensor"): + _forward_step(state, cast(Any, [1, 2])) + with self.assertRaisesRegex(ValueError, "shape"): + _forward_step(state, torch.tensor([1])) + with self.assertRaisesRegex(TypeError, "Tensor"): + _prefill(state, cast(Any, [[1], [2]])) + with self.assertRaisesRegex(ValueError, "shape"): + _prefill(state, torch.tensor([[1, 2]])) + with self.assertRaisesRegex(RuntimeError, "capacity"): + _prefill(state, torch.zeros((2, 3), dtype=torch.long)) + _prefill(state, torch.zeros((2, 1), dtype=torch.long)) + with self.assertRaisesRegex(RuntimeError, "empty"): + _prefill(state, torch.zeros((2, 1), dtype=torch.long)) + with self.assertRaisesRegex(IndexError, "batch_index"): + _reconstruct_rlbwt(state, 2) + with self.assertRaisesRegex(IndexError, "batch_index"): + _reconstruct_rlbwt(state, -1) + + with self.assertRaisesRegex(RuntimeError, "missing"): + _sentinel_location([_RLBWTRun(1)]) + with self.assertRaisesRegex(RuntimeError, "invalid"): + _sentinel_location( + [_RLBWTRun(is_sentinel=True), _RLBWTRun(is_sentinel=True)] + ) + with self.assertRaisesRegex(RuntimeError, "invalid"): + _sentinel_location([_RLBWTRun(length=2, is_sentinel=True)]) + with self.assertRaisesRegex(RuntimeError, "out of range"): + _insert_sentinel([_RLBWTRun(1)], -1) + with self.assertRaisesRegex(RuntimeError, "out of range"): + _insert_sentinel([_RLBWTRun(1)], 2) + with self.assertRaisesRegex(RuntimeError, "cannot split"): + _insert_sentinel([_RLBWTRun(length=2, is_sentinel=True)], 1) + with self.assertRaisesRegex(RuntimeError, "PA insertion"): + _update_pa_lcs(state.rows[0], 0, -1) + + scalar_state = _init_rlbwt_state(1, 1) + self.assertEqual(_forward_step(scalar_state, torch.tensor(3)).item(), -1) + + +if __name__ == "__main__": + unittest.main()