Skip to main content

Build License Coverage Status

STArr

Fast sum tree ops in Cython for NumPy arrays. Inspired by Prioritized Experience Replay.

Installation

pip install starr 

Quickstart

Initialize a SumTreeArray, a subclass of numpy.ndarray

>>> from starr import SumTreeArray
>>> sumtree_array = SumTreeArray(4, dtype='float32')
>>> sumtree_array
SumTreeArray([0., 0., 0., 0.], dtype=float32)

Or build one from an existing n-dimensional ndarray

>>> import numpy as np
>>> sumtree_array_2d = SumTreeArray(np.array([[1,2,3],[4,5,6]], dtype='int32'))
>>> sumtree_array_2d
SumTreeArray([[1, 2, 3],
              [4, 5, 6]], dtype=int32)

Set values like you normally would

>>> sumtree_array[0] = 1
>>> sumtree_array[1:2] = [2]
>>> sumtree_array[np.array([False,False,True,False])] = 3
>>> sumtree_array[-1] = 4
>>> sumtree_array
SumTreeArray([1., 2., 3., 4.], dtype=float32)

A SumTreeArray maintains an internal sum tree, which can be used for fast sampling and sum ops.

>>> sumtree_array.sumtree()
array([ 0., 10.,  3.,  7.], dtype=float32)

Sample indices (efficiently), where each element is the unnormalized probability of being sampled

>>> sumtree_array.sample(10)
array([2, 3, 3, 3, 3, 1, 2, 2, 2, 0], dtype=int32)

>>> # probability of being sampled
>>> sumtree_array / sumtree_array.sum() 
array([0.1, 0.2, 0.3, 0.4], dtype=float32)

>>> # sampled proportions
>>> (sumtree_array.sample(1000)[None] == np.arange(4)[:,None]).mean(axis=1) 
array([0.10057, 0.19919, 0.29983, 0.40041])

You can also sample indices from an n-dimensional SumTreeArray

>>> sumtree_array_2d.sample(4)
(array([1, 1, 0, 0]), array([0, 1, 1, 2]))

Use the array's sum method to use the sumtree to calculate sums (quickly)

>>> sumtree_array.sum()
10.0

Memory

Arithmetic operations return ndarray (to avoid expensive tree initialization)

>>> sumtree_array * 2
array([ 2., 4., 6., 8.], dtype=float32)

This is true for get operations as well

>>> sumtree_array[1:3]
array([2., 3.], dtype=float32)

>>> sumtree_array[:]
array([1., 2., 3., 4.], dtype=float32)

However, in-place operations update SumTreeArray

>>> sumtree_array_in_place_op = SumTreeArray(np.array([2,4,6,8]),dtype='float32')
>>> sumtree_array_in_place_op += 1
>>> sumtree_array_in_place_op 
SumTreeArray([3., 5., 7., 9.], dtype=float32)

Performance

See latest benchmarks.

Sampling indices is faster than normal sampling methods in numpy

>>> x = SumTreeArray(np.ones(int(1e6)))
>>> %timeit x.sample(100)
55.2 µs ± 6.17 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

>>> y = np.ones(int(1e6))
>>> %timeit np.random.choice(len(y),size=100,p=y/y.sum())
10.8 ms ± 697 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

For large arrays, sum operations over C-contiguous blocks of memory are faster than ndarray, because of the sum tree:

>>> x = SumTreeArray(np.ones((1000,1000)))
>>> %timeit x.sum()
428 ns ± 10.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

>>> y = np.ones((1000,1000))
>>> %timeit y.sum()
272 µs ± 51.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

>>> %timeit x.sum(axis=1)
118 µs ± 2.2 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

>>> %timeit y.sum(axis=1)
276 µs ± 68.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Sum operations over non C-contiguous blocks of memory (e.g. along the first axis of a 2d array) are slower:

>>> %timeit x.sum(axis=0)
367 µs ± 28 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

>>> %timeit y.sum(axis=0)
303 µs ± 6.97 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Set operations are much slower in SumTreeArray than in ndarray, because each set operation updates the tree, but that's okay when using SumTreeArray for applications that rely heavily on sampling and sum operations, such as prioritzed experience replay! In the example below, updating and sampling with SumTreeArray is 150x faster than with ndarray, even though the update operation alone in ndarray is 26x faster than SumTreeArray!

>>> x = SumTreeArray(np.ones(int(1e6)))

>>> # set + sample 
>>> %timeit x[-10:] = 2; x.sample(100)
71.4 µs ± 3.71 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

>>> y = np.ones(int(1e6))
>>> y_sum = y.sum() # let's assume we keep track of this efficiently

>>> # set + sample 
>>> %timeit y[-10:] = 2; np.random.choice(len(y),size=100,p=y/y_sum)
10.7 ms ± 752 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Metadata

Release files for starr 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for starr 0.3.2
File Size Uploaded Provenance Actions
starr-0.3.2.tar.gz 14.9 kB Without provenance Details for starr-0.3.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for starr 0.3.2
File Interpreter ABI Platform Actions
starr-0.3.2-cp36-cp36m-macosx_10_9_x86_64.whl CPython 3.6 CPython 3.6 pymalloc macOS 10.9+ x86-64 Details for starr-0.3.2-cp36-cp36m-macosx_10_9_x86_64.whl

Total release size: 243.2 kB

Release files / starr-0.3.2.tar.gz

Download URL starr-0.3.2.tar.gz
Size 14.9 kB
Tags Source
SHA-256 checksum
How to use checksums
a0ad82a79b2bb2d25b0954de4c9e5411c75ad8aff41a156ee43f47e742b6647e
BLAKE2b-256 checksum
How to use checksums
cb626e54153a113d1629eab0a470a372ec785e7e52df85f557dc2c224a5c0c07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.8.0 pkginfo/1.9.6 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/1.0.0 urllib3/1.26.18 tqdm/4.64.1 importlib-metadata/4.2.0 keyring/23.4.1 rfc3986/1.5.0 colorama/0.4.5 CPython/3.6.7

Release files / starr-0.3.2-cp36-cp36m-macosx_10_9_x86_64.whl

Download URL starr-0.3.2-cp36-cp36m-macosx_10_9_x86_64.whl
Size 228.3 kB
Tags CPython 3.6 CPython 3.6 pymalloc macOS 10.9+ x86-64
SHA-256 checksum
How to use checksums
be5af34b78618da880c956437a2c51595b259c2abe85d5233e3310b6318205a4
BLAKE2b-256 checksum
How to use checksums
54ac4ff9cb90e43157f7d2f8d5d5095e05c03393503f1f3e730927bb49391923
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.8.0 pkginfo/1.9.6 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/1.0.0 urllib3/1.26.18 tqdm/4.64.1 importlib-metadata/4.2.0 keyring/23.4.1 rfc3986/1.5.0 colorama/0.4.5 CPython/3.6.7

Security for starr 0.3.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page