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.

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.0.tar.gz (30.6 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.0-cp38-abi3-win_amd64.whl (137.5 kB view details)

Uploaded CPython 3.8+Windows x86-64

digit_bin_index-0.3.0-cp38-abi3-win32.whl (137.4 kB view details)

Uploaded CPython 3.8+Windows x86

digit_bin_index-0.3.0-cp38-abi3-musllinux_1_2_x86_64.whl (433.0 kB view details)

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

digit_bin_index-0.3.0-cp38-abi3-musllinux_1_2_i686.whl (462.5 kB view details)

Uploaded CPython 3.8+musllinux: musl 1.2+ i686

digit_bin_index-0.3.0-cp38-abi3-musllinux_1_2_armv7l.whl (538.4 kB view details)

Uploaded CPython 3.8+musllinux: musl 1.2+ ARMv7l

digit_bin_index-0.3.0-cp38-abi3-musllinux_1_2_aarch64.whl (436.7 kB view details)

Uploaded CPython 3.8+musllinux: musl 1.2+ ARM64

digit_bin_index-0.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (262.8 kB view details)

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

digit_bin_index-0.3.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl (305.0 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ s390x

digit_bin_index-0.3.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (291.2 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ppc64le

digit_bin_index-0.3.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (274.1 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARMv7l

digit_bin_index-0.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (257.4 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARM64

digit_bin_index-0.3.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl (282.5 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.5+ i686

digit_bin_index-0.3.0-cp38-abi3-macosx_11_0_arm64.whl (231.2 kB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

digit_bin_index-0.3.0-cp38-abi3-macosx_10_12_x86_64.whl (244.9 kB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

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

File metadata

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

File hashes

Hashes for digit_bin_index-0.3.0.tar.gz
Algorithm Hash digest
SHA256 5648cca1e961d7182e8f1fe5e2334714178ab91711dd745eee0c6db5dc8d9454
MD5 00ee305d164ff64e73baa110ea1003c4
BLAKE2b-256 dae1bbcc9f772433031251ff5fed60f9d3e1010acc02b339b87c371f6cfb796d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 b01ed48db94bc353b8d62e50bdebfc26198946a84712fd38011a3506d40e188d
MD5 ac6c29c99baf28c14d31a57e5875279e
BLAKE2b-256 0634c77c72701ecdcd1f08d07fd590f207349cc371132628df6bdce5cd868f30

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-win32.whl
Algorithm Hash digest
SHA256 055a57d48514a0c656ca5808974c2a59883ea4b5592fb8d59365514bddf887ed
MD5 554f65e165e432512945d95a6718ddac
BLAKE2b-256 fdab245600b8797cca3d0cbb8e00cc991422ab92fd7979563e61dfec1d41bde2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 089e9f8026d0b142d77519d2054bcead1385597c08256b3e9f5841c2673e77da
MD5 8be7b483b2754ec900550f325fcff9f7
BLAKE2b-256 250e89ce0b4f9b48daf9501de98a8bc9584c22eed125ce2905ce1a3aab5645d0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 32075f86c8464dfd4b2aac525d867415c82e0ac39f84ebed3deb5f2cbaef258e
MD5 7bd548b914aaa6c87a54024d2ca16b34
BLAKE2b-256 2686c6a84fa4987dee2b1fd36dae73b6510bd5ab6b6b873324df5f99e5d35ba3

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-musllinux_1_2_armv7l.whl
Algorithm Hash digest
SHA256 b732a708f3c1b5c07aada8882d5591122661fcf42a8b65e94e88ad2b8bf68f79
MD5 cc78ba50dbbb9505f11ab5661e0650e7
BLAKE2b-256 f68f627ca96f9ecf08c15b8a89e07152a224a594fd106ad03b70f9bcb95a6eec

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 16cbf3a5c390f776bd27a689bc84b1858b0ea0ee048f9b4349609246f4edce2f
MD5 8a1cc113937228058613b9ad6686b9b1
BLAKE2b-256 f34fa7e72b5d944218c17a4d1f7285d11010ea0c5bee00559310d80139da701d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9e111c189f8ad16ff9ba28c0d5228b0eb21d5226ede7fa245b7b416979e1fda5
MD5 4078620b1b0ee3ffa0d53ffd10284983
BLAKE2b-256 6621967b63c73ae8b0a10296fa04f7a00b18b2687373d9418fdf7aa53f940cdc

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl
Algorithm Hash digest
SHA256 488c3106d2b02b6e04f0274868a0f5ef9afd57cc913e1d3cbd03c2c858f0415a
MD5 f18991df544a3e58b2884278d043fcfa
BLAKE2b-256 5d39cfbe4b11e06ab9cceb9189424d336991ca4302da4f1586cf2223b18ac79a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
Algorithm Hash digest
SHA256 9e9e77a53fd718c32863480f9fb2deab5a45928d46a69b0aa69d32d1024b6ef8
MD5 57d0d292336629c683bbc9e75071212c
BLAKE2b-256 fc336fdd633ed1d294c5a46f0abb07d00d17961f9984d0fb15d39c390bc4e597

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm Hash digest
SHA256 2b58a50fa646a56fa06676d1413715339af03642fa17539546c2bcc8fa324a6f
MD5 45f366de35788861c12b1a162149cd10
BLAKE2b-256 a2dc647d38489e55e2ace55205a916915259d6d4e7b0400d31c4777b6d284921

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 70057829694d5a3d5913798373f0bc347d4ed44ba02931a1b3333213a586a2e9
MD5 e4c6f3dae4f0ab34e0a81ad927acf1cd
BLAKE2b-256 240e4fbfe011f80161a891dcfa35db7b42a8b7e7434606b768af6dfc652419d0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl
Algorithm Hash digest
SHA256 3d392b7c5811429a14f1d3d39eceeb4f775287874977331981c5a13a9cc2a319
MD5 85ae0d2be88cbb47aa3d49547aa483ce
BLAKE2b-256 0ca3f353091b4e3494de7ede2995ac388f4c3c47a50d4f946ceee8b454bb02d6

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 695eb3a56791997ccdb37fa686077d21d165294bb344830be800b9b4bade18a5
MD5 69e7def772f8aec7690097dce992d67d
BLAKE2b-256 73bf72186353df161b092f0555c378764c2a332074aebda77846f442172cfa54

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for digit_bin_index-0.3.0-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 a0b783df61986b0ea2f07741621d9a201828505fc907f212a3480e61d674eb4a
MD5 fbca0f9e633851703e07057a51f5136f
BLAKE2b-256 5081986cd82ca87e079621aa6365bf74bd3145755a60de26735fab59f047f9bb

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