Skip to main content

A high-performance, O(P) data structure for weighted random sampling of binned probabilities, ideal for large-scale simulations.

Project description

DigitBinIndex

A DigitBinIndex is a tree-based data structure designed for efficient weighted random selection and removal from large collections of items. It is optimized for scenarios involving millions of items where probabilities are approximate and high performance is critical, such as simulations for Wallenius' noncentral hypergeometric distribution or Fisher's noncentral hypergeometric distribution.

This library provides high-performance solutions for both major types of noncentral hypergeometric distributions:

  • Sequential Sampling (Wallenius'): Modeled by select_and_remove, where items are selected and removed one at a time.
  • Simultaneous Sampling (Fisher's): Modeled by select_many_and_remove, where a batch of unique items is selected and removed together.

The Core Problem

In simulations, forecasts, or statistical models (e.g., mortality models, Monte Carlo simulations, or machine learning sampling), managing a large, dynamic set of probabilities is common. A key task is to randomly select items based on their weights, often removing them afterward, and repeat this process efficiently. For datasets with millions of items, achieving high performance while maintaining reasonable accuracy is a significant challenge, especially for complex distributions like Wallenius' or Fisher's.

How It Works

DigitBinIndex is a radix tree that organizes items into bins based on the decimal digits of their rescaled probabilities, enabling fast weighted random selection and updates.

  1. Digit-based Tree Structure: Each level of the tree corresponds to a decimal place of the rescaled weight. For example, a weight of 0.543 at precision 3 is rescaled to 543 and placed by traversing the path: root -> child[5] -> child[4] -> child[3].

  2. Adaptive Bin Storage: Leaf nodes act as bins, storing item IDs in either a fast Vec<u32> (for small bins) or a compressed Roaring Bitmap (for large bins). The bin type is chosen automatically for optimal performance and memory use if a capacity hint is given.

  3. Accumulated Value Index: Each node tracks the accumulated_value (sum of weights beneath it), supporting O(P) weighted random selection, where P is the configured precision (number of decimal places).

Features

  • High Performance: Outperforms general-purpose data structures like Fenwick Trees for both sequential and simultaneous weighted sampling.
  • Dual-Model Support: Optimized methods for Wallenius' (select_and_remove) and Fisher's (select_many_and_remove) distributions.
  • O(P) Complexity: Core operations (add, remove, select) have a time complexity of O(P), where P is the fixed precision, effectively constant for a given configuration.
  • Memory Efficiency: Combines a sparse radix tree with Roaring Bitmaps for efficient storage, especially for sparse or clustered weight distributions.
  • Python Integration: Seamless Python bindings via pyo3 for cross-language support.

Performance

DigitBinIndex trades a small, controllable amount of precision by binning probabilities to achieve significant performance gains. The following benchmarks compare DigitBinIndex against an optimized FenwickTree implementation in a realistic, high-churn simulation.

The benchmark scenario starts with a large population (1M or 10M items), then simulates a high volume of activity:

  • Churn: A significant number of items are selected and removed.
  • Acquisition: New items are added to the population.

This measures the real-world throughput of both data structures under dynamic conditions. The tests were run on a standard desktop (Intel i7, 16GB RAM, Rust 1.75).


Wallenius' Draw (Sequential Churn)

This benchmark simulates sequential selection by removing 100,000 items one-by-one, then adding 110,000 new items. This is a common pattern in agent-based models or iterative simulations. DigitBinIndex's O(P) complexity gives it a decisive advantage as the population scales.

Scenario (N items) DigitBinIndex Time FenwickTree Time Speedup Factor
1 Million Items (p=3) ~31.3 ms ~81.4 ms ~2.6x faster
1 Million Items (p=5) ~51.8 ms ~79.6 ms ~1.5x faster
10 Million Items (p=3) ~661.6 ms ~1727.7 ms ~2.6x faster
  • Key Takeaway: DigitBinIndex is over 2.6 times faster than the FenwickTree for sequential operations on large datasets. Its performance is dependent on precision (P) and not the number of items (N), allowing it to scale far more effectively.

Fisher's Draw (Batch Churn)

This benchmark simulates simultaneous selection by removing a large batch of 100,000 unique items at once, followed by the acquisition of 110,000 new items. DigitBinIndex uses a highly optimized batch rejection sampling method.

Scenario (N items) DigitBinIndex Time FenwickTree Time Speedup Factor
1 Million Items (p=3) ~23.3 ms ~90.0 ms ~3.9x faster
1 Million Items (p=5) ~45.0 ms ~90.6 ms ~2.0x faster
10 Million Items (p=3) ~432.8 ms ~1556.1 ms ~3.6x faster

Key Takeaway: For batch selections, DigitBinIndex is even more efficient, performing up to 3.9 times faster. The batched nature of the operation further highlights the architectural advantages of the radix tree approach for this use case.


When to Choose DigitBinIndex

Use DigitBinIndex when:

  • You need high-performance sampling for Wallenius' or Fisher's distributions.
  • Your dataset is large (N > 100,000).
  • Probabilities are approximate, as is common in empirical data, simulations, or machine learning models.
  • Performance is more critical than perfect precision.

Consider a Fenwick Tree if you require exact precision and your weights differ only at high decimal places (e.g., 0.12345 vs. 0.12346), though this comes at the cost of O(log N) complexity and higher memory usage for large datasets.


Choosing a Precision

The precision parameter controls the radix tree's depth, balancing accuracy, performance, and memory. Higher precision improves sampling accuracy but increases memory usage (up to 10x per additional level) and slightly impacts runtime.

The Rule of Thumb

A precision of 3 or 4 (default: 3) is recommended for most applications. This captures sufficient detail for typical weight distributions while maintaining excellent performance and low memory usage.

The Mathematical Intuition

Each decimal place contributes exponentially less to a weight’s value. For a weight of 0.12345:

  • 1st digit (1): 0.1
  • 2nd digit (2): 0.02
  • 3rd digit (3): 0.003
  • 4th digit (4): 0.0004

Truncating at 3 digits limits the error per item to <0.001. In large populations, these errors average out, minimally affecting the selection distribution.

Guidance

Precision Typical Use Case Trade-offs
1-2 Maximum performance, minimal memory usage. Best for coarse weights (e.g., 0.1, 0.5). Loses accuracy with fine-grained data.
3-4 Recommended Default. Optimal for most scenarios. Captures sufficient detail for simulation or model data. Negligible performance/memory cost.
5+ High-fidelity scenarios with very close weights. Distinguishes weights like 0.12345 vs. 0.12346. Increases memory (up to 10x per level) and slightly impacts performance.

Maximum Item Capacity for DigitBinIndex

Since the identities used for individuals are u32, the current implementation limits the maxiumum capacity to 2^32 - 1 ≈ 4.29 billion.


Usage & Installation

DigitBinIndex is available as a Python library on PyPI or as a Rust crate on Crates.io. Ensure Python 3.6+ for Python bindings or Rust 1.75+ for the Rust crate.

For Python 🐍

Install from PyPI:

pip install digit-bin-index

Example usage:

from digit_bin_index import DigitBinIndex

def main():
    # Create an index with precision 3 (default).
    index = DigitBinIndex()

    # With custom precision
    index_5 = DigitBinIndex.with_precision(5)

    # With custom precision and capacity hint for large datasets
    # This might choose a more memory-efficient internal storage.
    index_3_xl = DigitBinIndex.with_precision_and_capacity(3, 10_000_000)

    # Add items with IDs and weights.
    index.add(id=101, weight=0.123)  # Low weight
    index.add(id=202, weight=0.800)  # High weight
    index.add(id=303, weight=0.755)  # High weight
    index.add(id=404, weight=0.110)  # Low weight

    # Sequential (Wallenius') Draw: Select and remove one item.
    # Higher-weighted items (202, 303) are more likely.
    selected_item = index.select_and_remove()
    if selected_item:
        # The returned weight is a float, representing the bin's average weight
        item_id, weight = selected_item
        print(f"Wallenius draw: ID {item_id}, Weight ~{weight:.3f}")
    
    print(f"Items remaining: {index.count()}")  # 3

    # Simultaneous (Fisher's) Draw: Select and remove 2 unique items.
    selected_items = index.select_many_and_remove(2)
    if selected_items:
        print(f"Fisher's draw: {selected_items}")
    
    print(f"Items remaining: {index.count()}")  # 1

if __name__ == "__main__":
    main()

For Rust 🦀

Add to your Cargo.toml:

[dependencies]
digit-bin-index = "0.3.0" # Replace with the latest version from crates.io

Example usage:

use digit_bin_index::DigitBinIndex;

fn main() {
    // Create an index with precision 3.
    let mut index = DigitBinIndex::with_precision(3);

    // Add items with IDs and f64 weights.
    index.add(101, 0.123); // Low weight
    index.add(202, 0.800); // High weight
    index.add(303, 0.755); // High weight
    index.add(404, 0.110); // Low weight

    // Sequential (Wallenius') Draw: Select and remove one item.
    if let Some((id, weight)) = index.select_and_remove() {
        println!("Wallenius draw: ID {}, Weight ~{}", id, weight);
    }
    println!("Items remaining: {}", index.count()); // 3

    // Simultaneous (Fisher's) Draw: Select and remove 2 unique items.
    if let Some(items) = index.select_many_and_remove(2) {
        println!("Fisher's draw: {:?}", items);
    }
    println!("Items remaining: {}", index.count()); // 1
}

License

This project is licensed under the MIT License, a permissive open-source license allowing free use, modification, and distribution.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

digit_bin_index-0.3.1.tar.gz (30.8 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

digit_bin_index-0.3.1-cp38-abi3-win_amd64.whl (136.9 kB view details)

Uploaded CPython 3.8+Windows x86-64

digit_bin_index-0.3.1-cp38-abi3-win32.whl (135.0 kB view details)

Uploaded CPython 3.8+Windows x86

digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_x86_64.whl (432.9 kB view details)

Uploaded CPython 3.8+musllinux: musl 1.2+ x86-64

digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_i686.whl (461.0 kB view details)

Uploaded CPython 3.8+musllinux: musl 1.2+ i686

digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_armv7l.whl (537.1 kB view details)

Uploaded CPython 3.8+musllinux: musl 1.2+ ARMv7l

digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_aarch64.whl (436.3 kB view details)

Uploaded CPython 3.8+musllinux: musl 1.2+ ARM64

digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (262.6 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ x86-64

digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl (303.3 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ s390x

digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (290.0 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ppc64le

digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (272.8 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARMv7l

digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (257.0 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARM64

digit_bin_index-0.3.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl (280.5 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.5+ i686

digit_bin_index-0.3.1-cp38-abi3-macosx_11_0_arm64.whl (230.5 kB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

digit_bin_index-0.3.1-cp38-abi3-macosx_10_12_x86_64.whl (244.7 kB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

Details for the file digit_bin_index-0.3.1.tar.gz.

File metadata

  • Download URL: digit_bin_index-0.3.1.tar.gz
  • Upload date:
  • Size: 30.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.9.4

File hashes

Hashes for digit_bin_index-0.3.1.tar.gz
Algorithm Hash digest
SHA256 7275d76087d1f4950b39c5c151ef0e0a6ddac311c9431d52c53c6681d0a65d62
MD5 d7c75cc84b8b21e496e045403e7e4325
BLAKE2b-256 41a7e9726011296a9408b0daa80bec3637d3a01bd2ff85267b5712cf47a38bf5

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 7b8953226dcd85f28e24fb53dfaef6cf4bc4592fa48cad2535ec34377bcc364e
MD5 828d898ff51f1cb10c9049298e5f6d73
BLAKE2b-256 580401ce7c5f2141b2eae9f597327b41c9c858e2b6172267371894c97e9ddd68

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-win32.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-win32.whl
Algorithm Hash digest
SHA256 0aff21864115b80cea95fa664cc11bd8209937f6fecd96a06bd9f1b1c32dba30
MD5 97056bb62a467d87a2d457469017577c
BLAKE2b-256 f232ea37a29da4d5fe4af111662962f14270987276a8677a4ebb3cba9f53d2ca

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 d7a7ba186083d541f4f1011c1f508e491c8526c87b461efed07c10be7199a176
MD5 b5353d301f4c608564cc3db6f895c02a
BLAKE2b-256 e4d8af00ac835b8621f808360fdefea020f960868811d817c2de1d6a09d79ec4

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 6e216c503b102bac709f3fa7a20a0dfd2f106fb4d506391bfa8ceec7c26c0e26
MD5 486e500b51b1216f72558bd98d469756
BLAKE2b-256 88b80a60a721879ec701f74e7285c01d21d61d8c19cc764a3d53817391e73a7f

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_armv7l.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_armv7l.whl
Algorithm Hash digest
SHA256 b9921a9e08e68416c1615c1a1159d3976e11afc6ae6793502178c8fa2607fc28
MD5 7fbe110e12b7ff4b7d38a9d31b0431aa
BLAKE2b-256 8e0b2f5289e5c41958062e29e20b3d9705f0b2d756e8f68691b69bd4c2a48b69

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 9901939cbcaf97488b0ba21d842a748b7a86117b2ec9050820439c80a3e56aec
MD5 d931f07bee9d7ca3685118c68c93967b
BLAKE2b-256 391037c9b1b49a91385197f55f92f6b128185ebabbdaf0a2412191e868139521

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b508c2625e1e4edb99eb05863d41d2fd5cc29a7241d176c32bdf546922f44214
MD5 3a0f3fc02639509ff3d66c2d8b3ec33a
BLAKE2b-256 40e64da9a3d17ee3ab57f960f578b9078cb63c9fe924306dc9fea446a472745d

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl
Algorithm Hash digest
SHA256 0f42f378f4f1dd376cae3032644511788a07245c4b672c651e1744e0e9fb794b
MD5 84c32d80e5f471b43119bf22d5132f9b
BLAKE2b-256 b5f958b78e4b9a3f88279ee13a11d48e3266dd04868767450ef2bdb2b0835326

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
Algorithm Hash digest
SHA256 924e8341cadb49619f1814f8a7c3c0677d1ef8c071d98669cff632f59c69c26d
MD5 0d00a8ef83c3ca5c0ba19c7d60d53163
BLAKE2b-256 d97b1f851e44ce71a64f14e2da58eba6c8c928bad580dfa4c0f59b738c82b608

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm Hash digest
SHA256 aed23d8478977a7c172d7f40596a28d8a2255917c08f77f4058d0b7dabd482c7
MD5 80c6fde871c21dabb7ad102adea47d2f
BLAKE2b-256 8603252f9ebb58b75faadc80c1efb023058af74109e97a6510cf90a4986dfd52

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 865de553152049f15174549fe1b674492471fda5765e62c7c7b82a7f1594de19
MD5 a1916d5d01cb34db57b32ba14642fa97
BLAKE2b-256 887c0b260bec5807827847a1720bb8bbe01d27d2063effe483c438af0f725489

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl
Algorithm Hash digest
SHA256 eac6cefe7f74071502aaa888293e544ac78cd2b63fb4f50b9dfb02f02adcf5e2
MD5 07db477d070c33e88115816942ce2ab0
BLAKE2b-256 93dffec61de57fc88f408dbbf9aed96cfd9dd22715eef76b5d057f631283c88e

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 70514d296f376e3d245445b4bd181da4381b1dcf5bc431e25e8af87a85eb0b0b
MD5 77a2c0415b7c28c4308e761ab7110865
BLAKE2b-256 92fefcd45898bf17ba03daba147dd17f16013d6d8a441d9ac164bb5b9d9ae1f9

See more details on using hashes here.

File details

Details for the file digit_bin_index-0.3.1-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for digit_bin_index-0.3.1-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 4cdd9ba2289d43fcd4b221819cd927c86b21515b410b75ffef0efa9276281cc6
MD5 165ae35f01e7a16c2a0015270b61aac6
BLAKE2b-256 25d69a90b917f0314c22fb1708452c3c1eca8773048d676ba6a6911e641c6c42

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page