Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: docs
on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Setup python
uses: actions/setup-python@v6
with:
python-version-file: "pyproject.toml"

- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0

- name: Build docs with mkdocs
run: |
uv run --group docs mkdocs gh-deploy -v --clean --strict --force
4 changes: 2 additions & 2 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0

- name: Run pytest
- name: Run tests with pytest
run: |
uv run --group dev --extra benchmark pytest -sv

- name: Upload artifacts
- name: Upload artifacts from tests
uses: actions/upload-artifact@v4
with:
name: artifacts
Expand Down
49 changes: 18 additions & 31 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
# DEVELOPMENT

data_streaming_accelerators/
├── .github/workflows/
├── docs/
│ └── mkdocs.yml
├── src/
│ └── data_streaming_accelerators/
│ ├── __init__.py
│ ├── __main__.py
│ ├── core/
│ | ├── dynamic_interval_management.py
│ | ├── dynamic_point_aggregation.py
│ | └── suffix_pattern_recognition.py
│ ├── api.py
├── tests/
│ ├── conftest.py
│ ├── test_api.py
│ ├── test_core.py
├── .gitignore
├── pyproject.toml
├── README.md
└── LICENSE
## Run tests with pytest

```sh
# https://docs.astral.sh/uv/getting-started/installation/
# install uv from official installer
wget -qO- https://astral.sh/uv/install.sh | sh
```
# Option 1: All tests [Default]
uv run --group dev --extra benchmark pytest -sv

```
# Run specific markers in pytest
# Option 2: Functional tests only
uv run --group dev --extra benchmark pytest -sv -m func

# Option 3: Performance tests only
uv run --group dev --extra benchmark pytest -sv -m perf
```

# Run pytest
uv run --group dev --extra benchmark pytest -sv
## Build docs with mkdocs

```sh
# Option 1: Build and deploy [Default]
uv run --group docs mkdocs gh-deploy -v --clean --strict --force

# Option 2: Build only
uv run --group docs mkdocs build -v --clean --strict

# Watch run until it completes.
gh run watch
# Option 3: Deploy only using local server
uv run --group docs mkdocs serve -v --clean --strict
```
Binary file not shown.
30 changes: 30 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Data Streaming Accelerators

![Python CI](https://github.com/achekery/data-streaming/actions/workflows/python-app.yml/badge.svg)

## 1. Summary

### [Dynamic Interval Management](modules/dynamic_interval_management)

--8<-- "modules/dynamic_interval_management/README.md:summary"

![Graph for Performance Report](modules/dynamic_interval_management/static/performance-report-1280x640.png)

### Dynamic Point Aggregation

--8<-- "modules/dynamic_point_aggregation/README.md:summary"

### Suffix Pattern Recognition

--8<-- "modules/suffix_pattern_recognition/README.md:summary"


## 2. Getting Started

This design requires **Python 3.13+**.

To run the regression tests for this design:

```bash
uv run --group dev --extra benchmark pytest -sv
```
16 changes: 16 additions & 0 deletions docs/javascripts/mathjax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
window.MathJax = {
tex: {
inlineMath: [["\\(", "\\)"]],
displayMath: [["\\[", "\\]"]],
processEscapes: true,
processEnvironments: true
},
options: {
ignoreHtmlClass: ".*|",
processHtmlClass: "arithmatex"
}
};

document.subscribe(() => {
MathJax.typesetPromise()
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
![Python CI](https://github.com/achekery/data-streaming/actions/workflows/python-app.yml/badge.svg)

## 1. Design Summary
<!-- --8<-- [start:summary] -->
The theoretical lower bound for merging $N$ static intervals is $\Omega(N \log N)$ due to the sorting requirement. By focusing on interval edges rather than the source intervals themselves, we can focus on efficient algorithms with compact implementation. However, in **Online Streaming Environments**, the task shifts from batch data loading (static intervals) to streaming data ingestion (dynamic intervals).
<!-- --8<-- [end:summary] -->

![Graph for Performance Report](static/performance-report-1280x640.png)

### 📊 Complexity Analysis

Expand All @@ -17,11 +21,13 @@ The theoretical lower bound for merging $N$ static intervals is $\Omega(N \log N

The advantage of an Augmented AVL Interval Tree is its ability to prune search branches that cannot contain an overlap, ensuring $O(\log N)$ search time.

![Diagram of Pruning Logic](static/pruning-diagram.png)
```mermaid
{% include "modules/dynamic_interval_management/static/pruning-mermaid.txt" %}
```

## 2. Design Approaches

### Optimal Approaches for Batch Data (Static Intervals)
## Optimal Approaches for Batch Data (Static Intervals)
* **Sweep-line (Variant 1):** Uses a sorted list of edge objects and a counter.
* **Complexity (Batch):** $O(N \log N)$ setup, $O(N)$ calculation.
* **Complexity (Streaming):** $O(N \log N)$ per `insert`/`put` operation.
Expand All @@ -31,7 +37,7 @@ The advantage of an Augmented AVL Interval Tree is its ability to prune search b
* **Complexity (Streaming):** $O(N \log N)$ per `insert`/`put` operation.
* **Verdict:** Suitable for batch case due to best-in-class ~7ms benchmark with static intervals. Not suitable for streaming case due to high complexity.

### Optimal Approaches for Streaming Data (Dynamic Intervals)
## Optimal Approaches for Streaming Data (Dynamic Intervals)
* **Augmented AVL Interval Tree (Variant 3):** A stateful, self-balancing BST.
* **Complexity (Batch):** $O(N \log N)$ setup, $O(N)$ calculation.
* **Complexity (Streaming):** $O(\log N)$ per `insert`/`put` operation.
Expand All @@ -53,8 +59,6 @@ In benchmarks with dynamic intervals up to $N=3000$, the Interval Tree (Variant
| 2000 | 0.6743 | 0.0383 | 17.6x |
| 3000 | 1.6093 | 0.0318 | **50.6x** |

![Graph for Performance Report](static/performance-report-1280x640.png)

## 4. Design Optimizations

### Augmented Metadata
Expand All @@ -68,3 +72,7 @@ By implementing a **Sentinel Node Architecture** with a **Two-Down** AVL balanci

### Python Object-Model Tuning
To reduce object-overhead due to the Python object-model, I implemented **Attribute Flattening**. After replacing packed collections (`self.interval[0]`) with discrete integers (`self.lo`, `self.hi`), I reduced constant factors in the hot execution path and achieved a **15% reduction** in benchmarks with static intervals.

## 5. API Reference

::: data_streaming_accelerators.core.dynamic_interval_management
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
![Python CI](https://github.com/achekery/data-streaming/actions/workflows/python-app.yml/badge.svg)

## 1. Design Summary
<!-- --8<-- [start:summary] -->
The optimal runtime complexity for dynamic point aggregation from $N$ point values to $O(N)$ disjoint intervals is $\Omega(N \log N)$ due to the ordering requirement. The request handler uses `put` requests to add new values and `get` requests to make a new interval sequence.

Because offline scenarios (batch data) have known usage patterns, the batch variant (V1) can **optimize the hot path** by simplifying high-usage `put` requests to $O(1)$ and expanding low-usage `get` requests to $O(N \log N)$.

However, because online scenarios (streaming data) do not have known usage patterns, the streaming variant (V2) must instead **balance complexity** between `put` requests ($O(\log N)$) and `get` requests ($O(N)$).
<!-- --8<-- [end:summary] -->

### 📊 Complexity Analysis

Expand Down Expand Up @@ -83,3 +85,7 @@ These results highlight that the batch variant (V1) is suitable for smaller data

### State Transitions
To ensure new point values are correctly merged with existing disjoint intervals, I applied **Correctness by Design** by separately handling the 3 overlap cases: the enclosed-by case (early exit if lower and upper bounds at insertion point are already aligned), the adjacent-to case (join with 1 or 2 neighbors if lower and upper bounds at insertion point are adjacent), and the distinct-from case (insert as new lower and upper bounds).

## 5. API Reference

::: data_streaming_accelerators.core.dynamic_point_aggregation
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,28 @@
![Python CI](https://github.com/achekery/data-streaming/actions/workflows/python-app.yml/badge.svg)

## 1. Design Summary
For suffix pattern recognition with $K$ suffix patterns (length $W$) and 1 query pattern (length $N$), using **reverse-traversal prefix tree search** is efficient because it can model common ancestor ordering with reverse encoding (by inserting suffix patterns in reverse order during setup and searching in reverse order during queries) and check the multiple patterns over a single root-to-leaf pass (taking runtime $O(K\*W)$ for one-time setup and $O(W)$ for each query). This improves over using direct suffix pattern search which must check the multiple patterns over separate array passes (taking $O(K\*W)$ for each query) or using forward-traversal prefix tree search which cannot model common ancestor ordering and must check the multiple patterns over separate root-to-leaf passes (taking runtime $O(K\*W)$ for one-time setup and $O(K\*W)$ for each query).
<!-- --8<-- [start:summary] -->
For suffix pattern recognition with $K$ suffix patterns (length $W$) and 1 query pattern (length $N$), using **reverse-traversal prefix tree search** is efficient because it can model common ancestor ordering with reverse encoding (by inserting suffix patterns in reverse order during setup and searching in reverse order during queries) and check the multiple patterns over a single root-to-leaf pass (taking runtime $O(K \cdot W)$ for one-time setup and $O(W)$ for each query). This improves over using direct suffix pattern search which must check the multiple patterns over separate array passes (taking $O(K \cdot W)$ for each query) or using forward-traversal prefix tree search which cannot model common ancestor ordering and must check the multiple patterns over separate root-to-leaf passes (taking runtime $O(K \cdot W)$ for one-time setup and $O(K \cdot W)$ for each query).

For batch data (offline scenario), reading the query pattern from a static array (size $N$) with a reverse iterator takes $O(1)$ auxiliary space. For streaming data (online scenario), reading the query pattern from a history array (size $W$) reconstructed from the stream takes $O(W)$ auxiliary space.
<!-- --8<-- [end:summary] -->

### 📊 Complexity Analysis

| Operation | Direct Search (v1) | Forward-traversal (v2) | Reverse-traversal (v3) | Comparison |
| :--- | :--- | :--- | :--- | :--- |
| **Initial Setup** | $O(1)$ | $O(K\*W)$ | $O(K\*W)$ | v1 is superior |
| **Batch Queries (N)** | $O(K\*W)$ | $O(K\*W)$ | $O(W)$ | v3 is superior |
| **Streaming Queries (1)** | $O(K\*W)$ | $O(K\*W)$ | $O(W)$ | v3 is superior |
| **Space Complexity** | $O(1)$ | $O(K\*W)$ | $O(K\*W)$ | v1 is superior |
| **Initial Setup** | $O(1)$ | $O(K \cdot W)$ | $O(K \cdot W)$ | v1 is superior |
| **Batch Queries (N)** | $O(K \cdot W)$ | $O(K \cdot W)$ | $O(W)$ | v3 is superior |
| **Streaming Queries (1)** | $O(K \cdot W)$ | $O(K \cdot W)$ | $O(W)$ | v3 is superior |
| **Space Complexity** | $O(1)$ | $O(K \cdot W)$ | $O(K \cdot W)$ | v1 is superior |

### 🚀 Reverse Encoding (Visual)

The advantage of reverse-traversal prefix tree search is that it can check multiple suffix patterns at once, ensuring $O(W)$ query time (and is independent of the suffix pattern count $K$ and the query pattern length $N$). This is possible because of reverse encoding.

![Diagram for reverse encoding](static/diagram-image.png)
```mermaid
{% include "modules/suffix_pattern_recognition/static/diagram-mermaid.txt" %}
```

## 2. Design Approaches & Trade-offs

Expand All @@ -28,3 +32,7 @@ Although Direct Search (v1) requires ~90% less lines of code, it performs worse

### Python Object-Model Tuning
To reduce object-overhead due to the Python object-model, I implemented a **nested hash map** for the reverse-traversal prefix tree, with each "node" using a character for the hash map key and a reference to the next "node" for the hash map value. This approach reduced object lookup during queries.

## 3. API Reference

::: data_streaming_accelerators.core.suffix_pattern_recognition
Binary file not shown.
48 changes: 48 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
site_name: Data Streaming Accelerators
site_url: https://achekery.github.io/data-streaming-accelerators/

repo_name: Data Streaming Accelerators
repo_url: https://github.com/achekery/data-streaming-accelerators/

theme:
language: en
name: material
features:
- content.code.copy
- content.code.annotate
- navigation.sections

markdown_extensions:
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.arithmatex:
generic: true # For your O(log N) LaTeX notations
- pymdownx.snippets:
base_path: docs

plugins:
- search
- macros:
on_error_fail: true
- mkdocstrings:
handlers:
python:
paths: [src]
options:
show_source: true
show_root_heading: true
show_category_heading: true

nav:
- Overview: index.md
- Modules:
- Dynamic Interval Management: modules/dynamic_interval_management/README.md
- Dynamic Point Aggregation: modules/dynamic_point_aggregation/README.md
- Suffix Pattern Recognition: modules/suffix_pattern_recognition/README.md

extra_javascript:
- javascripts/mathjax.js
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ benchmark = [
dev = [
"pytest>=9.0.3",
]
docs = [
"mkdocs-macros-plugin>=1.5.0",
"mkdocs-material>=9.7.6",
"mkdocs-mermaid2-plugin>=1.2.3",
"mkdocstrings-python>=2.0.3",
"mkdocstrings[python]>=1.0.4",
]
lint = [
"ruff>=0.15.12",
]
Expand Down
Loading
Loading