Repository: jina-ai/annlite Branch: main Commit: f0174dee0af6 Files: 97 Total size: 1.1 MB Directory structure: gitextract_ifbk854h/ ├── .gitattributes ├── .github/ │ ├── release-template.ejs │ ├── requirements-test.txt │ └── workflows/ │ ├── cd.yml │ ├── ci.yml │ ├── force-release.yml │ └── tag.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── annlite/ │ ├── __init__.py │ ├── container.py │ ├── core/ │ │ ├── __init__.py │ │ ├── codec/ │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── pq.py │ │ │ ├── projector.py │ │ │ └── vq.py │ │ └── index/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── flat_index.py │ │ ├── hnsw/ │ │ │ ├── __init__.py │ │ │ └── index.py │ │ └── pq_index.py │ ├── enums.py │ ├── executor.py │ ├── filter.py │ ├── helper.py │ ├── hubble_tools.py │ ├── index.py │ ├── math.py │ ├── profile.py │ ├── storage/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── kv.py │ │ └── table.py │ └── utils.py ├── benchmarks/ │ ├── filtering_bench.py │ └── hnsw_bench.py ├── bindings/ │ ├── hnsw_bindings.cpp │ └── pq_bindings.pyx ├── examples/ │ ├── annlite_vs_simpleindexer.py │ ├── filter_example.py │ ├── hnsw_example.py │ ├── pq_benchmark.py │ ├── pqlinearscann_benchmark_with_filtering.py │ └── utils.py ├── executor/ │ ├── Dockerfile │ ├── README.md │ ├── benchmark.py │ ├── config.yml │ ├── executor.py │ └── requirements.txt ├── include/ │ └── hnswlib/ │ ├── bruteforce.h │ ├── fusefilter.h │ ├── hnswalg.h │ ├── hnswlib.h │ ├── space_ip.h │ ├── space_l2.h │ ├── space_pq.h │ └── visited_list_pool.h ├── notebooks/ │ └── fashion_product_search.ipynb ├── pyproject.toml ├── requirements.txt ├── scripts/ │ ├── black.sh │ ├── get-all-test-paths.sh │ ├── get-last-release-note.py │ ├── release.sh │ └── update-version.sh ├── setup.py └── tests/ ├── __init__.py ├── conftest.py ├── docarray/ │ ├── __init__.py │ ├── test_add.py │ ├── test_del.py │ ├── test_find.py │ ├── test_get.py │ └── test_save_load.py ├── executor/ │ ├── __init__.py │ └── test_executor.py ├── test_codec.py ├── test_crud.py ├── test_dump.py ├── test_enums.py ├── test_filter.py ├── test_hnsw_load_save.py ├── test_index.py ├── test_pq_bind.py ├── test_pq_index.py ├── test_projector.py ├── test_projector_index.py ├── test_store.py └── test_table.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ notebooks/* linguist-vendored include/* linguist-vendored bindings/* linguist-vendored *.h linguist-detectable=false *.cpp linguist-detectable=false ================================================ FILE: .github/release-template.ejs ================================================ <% var groupCommits = [ { name: 'breaking', show: true, list: [] }, { name: 'feat', show: true, list: [] }, { name: 'perf', show: true, list: [] }, { name: 'fix', show: true, list: [] }, { name: 'refactor', show: true, list: [] }, { name: 'docs', show: true, list: [] }, { name: 'test', show: true, list: [] }, { name: 'other', show: true, list: [] } ] var all_titles = {}; var all_commiters = {}; var commitHref = "https://github.com/jina-ai/docarray/commit/" commits.forEach(function (commit) { var result = (commit.title).match(/^(\w*)(\((.*)\))?\: (.*)$/); var type = result && result[1]; var scope = result && result[3]; var title = result && result[4]; var committer = commit.authorName if (!(committer in all_commiters)) { all_commiters[committer] = 1 } if (!(title in all_titles)) { all_titles[title] = 1 if( title != null && (title.indexOf('💥')>-1 || title.indexOf(':boom:')>-1) ){ groupCommits.find(item => item.name === 'breaking').list.push({ type: type, scope: scope, title: title, commit: commit }) } else if(type == 'fix' || type == 'fixed'){ groupCommits.find(item => item.name === 'fix').list.push({ type: type, scope: scope, title: title, commit: commit }) } else if(type == 'perf' || type == 'performance'){ groupCommits.find(item => item.name === 'perf').list.push({ type: type, scope: scope, title: title, commit: commit }) } else if(type == 'feat' || type == 'feature'){ groupCommits.find(item => item.name === 'feat').list.push({ type: type, scope: scope, title: title, commit: commit }) } else if(type == 'refactor'){ groupCommits.find(item => item.name === 'refactor').list.push({ type: type, scope: scope, title: title, commit: commit }) } else if(type == 'docs' || type == 'doc'){ groupCommits.find(item => item.name === 'docs').list.push({ type: type, scope: scope, title: title, commit: commit }) } else if(type == 'test' || type == 'tests' || type == 'ci'){ groupCommits.find(item => item.name === 'test').list.push({ type: type, scope: scope, title: title, commit: commit }) } else { groupCommits.find(item => item.name === 'other').list.push({ type: type, scope: scope, title: title, commit: commit }) } } }); var listCommits = function(list, key){ list.forEach(function (ct) { var type = ct.type; var scope = ct.scope; var title = ''; var commit = ct.commit; if(type){ if(key != 'other'){ title = (scope? '__'+scope+'__: ':'') + ct.title; }else{ title = '__' + type + (scope? '('+scope+')':'') + '__ : ' + ct.title; } }else{ title = commit.title; } %> - <% if(typeof commitHref === 'undefined' || commitHref === '') { %>[```<%=commit.sha1.slice(0,8)%>```]<% } else { %>[[```<%=commit.sha1.slice(0,8)%>```](<%=commitHref%><%=commit.sha1%>)]<%}%> __-__ <%=title%> (*<%= commit.authorName %>*) <% })} %> 🙇 We'd like to thank all contributors for this new release! In particular, <% Object.keys(all_commiters).forEach(function (key) { %> <%= key %>, <% }) %> 🙇 <% for(var i of groupCommits){ if(i.list.length == 0) continue; if (i.name === 'breaking' && i.show) { %> ### 💥 Breaking changes <% } else if (i.name === 'fix' && i.show) { %> ### 🐞 Bug fixes <% } else if( i.name === 'feat' && i.show) { %> ### 🆕 New Features <% } else if(i.name === 'perf' && i.show) { %> ### ⚡ Performance Improvements <% } else if(i.name === 'refactor' && i.show) { %> ### 🧼 Code Refactoring <% } else if(i.name === 'docs' && i.show) { %> ### 📗 Documentation <% } else if(i.name === 'test' && i.show) { %> ### 🏁 Unit Test and CICD <% } else if (i.name === 'other' && i.show) { %> ### 🍹 Other Improvements <% } i.show && listCommits(i.list, i); } %> ================================================ FILE: .github/requirements-test.txt ================================================ pytest pytest-custom_exit_code ================================================ FILE: .github/workflows/cd.yml ================================================ name: CD on: push: branches: - main jobs: prep-testbed: if: | !startsWith(github.event.head_commit.message, 'chore') && !startsWith(github.event.head_commit.message, 'build: hotfix') && !endsWith(github.event.head_commit.message, 'reformatted by jina-dev-bot') runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - id: set-matrix run: | sudo apt-get install jq echo "::set-output name=matrix::$(bash scripts/get-all-test-paths.sh)" outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} build-wheels: needs: [prep-testbed] runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] cibw_arch: ["auto64"] python-version: [['3.7', "cp37-*"], ['3.8', "cp38-*"], ['3.9', "cp39-*"], ['3.10', "cp310-*"]] steps: - uses: actions/checkout@v3 with: fetch-depth: 100 - name: Set up Python ${{ matrix.python-version[0] }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version[0] }} - name: Update version shell: bash run: | git fetch --depth=1 origin +refs/tags/*:refs/tags/* ./scripts/update-version.sh - name: Build sdist if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version[0] == '3.7' }} run: | pip install build python -m build --sdist - name: Build wheels uses: pypa/cibuildwheel@v2.10.2 with: package-dir: ./ env: CIBW_ENVIRONMENT: > STAN_BACKEND="${{ env.STAN_BACKEND }}" CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_BUILD: ${{ matrix.python-version[1] }} CIBW_SKIP: "*musllinux*" CIBW_ARCHS: ${{ matrix.cibw_arch }} # CIBW_ARCH_MACOS: x86_64 arm64 CIBW_BUILD_FRONTEND: build - uses: actions/upload-artifact@v3 with: path: | ./wheelhouse/*.whl ./dist/*.tar.gz core-test: needs: [ prep-testbed, build-wheels ] runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] cibw_arch: [ "auto64" ] python-version: [ [ '3.7', "cp37-*" ] ] test-path: ${{fromJson(needs.prep-testbed.outputs.matrix)}} env: JINA_HIDE_SURVEY: "1" steps: - uses: actions/checkout@v3 with: fetch-depth: 100 - name: Set up Python ${{ matrix.python-version[0] }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version[0] }} - name: Prepare enviroment run: | python -m pip install --upgrade pip pip install jina pip install --pre docarray pip install pytest pytest-html pytest-cov pytest-mock pytest-repeat pytest-custom-exit-code pytest-timeout pytest-reraise - uses: actions/download-artifact@v3 with: name: artifact - name: Install annlite linux if: ${{ matrix.os == 'ubuntu-latest' }} run: | pip install wheelhouse/*${{ matrix.python-version[1] }}**linux*.whl - name: Install annlite macos if: ${{ matrix.os == 'macos-latest' }} run: | pip install wheelhouse/*${{ matrix.python-version[1] }}**macos**x86_64*.whl - name: Install annlite win if: ${{ matrix.os == 'windows-latest'}} run: | pip install --pre --find-links=wheelhouse/ annlite - name: Test unix id: test_unix if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' }} run: | cd .. mv annlite/tests/ ./ pytest --suppress-no-test-exit-code --cov=annlite --cov-report=xml \ -v -s -m "not gpu" ${{ matrix.test-path }} echo "::set-output name=codecov_flag::annlite" timeout-minutes: 30 - name: Test win id: test_win if: ${{ matrix.os == 'windows-latest'}} env: PYTHONIOENCODING: 'utf-8' run: | cd .. move annlite/tests/ ./ cd tests/ pytest -v -s -m "not gpu" -k "test" timeout-minutes: 30 - name: Check codecov file id: check_files uses: andstor/file-existence-action@v1 with: files: "coverage.xml" - name: Upload coverage from test to Codecov uses: codecov/codecov-action@v2 if: steps.check_files.outputs.files_exists == 'true' && ${{ matrix.python-version[0] }} == '3.7' && ${{ matrix.matrix.os }} == 'ubuntu-latest' with: file: coverage.xml flags: ${{ steps.test.outputs.codecov_flag }} fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos prerelease: needs: [core-test] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 100 - uses: actions/download-artifact@v3 with: name: artifact - name: Pre-release (.devN) run: | git fetch --depth=1 origin +refs/tags/*:refs/tags/* pip install twine ./scripts/release.sh env: TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} JINA_SLACK_WEBHOOK: ${{ secrets.JINA_SLACK_WEBHOOK }} ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: jobs: commit-lint: runs-on: ubuntu-latest steps: - name: find the prev warning if exist uses: peter-evans/find-comment@v1 id: fc with: issue-number: ${{ github.event.pull_request.number }} comment-author: "github-actions[bot]" body-includes: "bad commit message" - name: Delete comment if exist if: ${{ steps.fc.outputs.comment-id != 0 }} uses: actions/github-script@v3 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | github.issues.deleteComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: ${{ steps.fc.outputs.comment-id }}, }) - uses: actions/checkout@v2 with: fetch-depth: 0 - run: 'echo "module.exports = {extends: [''@commitlint/config-conventional'']}" > commitlint.config.js' - uses: wagoid/commitlint-github-action@v1 env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - name: if lint failed if: ${{ failure() }} uses: peter-evans/create-or-update-comment@v1 with: issue-number: ${{ github.event.pull_request.number }} body: | Thanks for your contribution :heart: :broken_heart: Unfortunately, this PR has one ore more **bad commit messages**, it can not be merged. To fix this problem, please refer to: - [Commit Message Guideline for the First Time Contributor](https://github.com/jina-ai/jina/issues/553) - [Contributing Guideline](https://github.com/jina-ai/jina/blob/master/CONTRIBUTING.md) Note, other CI tests will *not* *start* until the commit messages get fixed. This message will be deleted automatically when the commit messages get fixed. reaction-type: "eyes" # lint-flake-8: # runs-on: ubuntu-latest # steps: # - uses: actions/checkout@v2 # - name: Set up Python 3.7 # uses: actions/setup-python@v2 # with: # python-version: 3.7 # - name: Lint with flake8 # run: | # pip install flake8 # # stop the build if there are Python syntax errors or undefined names # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude .git,__pycache__,docs/source/conf.py,old,build,dist,tests/,jina/resources/,bindings # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude .git,__pycache__,docs/source/conf.py,old,build,dist,tests/,jina/resources/ check-black: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Set up Python 3.7 uses: actions/setup-python@v2 with: python-version: 3.7 - id: file_changes uses: Ana06/get-changed-files@v1.2 - name: check black run: ./scripts/black.sh env: CHANGED_FILES: ${{ steps.file_changes.outputs.added_modified }} prep-testbed: runs-on: ubuntu-latest needs: [commit-lint, check-black] steps: - uses: actions/checkout@v2 - id: set-matrix run: | sudo apt-get install jq echo "::set-output name=matrix::$(bash scripts/get-all-test-paths.sh)" outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} core-test: needs: [prep-testbed] runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] cpython-version: ["cp37-*"] python-version: [3.7] cibw_arch: ["auto64"] test-path: ${{fromJson(needs.prep-testbed.outputs.matrix)}} env: JINA_HIDE_SURVEY: "1" steps: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - uses: actions/checkout@v3 - name: Prepare enviroment run: | python -m pip install --upgrade pip pip install jina pip install --pre docarray pip install pytest pytest-html pytest-cov pytest-mock pytest-repeat pytest-custom-exit-code pytest-timeout pytest-reraise - name: Build wheel uses: pypa/cibuildwheel@v2.10.2 with: package-dir: ./ env: CIBW_ENVIRONMENT: > STAN_BACKEND="${{ env.STAN_BACKEND }}" CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_BUILD: ${{ matrix.cpython-version }} CIBW_SKIP: "*musllinux*" CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BUILD_FRONTEND: build - name: Install annlite unix if: ${{ matrix.os == 'ubuntu-latest' }} run: | pip install wheelhouse/*linux*.whl - name: Install annlite win if: ${{ matrix.os == 'windows-latest'}} run: | pip install --pre --find-links=wheelhouse/ annlite - name: Test unix id: test_unix if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' }} run: | cd .. mv annlite/tests/ ./ pytest --suppress-no-test-exit-code --cov=annlite --cov-report=xml \ -v -s -m "not gpu" ${{ matrix.test-path }} echo "::set-output name=codecov_flag::annlite" timeout-minutes: 30 - name: Test win id: test_win if: ${{ matrix.os == 'windows-latest'}} env: PYTHONIOENCODING: 'utf-8' run: | cd .. move annlite/tests/ ./ cd tests/ pytest -v -s -m "not gpu" -k "test" echo "::set-output name=codecov_flag::annlite" timeout-minutes: 30 - name: Check codecov file id: check_files uses: andstor/file-existence-action@v1 with: files: "coverage.xml" - name: Upload coverage from test to Codecov uses: codecov/codecov-action@v2 if: steps.check_files.outputs.files_exists == 'true' && ${{ matrix.python-version }} == '3.7' with: file: coverage.xml flags: ${{ steps.test.outputs.codecov_flag }} fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos # just for blocking the merge until all parallel core-test are successful success-all-test: needs: [core-test] if: always() runs-on: ubuntu-latest steps: - uses: technote-space/workflow-conclusion-action@v2 - name: Check Failure if: env.WORKFLOW_CONCLUSION == 'failure' run: exit 1 - name: Success if: ${{ success() }} run: echo "All Done" ================================================ FILE: .github/workflows/force-release.yml ================================================ name: Manual Release on: workflow_dispatch: inputs: release_token: description: 'Your release token' required: true release_reason: description: 'Short reason for this manual release' required: true jobs: token-check: runs-on: ubuntu-latest steps: - run: echo "success!" if: "${{ github.event.inputs.release_token }} == ${{ env.release_token }}" env: release_token: ${{ secrets.ANNLITE_RELEASE_TOKEN }} build-wheels: needs: [token-check] runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] cibw_arch: [ "auto64" ] python-version: [ [ '3.7', "cp37-*" ], [ '3.8', "cp38-*" ], [ '3.9', "cp39-*" ], [ '3.10', "cp310-*" ] ] steps: - uses: actions/checkout@v3 with: fetch-depth: 100 - name: Set up Python ${{ matrix.python-version[0] }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version[0] }} - name: Build sdist if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version[0] == '3.7' }} run: | pip install build python -m build --sdist - name: Build wheels uses: pypa/cibuildwheel@v2.10.2 with: package-dir: ./ env: CIBW_ENVIRONMENT: > STAN_BACKEND="${{ env.STAN_BACKEND }}" CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_BUILD: ${{ matrix.python-version[1] }} CIBW_SKIP: "*musllinux*" CIBW_ARCHS: ${{ matrix.cibw_arch }} # CIBW_ARCH_MACOS: x86_64 arm64 CIBW_BUILD_FRONTEND: build - uses: actions/upload-artifact@v3 with: path: | ./wheelhouse/*.whl ./dist/*.tar.gz regular-release: needs: build-wheels runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: token: ${{ secrets.JINA_DEV_BOT }} fetch-depth: 100 # means max contribute history is limited to 100 lines # submodules: true - uses: actions/setup-python@v2 with: python-version: 3.7 - uses: actions/download-artifact@v3 with: name: artifact - run: | git fetch --depth=1 origin +refs/tags/*:refs/tags/* npm install git-release-notes pip install twine wheel ./scripts/release.sh final "${{ github.event.inputs.release_reason }}" "${{github.actor}}" env: TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} JINA_SLACK_WEBHOOK: ${{ secrets.JINA_SLACK_WEBHOOK }} - if: failure() run: echo "nothing to release" - name: bumping master version uses: ad-m/github-push-action@v0.6.0 with: github_token: ${{ secrets.JINA_DEV_BOT }} tags: true branch: main ================================================ FILE: .github/workflows/tag.yml ================================================ name: Release CD on: push: tags: - "v*" # push to version tags trigger the build #on: # push: # branches-ignore: # - '**' # temporally disable this action jobs: create-release: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 with: ref: 'main' - uses: actions/setup-python@v2 with: python-version: 3.7 - run: | python scripts/get-last-release-note.py - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token with: tag_name: ${{ github.ref }} release_name: 💫 Patch ${{ github.ref }} body_path: 'tmp.md' draft: false prerelease: false ================================================ FILE: .gitignore ================================================ # Initially taken from Github's Python gitignore file # local dev .vscode bindings/pq_bindings.cpp # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv docs/.python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ .idea/ toy*.py .DS_Store post/ toy*.ipynb data/ *.c .nes_cache toy*.yml *.tmp /junit/ /tests/junit/ /docs/chapters/proto/docs.md # IntelliJ IDEA *.iml .idea # Rust /target Cargo.lock toy*.py ================================================ FILE: .pre-commit-config.yaml ================================================ repos: #- repo: https://github.com/terrencepreilly/darglint # rev: v1.5.8 # hooks: # - id: darglint # files: annlite/ # exclude: docs/ # args: # - --message-template={path}:{line} {msg_id} {msg} # - -s=sphinx # - -z=full # - -v=2 #- repo: https://github.com/pycqa/pydocstyle # rev: 5.1.1 # pick a git hash / tag to point to # hooks: # - id: pydocstyle # files: annlite/ # exclude: docs/ # args: # - --select=D101,D102,D103 - repo: https://github.com/timothycrosley/isort rev: 5.12.0 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/psf/black rev: 22.3.0 hooks: - id: black types: [python] exclude: docs/ args: - -S - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: - id: trailing-whitespace - id: check-yaml - id: end-of-file-fixer - id: requirements-txt-fixer - id: double-quote-string-fixer - id: check-merge-conflict - id: fix-encoding-pragma args: ["--remove"] - id: mixed-line-ending args: ["--fix=lf"] - repo: https://github.com/pre-commit/mirrors-clang-format rev: "v14.0.3" hooks: - id: clang-format ================================================ FILE: CHANGELOG.md ================================================ ## Release Note (`0.3.0`) > Release time: 2022-03-04 11:49:06 🙇 We'd like to thank all contributors for this new release! In particular, numb3r3, felix-wang, David Buchaca Prats, 🙇 ### 🆕 New Features - [[```e4afbe17```](https://github.com/jina-ai/docarray/commit/e4afbe17c724f48de19bfbe6d6c127e62a8bcd5a)] __-__ add cd workflow (*numb3r3*) ### 🐞 Bug fixes - [[```2aeb5377```](https://github.com/jina-ai/docarray/commit/2aeb537779df6649fab143affa5c5decf8b2e364)] __-__ format (*numb3r3*) - [[```27ab16c2```](https://github.com/jina-ai/docarray/commit/27ab16c2622606e7bf77af5286620f0cdec4abb0)] __-__ build dep (*numb3r3*) - [[```7887ac95```](https://github.com/jina-ai/docarray/commit/7887ac95a7954c783264151cd26ae47319a47baa)] __-__ turn on upload pypi (#109) (*felix-wang*) - [[```2e4739ac```](https://github.com/jina-ai/docarray/commit/2e4739ac2f19e78bc23889ee2a1baef79be65e5d)] __-__ version (*numb3r3*) - [[```293a0dbf```](https://github.com/jina-ai/docarray/commit/293a0dbfe8b063c66ec4b8b710f44eb1c8ff4041)] __-__ release script (*numb3r3*) - [[```e77c95d9```](https://github.com/jina-ai/docarray/commit/e77c95d97fe54bb409ce39931de86fb45fdc6f41)] __-__ cov (*numb3r3*) - [[```287de379```](https://github.com/jina-ai/docarray/commit/287de379686fc4eb91afd99f32a85e3dcbc6bf27)] __-__ install in cd (*numb3r3*) - [[```88b8e0b6```](https://github.com/jina-ai/docarray/commit/88b8e0b6c033f33d76e5ec3e7e95f3de14d7ed70)] __-__ pip install in ci (*numb3r3*) - [[```348e0444```](https://github.com/jina-ai/docarray/commit/348e044463039c1a4ed60c43c1f83353378a86f7)] __-__ release (*numb3r3*) - [[```beb35f29```](https://github.com/jina-ai/docarray/commit/beb35f29c1f695728e7c43b235abb97a71f797ca)] __-__ setup pytest (*numb3r3*) - [[```e9ea4b89```](https://github.com/jina-ai/docarray/commit/e9ea4b8942d48034a7a471e2b91a6b542913f7c9)] __-__ workflow yml (*numb3r3*) - [[```5f0e21ec```](https://github.com/jina-ai/docarray/commit/5f0e21ec85a09a1dabc618841eb9fcc4c56c34bb)] __-__ ci yml (*numb3r3*) - [[```db58e283```](https://github.com/jina-ai/docarray/commit/db58e2835f758c94ec73fa3ccc795c286fdb4e86)] __-__ setup.py (*numb3r3*) - [[```bdc1c7a2```](https://github.com/jina-ai/docarray/commit/bdc1c7a276086b702c807112d0603c245476944a)] __-__ __cicd__: add scripts (*numb3r3*) ### 🧼 Code Refactoring - [[```9c693adb```](https://github.com/jina-ai/docarray/commit/9c693adb438742b633d04f554e442ceff9da923d)] __-__ remove pqlite namings (*David Buchaca Prats*) - [[```793f4711```](https://github.com/jina-ai/docarray/commit/793f4711c42d3a717e9e60e3548c222f1ecc7de2)] __-__ rename project (*numb3r3*) ### 🍹 Other Improvements - [[```f0c6d809```](https://github.com/jina-ai/docarray/commit/f0c6d809d47a6142f5ccf40c7cc7cf24045ad2e5)] __-__ bump version (*numb3r3*) ## Release Note (`0.3.1`) > Release time: 2022-03-04 16:29:52 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, Jina Dev Bot, 🙇 ### 🆕 New Features - [[```44298038```](https://github.com/jina-ai/docarray/commit/44298038e4e79f3dd0c3532ba6ad5b64d1a35caf)] __-__ add tag release workflow (#110) (*felix-wang*) ### 🍹 Other Improvements - [[```d15289f9```](https://github.com/jina-ai/docarray/commit/d15289f9912f158c7b9a7ec7d5f58188ecb19cf2)] __-__ __version__: the next version will be 0.3.1 (*Jina Dev Bot*) ## Release Note (`0.3.2`) > Release time: 2022-06-09 10:04:30 🙇 We'd like to thank all contributors for this new release! In particular, David Buchaca Prats, Gustavo Ye, felix-wang, Han Xiao, numb3r3, Jina Dev Bot, 🙇 ### 🆕 New Features - [[```a7804baf```](https://github.com/jina-ai/docarray/commit/a7804bafc90064f929afff62192f19e44406b5d1)] __-__ add filter method (#121) (*David Buchaca Prats*) ### 🐞 Bug fixes - [[```9448fcab```](https://github.com/jina-ai/docarray/commit/9448fcab060efe5b59108f3cac183aeb432a81d6)] __-__ only apply black with changed py files (#118) (*felix-wang*) - [[```e4e706e3```](https://github.com/jina-ai/docarray/commit/e4e706e313ba5cbfb7083a5dea9e75b8d2813394)] __-__ upgrade traverse path (#113) (*felix-wang*) - [[```a0fc7cac```](https://github.com/jina-ai/docarray/commit/a0fc7cac7722d108143f6590c3435c77376f76bd)] __-__ tag yml (*numb3r3*) ### 🍹 Other Improvements - [[```2ce1ec22```](https://github.com/jina-ai/docarray/commit/2ce1ec2283b381f5153ea60141a6bb474bbf0f0c)] __-__ __cpp/h__: clang-format (#117) (*Gustavo Ye*) - [[```a6168400```](https://github.com/jina-ai/docarray/commit/a61684000af4518aafa41d1d9dea47766bedf247)] __-__ update readme (*Han Xiao*) - [[```aec33c75```](https://github.com/jina-ai/docarray/commit/aec33c75b8ce883e044a828fbae7142f71dbb05a)] __-__ __version__: the next version will be 0.3.2 (*Jina Dev Bot*) ## Release Note (`0.3.3`) > Release time: 2022-07-04 13:57:38 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, Gustavo Ye, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```e240128f```](https://github.com/jina-ai/docarray/commit/e240128f403bde04bc21e1ac5ce3baa5507db687)] __-__ proto version bump (#126) (*felix-wang*) - [[```f62809b5```](https://github.com/jina-ai/docarray/commit/f62809b5d90f4fca1762c6a1c13beee40e972212)] __-__ change input type to `data_t` (#123) (*Gustavo Ye*) ### 🍹 Other Improvements - [[```f7f7b751```](https://github.com/jina-ai/docarray/commit/f7f7b75104a28f1860d3cdc94c87f526e742db51)] __-__ __version__: the next version will be 0.3.3 (*Jina Dev Bot*) ## Release Note (`0.3.4`) > Release time: 2022-07-20 07:12:22 🙇 We'd like to thank all contributors for this new release! In particular, Jie Fu, Jina Dev Bot, 🙇 ### 🆕 New Features - [[```1a9de05c```](https://github.com/jina-ai/docarray/commit/1a9de05cdcbb861cd036b2ed9a3a66fa84e707f9)] __-__ copy executor.py to annlite folder (#129) (*Jie Fu*) ### 🍹 Other Improvements - [[```5225e6cb```](https://github.com/jina-ai/docarray/commit/5225e6cbec7df3fbf4dbd7ab404d34bb7d899a29)] __-__ __version__: the next version will be 0.3.4 (*Jina Dev Bot*) ## Release Note (`0.3.5`) > Release time: 2022-07-29 08:17:02 🙇 We'd like to thank all contributors for this new release! In particular, Jie Fu, Jina Dev Bot, 🙇 ### 🆕 New Features - [[```534eed81```](https://github.com/jina-ai/docarray/commit/534eed810e6b56238f0b71cc4123783236eac7fd)] __-__ support load and save hnsw (#133) (*Jie Fu*) ### 🍹 Other Improvements - [[```1f76fb5d```](https://github.com/jina-ai/docarray/commit/1f76fb5dedc79d331dc655d6409718457c6c05d7)] __-__ Feat task queue (#131) (*Jie Fu*) - [[```103b5bf3```](https://github.com/jina-ai/docarray/commit/103b5bf31b59ca041d7ca70b8e0442421bcdf279)] __-__ __version__: the next version will be 0.3.5 (*Jina Dev Bot*) ## Release Note (`0.3.6`) > Release time: 2022-08-23 10:45:36 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, Jie Fu, Gustavo Ye, Jina Dev Bot, 🙇 ### 🆕 New Features - [[```690098be```](https://github.com/jina-ai/docarray/commit/690098be576fd5d6149e0502b9d2bf100b726f27)] __-__ add pq support for hnsw searching (#122) (*Gustavo Ye*) - [[```ad5a5fe3```](https://github.com/jina-ai/docarray/commit/ad5a5fe39293f771b9945102b45f05bebfaf3ad6)] __-__ indexer dumploader (#137) (*felix-wang*) - [[```29019e3d```](https://github.com/jina-ai/docarray/commit/29019e3da94d0a6025c91919a8978966f073b608)] __-__ integrate annlite with projector (#136) (*Jie Fu*) - [[```14c95986```](https://github.com/jina-ai/docarray/commit/14c9598602741a5558c02a98bd123b34cddc32b8)] __-__ implement pca (#135) (*Jie Fu*) ### 🐞 Bug fixes - [[```9f74de8f```](https://github.com/jina-ai/docarray/commit/9f74de8f34bc51ab740a05e9bfabf0be4ec77b87)] __-__ reload duplicate storage (#143) (*felix-wang*) - [[```7010d778```](https://github.com/jina-ai/docarray/commit/7010d77869dfd2a44ebaeb4a697bbac3e01d6970)] __-__ fix update/delete (#140) (*Jie Fu*) - [[```8a05887d```](https://github.com/jina-ai/docarray/commit/8a05887dc68d4219190de8767c98fc5a1740e3a2)] __-__ composite pca and pq (#139) (*felix-wang*) ### 🍹 Other Improvements - [[```157a1a9c```](https://github.com/jina-ai/docarray/commit/157a1a9c189f73f5757d2ec60c4427e41cd130bd)] __-__ __version__: the next version will be 0.3.6 (*Jina Dev Bot*) ## Release Note (`0.3.7`) > Release time: 2022-08-26 04:48:47 🙇 We'd like to thank all contributors for this new release! In particular, Jie Fu, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```01cf7a89```](https://github.com/jina-ai/docarray/commit/01cf7a8960f27abcbc414faf2d274e0d8da470db)] __-__ fix np.int64 issue (#146) (*Jie Fu*) ### 🍹 Other Improvements - [[```b602acce```](https://github.com/jina-ai/docarray/commit/b602acce8f7f2ac085f9cbccae6fadeee1ebcb85)] __-__ __version__: the next version will be 0.3.7 (*Jina Dev Bot*) ## Release Note (`0.3.8`) > Release time: 2022-08-26 11:12:19 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```a5eaa53e```](https://github.com/jina-ai/docarray/commit/a5eaa53e84aa374e061ca0bfddc7797fe369f8ec)] __-__ insert when not found at update (#151) (*felix-wang*) - [[```18af2d4c```](https://github.com/jina-ai/docarray/commit/18af2d4c42ced2ca9d393456ba9722953edce383)] __-__ normalize training data for cosine metric (#150) (*felix-wang*) ### 🍹 Other Improvements - [[```f0fb1c4b```](https://github.com/jina-ai/docarray/commit/f0fb1c4b37c89fea31fc24fb47b60181f0cd3218)] __-__ __version__: the next version will be 0.3.8 (*Jina Dev Bot*) ## Release Note (`0.3.9`) > Release time: 2022-08-31 03:46:53 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, Gustavo Ye, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```27589456```](https://github.com/jina-ai/docarray/commit/27589456fe72c3e9a1fe7d6c8d198e0e71fd9a89)] __-__ reload indexer (#154) (*felix-wang*) - [[```cdbe7256```](https://github.com/jina-ai/docarray/commit/cdbe725691cf5cc19db5df250d6d9d384bc94437)] __-__ __py__: roll back the original decorator (#142) (*Gustavo Ye*) ### 🍹 Other Improvements - [[```f34062b2```](https://github.com/jina-ai/docarray/commit/f34062b2249a3fce8cb51dc9e8b8497d33a54a2f)] __-__ __version__: the next version will be 0.3.9 (*Jina Dev Bot*) ## Release Note (`0.3.10`) > Release time: 2022-09-06 09:40:07 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, numb3r3, Jie Fu, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```bc7f5ae2```](https://github.com/jina-ai/docarray/commit/bc7f5ae2e8e83cef919052a9d8a3a954a1cd813f)] __-__ allow columns as dict format (#160) (*felix-wang*) - [[```25c3ec3d```](https://github.com/jina-ai/docarray/commit/25c3ec3d616a9ece53c710d734ba71d0eb6fc19c)] __-__ thread safe index and search (#157) (*felix-wang*) - [[```d235cb83```](https://github.com/jina-ai/docarray/commit/d235cb83a2d8399f1120b3ebf6e54392ecb37efa)] __-__ executor unittest (#156) (*felix-wang*) ### 🏁 Unit Test and CICD - [[```e6543a28```](https://github.com/jina-ai/docarray/commit/e6543a28894aad5481d1a6a5bbd8f070ca979a90)] __-__ add docarray tests (#153) (*Jie Fu*) ### 🍹 Other Improvements - [[```6a774648```](https://github.com/jina-ai/docarray/commit/6a774648d772ad98f42641c6094192bf4e7d1877)] __-__ update logo (*numb3r3*) - [[```33fd66be```](https://github.com/jina-ai/docarray/commit/33fd66be1135ebe63f2a1e1366196c8712e2bc63)] __-__ update readme (*numb3r3*) - [[```e1ad3a55```](https://github.com/jina-ai/docarray/commit/e1ad3a55d4732a115e6a925075b7b5ed768cd9f8)] __-__ update log (*numb3r3*) - [[```c2630257```](https://github.com/jina-ai/docarray/commit/c26302572de4e755d64ffee71b017e798f790e9e)] __-__ update readme (#149) (*felix-wang*) - [[```c1fd8cfe```](https://github.com/jina-ai/docarray/commit/c1fd8cfe746d94de2ebef843f6e13384f266773b)] __-__ __version__: the next version will be 0.3.10 (*Jina Dev Bot*) ## Release Note (`0.3.11`) > Release time: 2022-09-08 06:17:25 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```0b435eb6```](https://github.com/jina-ai/docarray/commit/0b435eb6c5a737d370b389ba3c2395796febe95f)] __-__ bump pybind11 (#162) (*felix-wang*) ### 🍹 Other Improvements - [[```9c43f02c```](https://github.com/jina-ai/docarray/commit/9c43f02c5ec9face32ee58858b8ab25d65edbf12)] __-__ __version__: the next version will be 0.3.11 (*Jina Dev Bot*) ## Release Note (`0.3.12`) > Release time: 2022-09-19 05:15:49 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, YangXiuyu, numb3r3, Jina Dev Bot, 🙇 ### 🆕 New Features - [[```4eb61d3d```](https://github.com/jina-ai/docarray/commit/4eb61d3dea5a86d5212dafaaa0c1ecd87abc399a)] __-__ add limit and offset for filtering (#167) (*felix-wang*) ### 📗 Documentation - [[```6ecfa833```](https://github.com/jina-ai/docarray/commit/6ecfa833e3d0f69a1a1b4992724753c89fa8d96f)] __-__ fix typo (#168) (*YangXiuyu*) ### 🍹 Other Improvements - [[```6902a8b1```](https://github.com/jina-ai/docarray/commit/6902a8b15763e73fd1cd90827ccfdc758f2fce32)] __-__ add description about hnsw parameters (#169) (*felix-wang*) - [[```aec2d605```](https://github.com/jina-ai/docarray/commit/aec2d605d43394638168282c78e13f7f2150200a)] __-__ update readme (*numb3r3*) - [[```95541bad```](https://github.com/jina-ai/docarray/commit/95541bad119dd2bb03e9894f32f57578cd3d8a7a)] __-__ __version__: the next version will be 0.3.12 (*Jina Dev Bot*) ## Release Note (`0.3.13`) > Release time: 2022-09-26 02:04:59 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, Jie Fu, numb3r3, Jina Dev Bot, 🙇 ### 🆕 New Features - [[```f98a8336```](https://github.com/jina-ai/docarray/commit/f98a83368e9fc81d82abb2be9c8a6569ee45e177)] __-__ change sqlite in_memory to false (#173) (*Jie Fu*) ### 🐞 Bug fixes - [[```b5f8694f```](https://github.com/jina-ai/docarray/commit/b5f8694fb5c33af15cc9b4a140a0165bf3aa01f4)] __-__ __hnswlib__: cannot find sufficient data with filtering (#176) (*felix-wang*) - [[```0aa07863```](https://github.com/jina-ai/docarray/commit/0aa078633d7b6b850a3ea0ffe651dbad06240cc0)] __-__ __ci__: unittest (#175) (*felix-wang*) ### 🧼 Code Refactoring - [[```896c5006```](https://github.com/jina-ai/docarray/commit/896c5006a6cac5f6b144bcb452ec5b2169ca4c88)] __-__ use rockdb as the storage backend (#171) (*felix-wang*) ### 🍹 Other Improvements - [[```66257b41```](https://github.com/jina-ai/docarray/commit/66257b4147836a8e8b7c17dd12537143509e78a2)] __-__ Revert "refactor: use rockdb as the storage backend (#171)" (*numb3r3*) - [[```e56aae75```](https://github.com/jina-ai/docarray/commit/e56aae757e65e5f8294a15b6ba998e00bda97e1a)] __-__ __version__: the next version will be 0.3.13 (*Jina Dev Bot*) ## Release Note (`0.4.0`) > Release time: 2022-10-24 09:53:20 🙇 We'd like to thank all contributors for this new release! In particular, numb3r3, Jie Fu, YangXiuyu, felix-wang, Ziniu Yu, Gustavo Ye, Jina Dev Bot, 🙇 ### 🆕 New Features - [[```1cd07f2c```](https://github.com/jina-ai/docarray/commit/1cd07f2c33e4f1f4e756baab4a6cd0a1452996cf)] __-__ hub as remote storage (#177) (*YangXiuyu*) - [[```90db6006```](https://github.com/jina-ai/docarray/commit/90db60067205d8e64a63c0c7eb4e782ac04d1033)] __-__ add flag for close (#191) (*Jie Fu*) - [[```3b782900```](https://github.com/jina-ai/docarray/commit/3b7829004edfb4f6cf4b77048a747a0d338c21b3)] __-__ cibuildwheels (#186) (*YangXiuyu*) ### 🐞 Bug fixes - [[```8c807937```](https://github.com/jina-ai/docarray/commit/8c80793733e96680d2ca846371ebc3c684f1d5d7)] __-__ restore when initializing annlite (#201) (*Jie Fu*) - [[```98f94d0f```](https://github.com/jina-ai/docarray/commit/98f94d0f4ff1bd0b1bc1b99d828d6fd21932dc2b)] __-__ set is_train=true after loading pca (#199) (*Jie Fu*) - [[```0b9bb413```](https://github.com/jina-ai/docarray/commit/0b9bb413abe631ea07bd7358160060b16cafa827)] __-__ unittest (#198) (*YangXiuyu*) - [[```0d1d5a2e```](https://github.com/jina-ai/docarray/commit/0d1d5a2e58eb043397f24a1d6efcee07c22e2cb1)] __-__ pre-release (*numb3r3*) - [[```fdcf3818```](https://github.com/jina-ai/docarray/commit/fdcf3818f1b8a2b11df96f4c6c72e972bab91238)] __-__ cd release version (#197) (*YangXiuyu*) - [[```f4bbc495```](https://github.com/jina-ai/docarray/commit/f4bbc49529a2ac4174bbb0cc7c88ab8666bd034c)] __-__ clean codes (*numb3r3*) - [[```6bedafe4```](https://github.com/jina-ai/docarray/commit/6bedafe4c49ce8c4831b4cc01c1b8702924e4b8f)] __-__ test-paths (*numb3r3*) - [[```beb79b23```](https://github.com/jina-ai/docarray/commit/beb79b23a1b1412bad402410a0b90393f0e535b6)] __-__ __cd__: combine build and test (*numb3r3*) - [[```19652ed0```](https://github.com/jina-ai/docarray/commit/19652ed0b6ef0ffc9dc1c3d2dc2640075e4d6d13)] __-__ cd workflow (#196) (*YangXiuyu*) - [[```4fe2b3be```](https://github.com/jina-ai/docarray/commit/4fe2b3be51ed0b571c6f0b9c9f20a68c18b7486c)] __-__ cd workflow (#195) (*YangXiuyu*) - [[```4fdacde2```](https://github.com/jina-ai/docarray/commit/4fdacde2a22a5e6fb0edc15bb6f77bf4d8f25834)] __-__ cd workflow (#193) (*YangXiuyu*) - [[```494fbfcb```](https://github.com/jina-ai/docarray/commit/494fbfcbb96257168542f56ec617a9dc9082c084)] __-__ cd release (#192) (*felix-wang*) - [[```d2803ce0```](https://github.com/jina-ai/docarray/commit/d2803ce00e41a69470a3001e4677927b407c3282)] __-__ cd workflow (#190) (*felix-wang*) - [[```fe9db3d9```](https://github.com/jina-ai/docarray/commit/fe9db3d9f7e04cbad96832409dd1d3159195c060)] __-__ build on apple silicon (#188) (*Ziniu Yu*) - [[```bb402ae9```](https://github.com/jina-ai/docarray/commit/bb402ae9674a8410686b8dde3aea82f1b86fc10b)] __-__ __bindings__: build on windows (#183) (*YangXiuyu*) - [[```efc18f80```](https://github.com/jina-ai/docarray/commit/efc18f80228ef2371c19fbb205cabb94afd47385)] __-__ update executor parameter (#180) (*YangXiuyu*) - [[```f142089a```](https://github.com/jina-ai/docarray/commit/f142089a419bba3d7e23b2c34b16455ddbcb805d)] __-__ executor tests (#179) (*felix-wang*) - [[```79e171d4```](https://github.com/jina-ai/docarray/commit/79e171d411f31dad7231bce1682ac78ff7b3e1b2)] __-__ __annliteindexer__: annlite executor integration (#170) (*YangXiuyu*) ### 🧼 Code Refactoring - [[```e8c59907```](https://github.com/jina-ai/docarray/commit/e8c59907540bada5282a413b43d64f11596678d3)] __-__ use rocksdb as the docs storage engine (#178) (*felix-wang*) ### 🍹 Other Improvements - [[```e6dfcd2a```](https://github.com/jina-ai/docarray/commit/e6dfcd2a7ff0fdd149dd606487f7aa0107775c63)] __-__ bump to v0.4.0 (*numb3r3*) - [[```6ce03fd0```](https://github.com/jina-ai/docarray/commit/6ce03fd04e5bb63da3c3d2cf1c620ac77daf6c2a)] __-__ release (*numb3r3*) - [[```02857ec4```](https://github.com/jina-ai/docarray/commit/02857ec4103e32139f695adac4f7a40c4e65c67a)] __-__ Add pq dist table support (#158) (*Gustavo Ye*) - [[```ff54290b```](https://github.com/jina-ai/docarray/commit/ff54290bfb19d07b953aa00d5d52cb1e8f805d3a)] __-__ __version__: the next version will be 0.3.14 (*Jina Dev Bot*) ## Release Note (`0.5.0`) > Release time: 2022-10-26 07:12:58 🙇 We'd like to thank all contributors for this new release! In particular, numb3r3, felix-wang, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```de5273ab```](https://github.com/jina-ai/docarray/commit/de5273ab7cf81ba26bb4307cd66f3fc50974b8ef)] __-__ undo wheels uploading (#203) (*felix-wang*) ### 🍹 Other Improvements - [[```5faf1dfa```](https://github.com/jina-ai/docarray/commit/5faf1dfa3326213c3b9e20de0d8b7aca4dbdf6ba)] __-__ regular release (*numb3r3*) - [[```95e51f26```](https://github.com/jina-ai/docarray/commit/95e51f269ac130b9d54b004734ff68afc07a9cf1)] __-__ __version__: the next version will be 0.4.1 (*Jina Dev Bot*) ## Release Note (`0.5.1`) > Release time: 2022-11-03 03:29:55 🙇 We'd like to thank all contributors for this new release! In particular, numb3r3, Jie Fu, Jina Dev Bot, 🙇 ### 🆕 New Features - [[```dd08f6f2```](https://github.com/jina-ai/docarray/commit/dd08f6f28008aaf020cb972f45e154afaf99cb28)] __-__ add file splitter and merger to support big file transfer to hubble (#202) (*Jie Fu*) ### 🍹 Other Improvements - [[```83e45001```](https://github.com/jina-ai/docarray/commit/83e4500180e98bb967ab2ec17ca202c1702fb411)] __-__ bump 0.5.1 (*numb3r3*) - [[```557c624b```](https://github.com/jina-ai/docarray/commit/557c624b3419b8bf70386e66a75d1958ef427738)] __-__ fix force release (*numb3r3*) - [[```77312214```](https://github.com/jina-ai/docarray/commit/773122144a9ec39663829c64090d6116585fb6e1)] __-__ __version__: the next version will be 0.5.1 (*Jina Dev Bot*) ## Release Note (`0.5.2`) > Release time: 2022-11-03 07:10:10 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```3f886f10```](https://github.com/jina-ai/docarray/commit/3f886f10d17195913cea839f3e4ac8631426ad5c)] __-__ restore from latest snapshot (#204) (*felix-wang*) ### 🍹 Other Improvements - [[```2462afa4```](https://github.com/jina-ai/docarray/commit/2462afa4451c66603cc1e25bf755c97a97b7eec1)] __-__ __version__: the next version will be 0.5.2 (*Jina Dev Bot*) ## Release Note (`0.5.4`) > Release time: 2022-11-04 04:27:28 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```f2ce3a9a```](https://github.com/jina-ai/docarray/commit/f2ce3a9a4b35a71a492fbe63203114e0d7fa6b52)] __-__ bump rocksdict (#206) (*felix-wang*) ### 🍹 Other Improvements - [[```c84635c9```](https://github.com/jina-ai/docarray/commit/c84635c9854b7b3c2b5718941979720b673ed7eb)] __-__ __version__: the next version will be 0.5.3 (*Jina Dev Bot*) ## Release Note (`0.5.5`) > Release time: 2022-12-22 03:27:38 🙇 We'd like to thank all contributors for this new release! In particular, Jie Fu, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```61e0a57f```](https://github.com/jina-ai/docarray/commit/61e0a57f901b087fbe6cab16aefb1d46d5fd54a2)] __-__ fix offset factory (#208) (*Jie Fu*) ### 🍹 Other Improvements - [[```f6fdc9d3```](https://github.com/jina-ai/docarray/commit/f6fdc9d34fd8e8de47e7ac68e6813956c2aeebfe)] __-__ __version__: the next version will be 0.5.5 (*Jina Dev Bot*) ## Release Note (`0.5.6`) > Release time: 2023-02-08 09:06:21 🙇 We'd like to thank all contributors for this new release! In particular, Jie Fu, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```906ed7d4```](https://github.com/jina-ai/docarray/commit/906ed7d4159527e8b5f00cdc9106433925f04a43)] __-__ save and load offset2ids for list_like feature (#210) (*Jie Fu*) ### 🏁 Unit Test and CICD - [[```a2519f0f```](https://github.com/jina-ai/docarray/commit/a2519f0f323d0c3cb2d68ad1cd3eeed4beb40ce6)] __-__ add test for offset2ids saving and loading (#212) (*Jie Fu*) ### 🍹 Other Improvements - [[```68215807```](https://github.com/jina-ai/docarray/commit/68215807953eed366e1d6f48fbbbbc1abebf13ff)] __-__ __version__: the next version will be 0.5.6 (*Jina Dev Bot*) ## Release Note (`0.5.7`) > Release time: 2023-02-21 12:34:55 🙇 We'd like to thank all contributors for this new release! In particular, Jie Fu, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```4a66d0c0```](https://github.com/jina-ai/docarray/commit/4a66d0c06e7497cb94cd18da505a2e309594b20c)] __-__ remove delete tags for cell_table and meta_table (#215) (*Jie Fu*) ### 🍹 Other Improvements - [[```eee752fe```](https://github.com/jina-ai/docarray/commit/eee752fe091dc5b5b519bb2aa866cb7d5dc48822)] __-__ __version__: the next version will be 0.5.7 (*Jina Dev Bot*) ## Release Note (`0.5.8`) > Release time: 2023-03-24 11:13:35 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, YangXiuyu, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```bfeb7e94```](https://github.com/jina-ai/docarray/commit/bfeb7e94c879db263dc4931b54b6b87dac09df81)] __-__ delete and update (#222) (*felix-wang*) - [[```7783b30f```](https://github.com/jina-ai/docarray/commit/7783b30fd55a10bd2dd0c75dc0451f0fe1834927)] __-__ __hnsw__: bump hnswlib to v0.6.2 (#185) (*YangXiuyu*) ### 🍹 Other Improvements - [[```4c145ddd```](https://github.com/jina-ai/docarray/commit/4c145ddd19abb4caec479941d1c0ffb03c4cfcf3)] __-__ __version__: the next version will be 0.5.8 (*Jina Dev Bot*) ## Release Note (`0.5.9`) > Release time: 2023-04-07 04:16:28 🙇 We'd like to thank all contributors for this new release! In particular, felix-wang, Jina Dev Bot, 🙇 ### 🐞 Bug fixes - [[```ab807ff6```](https://github.com/jina-ai/docarray/commit/ab807ff69e533ec90e0b0f6c782749267324d177)] __-__ bump rocksdb >= 0.3.9 (#225) (*felix-wang*) ### 🍹 Other Improvements - [[```3668c3fa```](https://github.com/jina-ai/docarray/commit/3668c3fa6f7a2d2af39549987637e11e385a75bb)] __-__ __version__: the next version will be 0.5.9 (*Jina Dev Bot*) ## Release Note (`0.5.10`) > Release time: 2023-04-19 03:12:50 🙇 We'd like to thank all contributors for this new release! In particular, Jie Fu, Jina Dev Bot, 🙇 ### 🍹 Other Improvements - [[```d55d544e```](https://github.com/jina-ai/docarray/commit/d55d544e876653bb47e79e2dd00f859c3ef00ffa)] __-__ bumping docarray (#227) (*Jie Fu*) - [[```86057c69```](https://github.com/jina-ai/docarray/commit/86057c69401e2b6d63822be2c3c3a0f63d4661a6)] __-__ __version__: the next version will be 0.5.10 (*Jina Dev Bot*) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MANIFEST.in ================================================ include setup.py include requirements.txt include pyproject.toml global-include *.pyx recursive-include include/hnswlib/ * ================================================ FILE: Makefile ================================================ pypi: dist twine upload dist/* dist: rm -rf dist/* pip install build python -m build --sdist test: python -m unittest discover --start-directory tests --pattern "*_test*.py" clean: rm -rf *.egg-info build dist tmp var tests/__pycache__ annlite/*.so .PHONY: dist ================================================ FILE: README.md ================================================




AnnLite logo: A fast and efficient ann libray

A fast embedded library for approximate nearest neighbor search

GitHub PyPI Codecov branch

## What is AnnLite? `AnnLite` is a *lightweight* and *embeddable* library for **fast** and **filterable** *approximate nearest neighbor search* (ANNS). It allows to search for nearest neighbors in a dataset of millions of points with a Pythonic API. **Highlighted features:** - 🐥 **Easy-to-use**: a simple API is designed to be used with Python. It is easy to use and intuitive to set up to production. - 🐎 **Fast**: the library uses a highly optimized approximate nearest neighbor search algorithm (*HNSW*) to search for nearest neighbors. - 🔎 **Filterable**: the library allows you to search for nearest neighbors within a subset of the dataset. - 🍱 **Integration**: Smooth integration with neural search ecosystem including [Jina](https://github.com/jina-ai/jina) and [DocArray](https://github.com/jina-ai/docarray), so that users can easily expose search API with **gRPC** and/or **HTTP**. The library is easy to install and use. It is designed to be used with Python. ## Installation To use AnnLite, you need to first install it. The easiest way to install AnnLite is using `pip`: ```bash pip install -U annlite ``` or install from source: ```bash python setup.py install ``` ## Quick start Before you start, you need to know some experience about [DocArray](https://github.com/jina-ai/docarray). `AnnLite` is designed to be used with [DocArray](https://github.com/jina-ai/docarray), so you need to know how to use `DocArray` first. For example, you can create a `DocArray` with `1000` random vectors with `128` dimensions: ```python from docarray import DocumentArray import numpy as np docs = DocumentArray.empty(1000) docs.embeddings = np.random.random([1000, 128]).astype(np.float32) ``` ### Index Then you can create an `AnnIndexer` to index the created `docs` and search for nearest neighbors: ```python from annlite import AnnLite ann = AnnLite(128, metric='cosine', data_path="/tmp/annlite_data") ann.index(docs) ``` Note that this will create a directory `/tmp/annlite_data` to persist the documents indexed. If this directory already exists, the index will be loaded from the directory. And if you want to create a new index, you can delete the directory first. ### Search Then you can search for nearest neighbors for some query docs with `ann.search()`: ```python query = DocumentArray.empty(5) query.embeddings = np.random.random([5, 128]).astype(np.float32) result = ann.search(query) ``` Then, you can inspect the retrieved docs for each query doc inside `query` matches: ```python for q in query: print(f'Query {q.id}') for k, m in enumerate(q.matches): print(f'{k}: {m.id} {m.scores["cosine"]}') ``` ```bash Query ddbae2073416527bad66ff186543eff8 0: 47dcf7f3fdbe3f0b8d73b87d2a1b266f {'value': 0.17575037} 1: 7f2cbb8a6c2a3ec7be024b750964f317 {'value': 0.17735684} 2: 2e7eed87f45a87d3c65c306256566abb {'value': 0.17917466} Query dda90782f6514ebe4be4705054f74452 0: 6616eecba99bd10d9581d0d5092d59ce {'value': 0.14570713} 1: d4e3147fc430de1a57c9883615c252c6 {'value': 0.15338594} 2: 5c7b8b969d4381f405b8f07bc68f8148 {'value': 0.15743542} ... ``` Or shorten the loop as one-liner using the element & attribute selector: ```python print(query['@m', ('id', 'scores__cosine')]) ``` ### Query You can get specific document by its id: ```python doc = ann.get_doc_by_id('') ``` And you can also get the documents with `limit` and `offset`, which is useful for pagination: ```python docs = ann.get_docs(limit=10, offset=0) ``` Furthermore, you can also get the documents ordered by a specific column from the index: ```python docs = ann.get_docs(limit=10, offset=0, order_by='x', ascending=True) ``` **Note**: the `order_by` column must be one of the `columns` in the index. ### Update After you have indexed the `docs`, you can update the docs in the index by calling `ann.update()`: ```python updated_docs = docs.sample(10) updated_docs.embeddings = np.random.random([10, 128]).astype(np.float32) ann.update(updated_docs) ``` ### Delete And finally, you can delete the docs from the index by calling `ann.delete()`: ```python to_delete = docs.sample(10) ann.delete(to_delete) ``` ## Search with filters To support search with filters, the annlite must be created with `colums` parameter, which is a series of fields you want to filter by. At the query time, the annlite will filter the dataset by providing `conditions` for certain fields. ```python import annlite # the column schema: (name:str, dtype:type, create_index: bool) ann = annlite.AnnLite(128, columns=[('price', float)], data_path="/tmp/annlite_data") ``` Then you can insert the docs, in which each doc has a field `price` with a float value contained in the `tags`: ```python import random docs = DocumentArray.empty(1000) docs = DocumentArray( [ Document(id=f'{i}', tags={'price': random.random()}) for i in range(1000) ] ) docs.embeddings = np.random.random([1000, 128]).astype(np.float32) ann.index(docs) ``` Then you can search for nearest neighbors with filtering conditions as: ```python query = DocumentArray.empty(5) query.embeddings = np.random.random([5, 128]).astype(np.float32) ann.search(query, filter={"price": {"$lte": 50}}, limit=10) print(f'the result with filtering:') for i, q in enumerate(query): print(f'query [{i}]:') for m in q.matches: print(f'\t{m.id} {m.scores["euclidean"].value} (price={m.tags["price"]})') ``` The `conditions` parameter is a dictionary of conditions. The key is the field name, and the value is a dictionary of conditions. The query language is the same as [MongoDB Query Language](https://docs.mongodb.com/manual/reference/operator/query/). We currently support a subset of those selectors. - `$eq` - Equal to (number, string) - `$ne` - Not equal to (number, string) - `$gt` - Greater than (number) - `$gte` - Greater than or equal to (number) - `$lt` - Less than (number) - `$lte` - Less than or equal to (number) - `$in` - Included in an array - `$nin` - Not included in an array The query will be performed on the field if the condition is satisfied. The following is an example of a query: 1. A Nike shoes with white color ```python { "brand": {"$eq": "Nike"}, "category": {"$eq": "Shoes"}, "color": {"$eq": "White"} } ``` We also support boolean operators `$or` and `$and`: ```python { "$and": { "brand": {"$eq": "Nike"}, "category": {"$eq": "Shoes"}, "color": {"$eq": "White"} } } ``` 2. A Nike shoes or price less than `100$`: ```python { "$or": { "brand": {"$eq": "Nike"}, "price": {"$lte": 100} } } ``` ## Dump and Load By default, the hnsw index is in memory. You can dump the index to `data_path` by calling `.dump()`: ```python from annlite import AnnLite ann = AnnLite(128, metric='cosine', data_path="/path/to/data_path") ann.index(docs) ann.dump() ``` And you can restore the hnsw index from `data_path` if it exists: ```python new_ann = AnnLite(128, metric='cosine', data_path="/path/to/data_path") ``` If you didn't dump the hnsw index, the index will be rebuilt from scratch. This will take a while. ## Supported distance metrics The annlite supports the following distance metrics: #### Supported distances: | Distance | parameter | Equation | |----------------------------------------------------------------------|----------------:|--------------------------------------------------------:| | [Euclidean](https://en.wikipedia.org/wiki/Euclidean_distance) | `euclidean` | d = sqrt(sum((Ai-Bi)^2)) | | [Inner product](https://en.wikipedia.org/wiki/Inner_product_space) | `inner_product` | d = 1.0 - sum(Ai\*Bi) | | [Cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity) | `cosine` | d = 1.0 - sum(Ai\*Bi) / sqrt(sum(Ai\*Ai) * sum(Bi\*Bi)) | Note that inner product is not an actual metric. An element can be closer to some other element than to itself. That allows some speedup if you remove all elements that are not the closest to themselves from the index, e.g., `inner_product([1.0, 1.0], [1.0. 1.0]) < inner_product([1.0, 1.0], [2.0, 2.0])` ## HNSW algorithm parameters The HNSW algorithm has several parameters that can be tuned to improve the search performance. ### Search parameters - `ef_search` - The size of the dynamic list for the nearest neighbors during search (default: `50`). The larger the value, the more accurate the search results, but the slower the search speed. The `ef_search` must be larger than `limit` parameter in `search(..., limit)`. - `limit` - The maximum number of results to return (default: `10`). ## Construction parameters - `max_connection` - The number of bi-directional links created for every new element during construction (default: `16`). Reasonable range is from `2` to `100`. Higher values works better for dataset with higher dimensionality and/or high recall. This parameter also affects the memory consumption during construction, which is roughly `max_connection * 8-10` bytes per stored element. As an example for `n_dim=4` random vectors optimal `max_connection` for search is somewhere around `6`, while for high dimensional datasets, higher `max_connection` are required (e.g. `M=48-64`) for optimal performance at high recall. The range `max_connection=12-48` is ok for the most of the use cases. When `max_connection` is changed one has to update the other parameters. Nonetheless, `ef_search` and `ef_construction` parameters can be roughly estimated by assuming that `max_connection * ef_{construction}` is a constant. - `ef_construction`: The size of the dynamic list for the nearest neighbors during construction (default: `200`). Higher values give better accuracy, but increase construction time and memory consumption. At some point, increasing `ef_construction` does not give any more accuracy. To set `ef_construction` to a reasonable value, one can measure the recall: if the recall is lower than 0.9, then increase `ef_construction` and re-run the search. To set the parameters, you can define them when creating the annlite: ```python from annlite import AnnLite ann = AnnLite(128, columns=[('price', float)], data_path="/tmp/annlite_data", ef_construction=200, max_connection=16) ``` ## Benchmark One can run `executor/benchmark.py` to get a quick performance overview. |Stored data| Indexing time | Query size=1 | Query size=8 | Query size=64| |---|---|---|---|---| |10000 | 2.970 | 0.002 | 0.013 | 0.100| |100000 | 76.474 | 0.011 | 0.078 | 0.649| |500000 | 467.936 | 0.046 | 0.356 | 2.823| |1000000 | 1025.506 | 0.091 | 0.695 | 5.778| Results with filtering can be generated from `examples/benchmark_with_filtering.py`. This script should produce a table similar to: | Stored data |% same filter| Indexing time | Query size=1 | Query size=8 | Query size=64| |-----|-----|-----|-----|-----|-----| | 10000 | 5 | 2.869 | 0.004 | 0.030 | 0.270 | | 10000 | 15 | 2.869 | 0.004 | 0.035 | 0.294 | | 10000 | 20 | 3.506 | 0.005 | 0.038 | 0.287 | | 10000 | 30 | 3.506 | 0.005 | 0.044 | 0.356 | | 10000 | 50 | 3.506 | 0.008 | 0.064 | 0.484 | | 10000 | 80 | 2.869 | 0.013 | 0.098 | 0.910 | | 100000 | 5 | 75.960 | 0.018 | 0.134 | 1.092 | | 100000 | 15 | 75.960 | 0.026 | 0.211 | 1.736 | | 100000 | 20 | 78.475 | 0.034 | 0.265 | 2.097 | | 100000 | 30 | 78.475 | 0.044 | 0.357 | 2.887 | | 100000 | 50 | 78.475 | 0.068 | 0.565 | 4.383 | | 100000 | 80 | 75.960 | 0.111 | 0.878 | 6.815 | | 500000 | 5 | 497.744 | 0.069 | 0.561 | 4.439 | | 500000 | 15 | 497.744 | 0.134 | 1.064 | 8.469 | | 500000 | 20 | 440.108 | 0.152 | 1.199 | 9.472 | | 500000 | 30 | 440.108 | 0.212 | 1.650 | 13.267 | | 500000 | 50 | 440.108 | 0.328 | 2.637 | 21.961 | | 500000 | 80 | 497.744 | 0.580 | 4.602 | 36.986 | | 1000000 | 5 | 1052.388 | 0.131 | 1.031 | 8.212 | | 1000000 | 15 | 1052.388 | 0.263 | 2.191 | 16.643 | | 1000000 | 20 | 980.598 | 0.351 | 2.659 | 21.193 | | 1000000 | 30 | 980.598 | 0.461 | 3.713 | 29.794 | | 1000000 | 50 | 980.598 | 0.732 | 5.975 | 47.356 | | 1000000 | 80 | 1052.388 | 1.151 | 9.255 | 73.552 | Note that: - query times presented are represented in seconds. - `% same filter` indicates the amount of data that verifies a filter in the database. - For example, if `% same filter = 10` and `Stored data = 1_000_000` then it means `100_000` example verify the filter. ## Next steps If you already have experience with Jina and DocArray, you can start using `AnnLite` right away. Otherwise, you can check out this advanced tutorial to learn how to use `AnnLite`: [here]() in practice. ## 🙋 FAQ **1. Why should I use `AnnLite`?** `AnnLite` is easy to use and intuitive to set up in production. It is also very fast and memory efficient, making it a great choice for approximate nearest neighbor search. **2. How do I use `AnnLite` with Jina?** We have implemented an executor for `AnnLite` that can be used with Jina. ```python from jina import Flow with Flow().add(uses='jinahub://AnnLiteIndexer', uses_with={'n_dim': 128}) as f: f.post('/index', inputs=docs) ``` 3. Does `AnnLite` support search with filters? ```text Yes. ``` ## Documentation You can find the documentation on [Github]() and [ReadTheDocs]() ## 🤝 Contribute and spread the word We are also looking for contributors who want to help us improve: code, documentation, issues, feedback! Here is how you can get started: - Have a look through GitHub issues labeled "Good first issue". - Read our Contributor Covenant Code of Conduct - Open an issue or submit your pull request! ## License `AnnLite` is licensed under the [Apache License 2.0](). ================================================ FILE: annlite/__init__.py ================================================ __version__ = '0.5.11' from .index import AnnLite ================================================ FILE: annlite/container.py ================================================ import warnings from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Optional, Tuple import numpy as np from docarray import Document, DocumentArray from loguru import logger if TYPE_CHECKING: from .core.codec.pq import PQCodec from .core.codec.projector import ProjectorCodec from .core.index.hnsw import HnswIndex from .enums import Metric from .storage.base import ExpandMode from .storage.kv import DocStorage from .storage.table import CellTable, MetaTable VALID_FILTERABLE_DATA_TYPES = [int, str, float] class CellContainer: def __init__( self, n_dim: int, metric: Metric = Metric.COSINE, n_cells: int = 1, projector_codec: Optional['ProjectorCodec'] = None, pq_codec: Optional['PQCodec'] = None, initial_size: Optional[int] = None, expand_step_size: int = 50000, expand_mode: 'ExpandMode' = ExpandMode.STEP, filterable_attrs: Optional[Dict] = None, serialize_config: Optional[Dict] = None, data_path: 'Path' = Path('./data'), **kwargs, ): self.n_dim = n_dim self.metric = metric self.n_cells = n_cells self.n_components = projector_codec.n_components if projector_codec else None self.data_path = data_path self.serialize_config = serialize_config self._pq_codec = pq_codec self._projector_codec = projector_codec self._vec_indexes = [ HnswIndex( dim=self.n_components or n_dim, metric=metric, initial_size=initial_size, expand_step_size=expand_step_size, expand_mode=expand_mode, pq_codec=pq_codec, **kwargs, ) for _ in range(n_cells) ] self._doc_stores = [ DocStorage( data_path / f'cell_{_}', serialize_config=serialize_config or {}, lock=True, ) for _ in range(n_cells) ] columns = [] if filterable_attrs: for attr_name, attr_type in filterable_attrs.items(): if isinstance(attr_type, str): attr_type = eval(attr_type) if attr_type not in VALID_FILTERABLE_DATA_TYPES: raise ValueError( f'Invalid filterable attribute type `{attr_type}` for attribute `{attr_name}`. ' ) columns.append((attr_name, attr_type)) self._cell_tables = [ CellTable(f'table_{c}', columns=columns) for c in range(n_cells) ] self._meta_table = MetaTable('metas', data_path=data_path, in_memory=True) def ivf_search( self, x: 'np.ndarray', cells: 'np.ndarray', where_clause: str = '', where_params: Tuple = (), limit: int = 10, ): dists = [] doc_idx = [] cell_ids = [] count = 0 for cell_id in cells: cell_table = self.cell_table(cell_id) cell_size = cell_table.count() if cell_size == 0: continue indices = None if where_clause: indices = cell_table.query( where_clause=where_clause, where_params=where_params ) if len(indices) == 0: continue indices = np.array(indices, dtype=np.int64) _dists, _doc_idx = self.vec_index(cell_id).search( x, limit=min(limit, cell_size), indices=indices ) if count >= limit and _dists[0] > dists[-1][-1]: continue dists.append(_dists) doc_idx.append(_doc_idx) cell_ids.extend([cell_id] * len(_dists)) count += len(_dists) cell_ids = np.array(cell_ids, dtype=np.int64) if len(dists) != 0: dists = np.hstack(dists) doc_idx = np.hstack(doc_idx) indices = dists.argsort(axis=0)[:limit] dists = dists[indices] cell_ids = cell_ids[indices] doc_idx = doc_idx[indices] doc_ids = [] for cell_id, offset in zip(cell_ids, doc_idx): doc_id = self.cell_table(cell_id).get_docid_by_offset(offset) doc_ids.append(doc_id) return dists, doc_ids, cell_ids def filter_cells( self, cells: 'np.ndarray', where_clause: str = '', where_params: Tuple = (), limit: int = -1, offset: int = 0, order_by: Optional[str] = None, ascending: bool = True, include_metadata: bool = False, ): result = DocumentArray() if len(cells) > 1 and offset > 0: raise ValueError('Offset is not supported for multiple cells') for cell_id in cells: cell_table = self.cell_table(cell_id) cell_size = cell_table.count() if cell_size == 0: continue indices = cell_table.query( where_clause=where_clause, where_params=where_params, order_by=order_by, limit=limit, offset=offset, ascending=ascending, ) if len(indices) == 0: continue for offset in indices: doc_id = self.cell_table(cell_id).get_docid_by_offset(offset) doc = Document(id=doc_id) if include_metadata or (len(cells) > 1 and order_by): doc = self.doc_store(cell_id).get([doc_id])[0] result.append(doc) if not order_by and len(result) >= limit > 0: break # reordering the results from multiple cells if order_by and len(cells) > 1: result = sorted( result, key=lambda d: d.tags.get(order_by), reverse=not ascending ) if limit > 0: result = result[:limit] result = DocumentArray(result) return result def search_cells( self, query: 'np.ndarray', cells: 'np.ndarray', where_clause: str = '', where_params: Tuple = (), limit: int = 10, include_metadata: bool = False, ): if self._projector_codec: query = self._projector_codec.encode(query) topk_dists, topk_docs = [], [] for x, cell_idx in zip(query, cells): # x.shape = (self.n_dim,) dists, doc_ids, cells = self.ivf_search( x, cells=cell_idx, where_clause=where_clause, where_params=where_params, limit=limit, ) topk_dists.append(dists) match_docs = DocumentArray() for dist, doc_id, cell_id in zip(dists, doc_ids, cells): doc = Document(id=doc_id) if include_metadata: doc = self.doc_store(cell_id).get([doc_id])[0] doc.scores[self.metric.name.lower()].value = dist match_docs.append(doc) topk_docs.append(match_docs) return topk_dists, topk_docs def _search_cells( self, query: 'np.ndarray', cells: 'np.ndarray', where_clause: str = '', where_params: Tuple = (), limit: int = 10, ): if self._projector_codec: query = self._projector_codec.encode(query) topk_dists, topk_ids = [], [] for x, cell_idx in zip(query, cells): dists, ids, cells = self.ivf_search( x, cells=cell_idx, where_clause=where_clause, where_params=where_params, limit=limit, ) topk_dists.append(dists) topk_ids.append(ids) return topk_dists, [np.array(ids, dtype=int) for ids in topk_ids] def insert( self, data: 'np.ndarray', cells: 'np.ndarray', docs: 'DocumentArray', only_index: bool = False, ): assert len(docs) == len(data) if self._projector_codec: data = self._projector_codec.encode(data) unique_cells, unique_cell_counts = np.unique(cells, return_counts=True) if len(unique_cells) == 1: cell_id = unique_cells[0] offsets = self.cell_table(cell_id).insert(docs) offsets = np.array(offsets, dtype=np.int64) self.vec_index(cell_id).add_with_ids(data, offsets) self._meta_table.bulk_add_address([d.id for d in docs], cells, offsets) if not only_index: self.doc_store(cell_id).insert(docs) else: for cell_id, cell_count in zip(unique_cells, unique_cell_counts): # TODO: Jina should allow boolean filtering in docarray to avoid this # and simply use cells == cell_index indices = np.where(cells == cell_id)[0] cell_docs = docs[indices.tolist()] cell_offsets = self.cell_table(cell_id).insert(cell_docs) cell_offsets = np.array(cell_offsets, dtype=np.int64) cell_data = data[indices, :] self.vec_index(cell_id).add_with_ids(cell_data, cell_offsets) self._meta_table.bulk_add_address( [d.id for d in cell_docs], [cell_id] * cell_count, cell_offsets ) if not only_index: self.doc_store(cell_id).insert(cell_docs) logger.debug(f'{len(docs)} new docs added') def _add_vecs(self, data: 'np.ndarray', cells: 'np.ndarray', offsets: 'np.ndarray'): assert data.shape[0] == cells.shape[0] assert data.shape[1] == self.n_dim unique_cells, _ = np.unique(cells, return_counts=True) for cell_id in unique_cells: indices = cells == cell_id x = data[indices, :] ids = offsets[indices] self.vec_index(cell_id).add_with_ids(x, ids) def update( self, data: 'np.ndarray', cells: 'np.ndarray', docs: 'DocumentArray', insert_if_not_found: bool = True, raise_errors_on_not_found: bool = False, ): update_success = 0 new_data = [] new_cells = [] new_docs = [] for ( x, doc, cell_id, ) in zip(data, docs, cells): _cell_id, _offset = self._meta_table.get_address(doc.id) if cell_id == _cell_id: self.vec_index(cell_id).add_with_ids(x.reshape(1, -1), [_offset]) self.doc_store(cell_id).update([doc]) self.meta_table.add_address(doc.id, cell_id, _offset) update_success += 1 elif _cell_id is None: if raise_errors_on_not_found and not insert_if_not_found: raise Exception( f'The document (id={doc.id}) cannot be updated as' f'it is not found in the index' ) elif not (raise_errors_on_not_found or insert_if_not_found): warnings.warn( f'The document (id={doc.id}) cannot be updated as ' f'it is not found in the index', RuntimeWarning, ) elif insert_if_not_found: new_data.append(x) new_cells.append(cell_id) new_docs.append(doc) update_success += 1 else: continue else: # DELETE and INSERT self.vec_index(_cell_id).delete(_offset) self.cell_table(_cell_id).delete_by_offset(_offset) self.doc_store(_cell_id).delete([doc.id]) new_data.append(x) new_cells.append(cell_id) new_docs.append(doc) update_success += 1 if len(new_data) > 0: new_data = np.stack(new_data) new_cells = np.array(new_cells, dtype=np.int64) self.insert(new_data, new_cells, new_docs) logger.debug( f'total items for updating: {len(docs)}, ' f'success: {update_success}' ) def delete( self, ids: List[str], raise_errors_on_not_found: bool = False, ): delete_success = 0 for doc_id in ids: cell_id, offset = self._meta_table.get_address(doc_id) if cell_id is not None: self.vec_index(cell_id).delete([offset]) self.cell_table(cell_id).delete_by_offset(offset) self.doc_store(cell_id).delete([doc_id]) self.meta_table.delete_address(doc_id) delete_success += 1 else: if raise_errors_on_not_found: raise Exception( f'The document (id={doc_id}) cannot be updated as' f'it is not found in the index' ) else: continue logger.debug( f'total items for updating: {len(ids)}, ' f'success: {delete_success}' ) def _rebuild_database(self): """rebuild doc_store and meta_table after annlite download databse from hubble""" self._doc_stores = [ DocStorage( self.data_path / f'cell_{_}', serialize_config=self.serialize_config or {}, lock=True, ) for _ in range(self.n_cells) ] # self._meta_table = MetaTable('metas', data_path=self.data_path, in_memory=False) def _get_doc_by_id(self, doc_id: str): cell_id = 0 if self.n_cells > 1: cell_id, _ = self._meta_table.get_address(doc_id) da = self.doc_store(cell_id).get([doc_id]) return da[0] if len(da) > 0 else None def documents_generator(self, cell_id: int, batch_size: int = 1000): for docs in self.doc_store(cell_id).batched_iterator(batch_size=batch_size): yield docs @property def cell_tables(self): return self._cell_tables @property def cell_indexes(self): return self._vec_indexes def cell_table(self, cell_id: int): return self._cell_tables[cell_id] def doc_store(self, cell_id: int): return self._doc_stores[cell_id] def vec_index(self, cell_id: int): return self._vec_indexes[cell_id] @property def meta_table(self): return self._meta_table @property def total_docs(self): return sum([store.size for store in self._doc_stores]) @property def index_size(self): return sum([table.size for table in self._cell_tables]) ================================================ FILE: annlite/core/__init__.py ================================================ from .codec import PQCodec, ProjectorCodec, VQCodec ================================================ FILE: annlite/core/codec/__init__.py ================================================ from .pq import PQCodec from .projector import ProjectorCodec from .vq import VQCodec ================================================ FILE: annlite/core/codec/base.py ================================================ import pickle from abc import ABC, abstractmethod from typing import TYPE_CHECKING if TYPE_CHECKING: from pathlib import Path class BaseCodec(ABC): def __init__(self, require_train: bool = True): self.require_train = require_train self._is_trained = False if require_train else True @abstractmethod def fit(self, *args, **kwargs): pass @abstractmethod def encode(self): pass @abstractmethod def decode(self): pass def dump(self, target_path: 'Path'): pickle.dump(self, target_path.open('wb'), protocol=4) @staticmethod def load(from_path: 'Path'): return pickle.load(from_path.open('rb')) @property def is_trained(self): return self._is_trained def _check_trained(self): assert self.is_trained is True, f'{self.__class__.__name__} requires training' ================================================ FILE: annlite/core/codec/pq.py ================================================ from argparse import ArgumentError import numpy as np from scipy.cluster.vq import vq from annlite import pq_bind from ...enums import Metric from ...math import l2_normalize from ...profile import time_profile from .base import BaseCodec # from pqlite.pq_bind import precompute_adc_table, dist_pqcodes_to_codebooks class PQCodec(BaseCodec): """Implementation of Product Quantization (PQ) [Jegou11]_. For the indexing phase of database vectors, a `D`-dim input vector is divided into `M` `D`/`M`-dim sub-vectors. Each sub-vector is quantized into a small integer via `Ks` codewords. For the querying phase, given a new `D`-dim query vector, the distance between the query and the database PQ-codes are efficiently approximated via Asymmetric Distance. All vectors must be np.ndarray with np.float32 .. [Jegou11] H. Jegou et al., "Product Quantization for Nearest Neighbor Search", IEEE TPAMI 2011 :param d_vector: the dimensionality of input vectors :param n_subvectors: The number of sub-space :param n_clusters: The number of codewords for each subspace (typically 256, so that each sub-vector is quantized into 256 bits pqlite.utils.asymmetric_distance= 1 byte = uint8) :param n_init: Number of times K-Means is trained with different centroid seeds. Best result of the `n_init` consecutive runs is selected. """ def __init__( self, dim: int, n_subvectors: int = 8, n_clusters: int = 256, metric: Metric = Metric.EUCLIDEAN, n_init: int = 4, ): super(PQCodec, self).__init__(require_train=True) self.dim = dim self.n_subvectors = n_subvectors self.n_clusters = n_clusters assert ( dim % n_subvectors == 0 ), 'input dimension must be dividable by number of sub-space' self.d_subvector = dim // n_subvectors self.code_dtype = ( np.uint8 if n_clusters <= 2**8 else (np.uint16 if n_clusters <= 2**16 else np.uint32) ) # assert ( # metric == Metric.EUCLIDEAN # ), f'The distance metric `{metric.name}` is not supported yet!' self.metric = metric self.normalize_input = False if self.metric == Metric.COSINE: self.normalize_input = True self._codebooks = np.zeros( (self.n_subvectors, self.n_clusters, self.d_subvector), dtype=np.float32 ) self.kmeans = [] self.n_init = n_init def __hash__(self): return hash( ( self.__class__.__name__, self.dim, self.n_subvectors, self.n_clusters, self.metric, self.code_dtype, ) ) def fit(self, x: 'np.ndarray', iter: int = 100): """Train the K-Means for each cartesian product :param x: Training vectors with shape=(N, D) :param iter: Number of iterations in Kmeans """ from sklearn.cluster import KMeans assert x.dtype == np.float32 assert x.ndim == 2 if self.normalize_input: x = l2_normalize(x) # [m][ks][ds]: m-th subspace, ks-the codeword, ds-th dim self._codebooks = np.zeros( (self.n_subvectors, self.n_clusters, self.d_subvector), dtype=np.float32 ) for m in range(self.n_subvectors): kmeans = KMeans( n_clusters=self.n_clusters, max_iter=iter, n_init=self.n_init ) self.kmeans.append(kmeans) self.kmeans[m].fit(x[:, m * self.d_subvector : (m + 1) * self.d_subvector]) self._codebooks[m] = self.kmeans[m].cluster_centers_ self._is_trained = True def partial_fit(self, x: 'np.ndarray'): """Given a batch of training vectors, update the internal MiniBatchKMeans. This method is specially designed to be used when data does not fit in memory. :param x: Training vectors with shape=(N, D) """ assert x.ndim == 2 if self.normalize_input: x = l2_normalize(x) if len(self.kmeans) > 0: for m in range(self.n_subvectors): self.kmeans[m].partial_fit( x[:, m * self.d_subvector : (m + 1) * self.d_subvector] ) else: from sklearn.cluster import MiniBatchKMeans for m in range(self.n_subvectors): self.kmeans.append(MiniBatchKMeans(n_clusters=self.n_clusters)) for m in range(self.n_subvectors): self.kmeans[m].partial_fit( x[:, m * self.d_subvector : (m + 1) * self.d_subvector] ) def build_codebook(self): """Constructs sub-codebooks from the current parameters of the models in `self.kmeans` This step is not necessary if full KMeans is trained used calling `.fit`. """ self._codebooks = np.zeros( (self.n_subvectors, self.n_clusters, self.d_subvector), dtype=np.float32 ) for m in range(self.n_subvectors): self._codebooks[m] = self.kmeans[m].cluster_centers_ self._is_trained = True def encode(self, x: 'np.ndarray'): """Encode input vectors into PQ-codes. :param x: Input vectors with shape=(N, D) and dtype=np.float32. :return: np.ndarray: PQ codes with shape=(N, M) and dtype=self.code_dtype """ assert x.dtype == np.float32 assert x.ndim == 2 N, D = x.shape assert ( D == self.d_subvector * self.n_subvectors ), 'input dimension must be Ds * M' # codes[n][m] : code of n-th vec, m-th subspace codes = np.empty((N, self.n_subvectors), dtype=self.code_dtype) for m in range(self.n_subvectors): sub_vecs = x[:, m * self.d_subvector : (m + 1) * self.d_subvector] codes[:, m], _ = vq(sub_vecs, self.codebooks[m]) return codes def decode(self, codes: 'np.ndarray'): """Given PQ-codes, reconstruct original D-dimensional vectors approximately by fetching the codewords. :param codes: PQ-cdoes with shape=(N, M) and dtype=self.code_dtype. Each row is a PQ-code :return: Reconstructed vectors with shape=(N, D) and dtype=np.float32 """ assert codes.ndim == 2 N, M = codes.shape assert M == self.n_subvectors assert codes.dtype == self.code_dtype vecs = np.empty((N, self.d_subvector * self.n_subvectors), dtype=np.float32) for m in range(self.n_subvectors): vecs[:, m * self.d_subvector : (m + 1) * self.d_subvector] = self.codebooks[ m ][codes[:, m], :] return vecs def precompute_adc(self, query: object) -> object: """Compute a distance table for a query vector. The distances are computed by comparing each sub-vector of the query to the codewords for each sub-subspace. `dtable[m][ks]` contains the squared Euclidean distance between the `m`-th sub-vector of the query and the `ks`-th codeword for the `m`-th sub-space (`self.codewords[m][ks]`). :param query: Input vector with shape=(D, ) and dtype=np.float32 :return: Distance table. which contains dtable with shape=(M, Ks) and dtype=np.float32 """ assert query.dtype == np.float32 assert query.ndim == 1, 'input must be a single vector' # dtable[m] : distance between m-th subvec and m-th codewords (m-th subspace) # dtable[m][ks] : distance between m-th subvec and ks-th codeword of m-th codewords # Warning: the following line produces `ValueError: buffer source array is read-only` # if no `const` is used in the cython implementation using a memoryview dtable = pq_bind.precompute_adc_table( query, self.d_subvector, self.n_clusters, self.codebooks ) return DistanceTable(dtable) @property def codebooks(self): return self._codebooks # trained pq interface ---------------- def get_codebook(self) -> 'np.ndarray': """Return the codebook parameters. Expect a 3-dimensional matrix is returned, with shape (`n_subvectors`, `n_clusters`, `d_subvector`) and dtype float32 """ return np.ascontiguousarray(self.codebooks, dtype='float32') def get_subspace_splitting(self): """Return subspace splitting setting :return: tuple of (`n_subvectors`, `n_clusters`, `d_subvector`) """ return (self.n_subvectors, self.n_clusters, self.d_subvector) # def get_dist_mat(self, x: np.ndarray): # """Return the distance tables in form of matrix for multiple queries # :param query: shape('N', 'D'), # :return: ndarray with shape('N', `n_subvectors`, `n_clusters`) # .. note:: # _description_ # """ # assert x.dtype == np.float32 # assert x.ndim == 2 # N, D = x.shape # assert ( # D == self.d_subvector * self.n_subvectors # ), 'input dimension must be Ds * M' # if self.normalize_input: # x = l2_normalize(x) # x = x.reshape( # N, # self.n_subvectors, # 1, # self.d_subvector, # ) # if self.metric == Metric.EUCLIDEAN: # # (1, n_subvectors, n_clusters, d_subvector) # codebook = self.codebooks[np.newaxis, ...] # # broadcast to (N, n_subvectors, n_clusters, d_subvector) # dist_vector = (x - codebook) ** 2 # # reduce to (N, n_subvectors, n_clusters) # dist_mat = np.sum(dist_vector, axis=3) # elif self.metric in [Metric.INNER_PRODUCT, Metric.COSINE]: # # (1, n_subvectors, n_clusters, d_subvector) # codebook = self.codebooks[np.newaxis, ...] # # broadcast to (N, n_subvectors, n_clusters, d_subvector) # dist_vector = x * codebook # # reduce to (N, n_subvectors, n_clusters) # dist_mat = 1 / self.n_clusters - np.sum(dist_vector, axis=3) # else: # raise ArgumentError(f'Unable support metrics {self.metric}') # return np.ascontiguousarray(dist_mat, dtype='float32') def get_dist_mat(self, x: np.ndarray): """Return the distance tables in form of matrix for multiple queries :param query: shape('N', 'D'), :return: ndarray with shape('N', `n_subvectors`, `n_clusters`) .. note:: _description_ """ assert x.dtype == np.float32 assert x.ndim == 2 N, D = x.shape assert ( D == self.d_subvector * self.n_subvectors ), 'input dimension must be Ds * M' if self.normalize_input: x = l2_normalize(x) if self.metric == Metric.EUCLIDEAN: dist_mat = pq_bind.batch_precompute_adc_table( x, self.d_subvector, self.n_clusters, self.codebooks ) elif self.metric in [Metric.INNER_PRODUCT, Metric.COSINE]: dist_mat = 1 / self.n_clusters - np.array( pq_bind.batch_precompute_adc_table_ip( x, self.d_subvector, self.n_clusters, self.codebooks ), dtype='float32', ) else: raise ArgumentError(f'Unable support metrics {self.metric}') return np.ascontiguousarray(dist_mat, dtype='float32') # ------------------------------------- class DistanceTable(object): """Distance table from query to codeworkds. Given a query vector, a PQ/OPQ instance compute this DistanceTable class using :func:`PQ.dtable` or :func:`OPQ.dtable`. The Asymmetric Distance from query to each database codes can be computed by :func:`DistanceTable.adist`. Args: dtable (np.ndarray): Distance table with shape=(M, Ks) and dtype=np.float32 computed by :func:`PQ.dtable` or :func:`OPQ.dtable` Attributes: dtable (np.ndarray): Distance table with shape=(M, Ks) and dtype=np.float32. Note that dtable[m][ks] contains the squared Euclidean distance between (1) m-th sub-vector of query and (2) ks-th codeword for m-th subspace. """ def __init__(self, dtable: 'np.ndarray'): assert dtable.ndim == 2 self.dtable = dtable def adist(self, codes): """Given PQ-codes, compute Asymmetric Distances between the query (self.dtable) and the PQ-codes. Args: codes (np.ndarray): PQ codes with shape=(N, M) and dtype=pq.code_dtype where pq is a pq instance that creates the codes Returns: np.ndarray: Asymmetric Distances with shape=(N, ) and dtype=np.float32 """ assert codes.ndim == 2 dists = pq_bind.dist_pqcodes_to_codebooks(self.dtable, codes) # The above line is equivalent to the followings: # dists = np.zeros((N, )).astype(np.float32) # for n in range(N): # for m in range(M): # dists[n] += self.dtable[m][codes[n][m]] return dists ================================================ FILE: annlite/core/codec/projector.py ================================================ from typing import Optional import numpy as np from .base import BaseCodec class ProjectorCodec(BaseCodec): """Implementation of Projector. :param n_components: number of components to keep. :param whiten: when True (False by default) the components_ vectors are multiplied by the square root of n_samples and then divided by the singular values to ensure uncorrelated outputs with unit component-wise variances. :param svd_solver: If auto: The solver is selected by a default policy based on X.shape and n_components: if the input data is larger than 500x500 and the number of components to extract is lower than 80% of the smallest dimension of the data, then the more efficient ‘randomized’ method is enabled. Otherwise the exact full SVD is computed and optionally truncated afterwards. If full: run exact full SVD calling the standard LAPACK solver via scipy. linalg.svd and select the components by postprocessing. If arpack: run SVD truncated to n_components calling ARPACK solver via scipy.sparse.linalg.svds. It requires strictly 0 < n_components < min(X.shape). """ def __init__( self, dim: int, n_components: int = 128, whiten: Optional[bool] = False, svd_solver: Optional[str] = 'auto', ): super(ProjectorCodec, self).__init__(require_train=True) self.dim = dim self.n_components = n_components assert self.dim >= self.n_components, ( f'the dimension after projector should be less than original dimension, got ' f'original dimension: {self.dim} and projector dimension: {self.n_components}' ) self.whiten = whiten self.svd_solver = svd_solver self.pca = None def __hash__(self): return hash( ( self.__class__.__name__, self.dim, self.n_components, self.whiten, self.svd_solver, ) ) def fit(self, x: 'np.ndarray'): """Train projector model :param x: Training vectors with shape=(N, D) """ assert x.ndim == 2 assert ( x.shape[1] == self.dim, ), 'dimension of input data must be equal to "dim"' assert ( x.shape[0] > self.n_components ), 'number of input data must be larger than or equal to n_components' if self.pca is None: from sklearn.decomposition import PCA self.pca = PCA( n_components=self.n_components, whiten=self.whiten, svd_solver=self.svd_solver, ) self.pca.fit(x) self._is_trained = True def partial_fit(self, x: 'np.ndarray'): """Given a batch of training vectors, update the internal projector. This method is specially designed to be used when data does not fit in memory. :param x: Training vectors with shape=(N, D) """ assert x.ndim == 2 assert x.shape[1] == self.dim, 'dimension of input data must be equal to "dim"' assert ( x.shape[0] > self.n_components ), 'number of input data must be larger than or equal to n_components' if self.pca is None: from sklearn.decomposition import IncrementalPCA self.pca = IncrementalPCA( n_components=self.n_components, whiten=self.whiten, ) self.pca.partial_fit(x) self._is_trained = True def encode(self, x: 'np.ndarray'): """Encode input vectors using projector. :param x: Input vectors with shape=(N, D) :return: np.ndarray: transformed vectors using projector. """ assert x.ndim == 2 assert x.shape[1] == self.dim, 'dimension of input data must be equal to "dim"' return self.pca.transform(x) def decode(self, x: 'np.ndarray'): """Given transformed vectors, reconstruct original D-dimensional vectors approximately. :param x: vectors with shape=(N, self.n_components). :return: Reconstructed vectors with shape=(N, D) """ assert x.ndim == 2 assert x.shape[1] == self.n_components return self.pca.inverse_transform(x) @property def components(self): """Principal axes in feature space, representing the directions of maximum variance in the data. """ self._check_trained() return self.pca.components_ @property def explained_variance_ratio(self): """Percentage of variance explained by each of the selected components.""" self._check_trained() return self.pca.explained_variance_ratio_ @property def mean(self): """Per-feature empirical mean.""" self._check_trained() return self.pca.mean_ @property def var(self): """Per-feature empirical variance""" self._check_trained() return self.pca.var_ ================================================ FILE: annlite/core/codec/vq.py ================================================ import numpy as np from scipy.cluster.vq import vq from ...enums import Metric from .base import BaseCodec class VQCodec(BaseCodec): def __init__( self, n_clusters: int, metric: Metric = Metric.EUCLIDEAN, iter: int = 100, n_init: int = 4, *args, **kwargs ): super(VQCodec, self).__init__(require_train=True) self.n_clusters = n_clusters # assert ( # metric == Metric.EUCLIDEAN # ), f'The distance metric `{metric.name}` is not supported yet!' self.metric = metric self._codebook = None self.iter = iter self.kmeans = None self.n_init = n_init def __hash__(self): return hash((self.__class__.__name__, self.n_clusters, self.metric)) def fit(self, x: 'np.ndarray'): """Given training vectors, run k-means for each sub-space and create codewords for each sub-space. :param x: Training vectors with shape=(N, D) and dtype=np.float32. :param iter: The number of iteration for k-means """ from sklearn.cluster import KMeans assert x.dtype == np.float32 assert x.ndim == 2 self.kmeans = KMeans(self.n_clusters, max_iter=self.iter, n_init=self.n_init) self.kmeans.fit(x) self._codebook = self.kmeans.cluster_centers_ self._is_trained = True def partial_fit(self, x: 'np.ndarray'): """Given a batch of training vectors, update the internal MiniBatchKMeans. This method is specially designed to be used when data does not fit in memory. :param x: Training vectors with shape=(N, D) """ assert x.ndim == 2 if self.kmeans: self.kmeans.partial_fit(x) else: from sklearn.cluster import MiniBatchKMeans self.kmeans = MiniBatchKMeans( n_clusters=self.n_clusters, max_iter=self.iter ) self.kmeans.partial_fit(x) def build_codebook(self): """Constructs a codebook from the current MiniBatchKmeans This step is not necessary if full KMeans is trained used calling `.fit`. """ self._codebook = self.kmeans.cluster_centers_ self._is_trained = True def encode(self, x: 'np.ndarray'): """Encodes each row of the input array `x` it's closest cluster id.""" self._check_trained() assert x.dtype == np.float32 assert x.ndim == 2 codes, _ = vq(x, self.codebook) return codes def decode(self, x: 'np.ndarray'): return None @property def codebook(self): self._check_trained() return self._codebook ================================================ FILE: annlite/core/index/__init__.py ================================================ ================================================ FILE: annlite/core/index/base.py ================================================ import abc from typing import List, Optional, Union import numpy as np from ...enums import ExpandMode, Metric from ...helper import str2dtype class BaseIndex(abc.ABC): def __init__( self, dim: int, dtype: Union[np.dtype, str] = np.float32, metric: Metric = Metric.COSINE, initial_size: Optional[int] = None, expand_step_size: int = 10240, expand_mode: ExpandMode = ExpandMode.STEP, *args, **kwargs ): assert expand_step_size > 0 self.initial_size = initial_size or expand_step_size self.expand_step_size = expand_step_size self.expand_mode = expand_mode self.dim = dim self.dtype = str2dtype(dtype) if isinstance(dtype, str) else dtype self.metric = metric self._size = 0 self._capacity = self.initial_size @property def capacity(self) -> int: return self._capacity @property def size(self): return self._size @abc.abstractmethod def add_with_ids(self, x: np.ndarray, ids: List[int], **kwargs): ... @abc.abstractmethod def delete(self, ids: List[int]): ... @abc.abstractmethod def update_with_ids(self, x: np.ndarray, ids: List[int], **kwargs): ... def reset(self, capacity: Optional[int] = None): self._size = 0 self._capacity = capacity or self.initial_size ================================================ FILE: annlite/core/index/flat_index.py ================================================ from typing import List, Optional import numpy as np from loguru import logger from ...math import cdist, top_k from .base import BaseIndex class FlatIndex(BaseIndex): def __init__(self, *args, **kwargs): super(FlatIndex, self).__init__(*args, **kwargs) self._data = np.zeros((self.initial_size, self.dim), dtype=self.dtype) def search( self, x: np.ndarray, limit: int = 10, indices: Optional[np.ndarray] = None ): _dim = x.shape[-1] assert ( _dim == self.dim ), f'the query embedding dimension does not match with index dimension: {_dim} vs {self.dim}' x = x.reshape((-1, self.dim)) data = self._data[: self.size] data_ids = np.arange(self.size) if indices is not None: data = self._data[indices] data_ids = data_ids[indices] dists = cdist(x, data, metric=self.metric.name.lower()) dists, idx = top_k(dists, limit, descending=False) # TODO: change the shape of return dists = dists[0] data_ids = data_ids[idx[0]] return dists, data_ids def add_with_ids(self, x: np.ndarray, ids: List[int]): for idx in ids: if idx >= self._capacity: self._expand_capacity() start = self._size end = start + len(x) self._data[ids, :] = x self._size = end def _expand_capacity(self): new_block = np.zeros((self.expand_step_size, self.dim), dtype=self.dtype) self._data = np.concatenate((self._data, new_block), axis=0) self._capacity += self.expand_step_size logger.debug( f'total storage capacity is expanded by {self.expand_step_size}', ) def reset(self, capacity: Optional[int] = None): super().reset(capacity=capacity) self._data = np.zeros((self.capacity, self.dim), dtype=self.dtype) def delete(self, ids: List[int]): raise RuntimeError( f'the deletion operation is not allowed for {self.__class__.__name__}!' ) def update_with_ids(self, x: np.ndarray, ids: List[int], **kwargs): self._data[ids, :] = x ================================================ FILE: annlite/core/index/hnsw/__init__.py ================================================ from .index import HnswIndex ================================================ FILE: annlite/core/index/hnsw/index.py ================================================ import math import os.path from functools import wraps from pathlib import Path from typing import TYPE_CHECKING, List, Optional, Union import numpy as np from loguru import logger from annlite.hnsw_bind import Index from ....enums import Metric from ....math import l2_normalize from ..base import BaseIndex if TYPE_CHECKING: from ...codec.base import BaseCodec def pre_process(f): @wraps(f) def pre_processed(self: 'HnswIndex', x: np.ndarray, *args, **kwargs): if x.ndim == 1: x = x.reshape((1, -1)) if x.dtype != self.dtype: x = x.astype(self.dtype) if self.normalization_enable: x = l2_normalize(x) if self.pq_enable: if not self.pq_codec.is_trained: raise RuntimeError( 'Please train the PQ before using HNSW quantization backend' ) elif not self._set_backend_pq: self._index.loadPQ(self.pq_codec) self._set_backend_pq = True kwargs['pre_process_dtables'] = self.pq_codec.get_dist_mat(x) x = self.pq_codec.encode(x) assert kwargs['pre_process_dtables'].dtype == 'float32' assert kwargs['pre_process_dtables'].flags['C_CONTIGUOUS'] return f(self, x, *args, **kwargs) else: return f(self, x, *args, **kwargs) return pre_processed class HnswIndex(BaseIndex): def __init__( self, dim: int, dtype: np.dtype = np.float32, metric: Metric = Metric.COSINE, ef_construction: int = 200, ef_search: int = 50, max_connection: int = 16, pq_codec: Optional['BaseCodec'] = None, index_file: Optional[Union[str, Path]] = None, **kwargs, ): """ :param dim: The dimensionality of vectors to index :param index_file: A file-like object or a string containing a file name. :param metric: Distance metric type, can be 'euclidean', 'inner_product', or 'cosine' :param ef_construction: the size of the dynamic list for the nearest neighbors (used during the building). :param ef_search: the size of the dynamic list for the nearest neighbors (used during the search). :param max_connection: The number of bi-directional links created for every new element during construction. Reasonable range for M is 2-100. """ super().__init__(dim, dtype=dtype, metric=metric, **kwargs) self.ef_construction = ef_construction self.ef_search = ef_search self.max_connection = max_connection self.pq_codec = pq_codec self._set_backend_pq = False self.index_file = index_file self._init_hnsw_index() def _init_hnsw_index(self): self._index = Index(space=self.space_name, dim=self.dim) if self.index_file: if os.path.exists(self.index_file): logger.info( f'indexer will be loaded from {self.index_file}', ) self.load(self.index_file) else: raise FileNotFoundError( f'index path: {self.index_file} does not exist', ) else: if self.pq_codec is not None and self.pq_codec.is_trained: self._index.init_index( max_elements=self.capacity, ef_construction=self.ef_construction, M=self.max_connection, pq_codec=self.pq_codec, ) self._set_backend_pq = True else: self._index.init_index( max_elements=self.capacity, ef_construction=self.ef_construction, M=self.max_connection, pq_codec=None, ) self._set_backend_pq = False self._index.set_ef(self.ef_search) def load(self, index_file: Union[str, Path]): self._index.load_index(str(index_file)) if self.pq_codec: self._index.loadPQ(self.pq_codec) def dump(self, index_file: Union[str, Path]): self._index.save_index(str(index_file)) @pre_process def add_with_ids( self, x: 'np.ndarray', ids: List[int], # kwargs maybe used by pre_process pre_process_dtables=None, ): max_id = max(ids) + 1 if max_id > self.capacity: expand_steps = math.ceil(max_id / self.expand_step_size) self._expand_capacity(expand_steps * self.expand_step_size) self._index.add_items(x, ids=ids, dtables=pre_process_dtables) @pre_process def search( self, query: 'np.ndarray', limit: int = 10, indices: Optional['np.ndarray'] = None, # kwargs maybe used by pre_process pre_process_dtables=None, ): ef_search = max(self.ef_search, limit) self._index.set_ef(ef_search) if indices is not None: # TODO: add a smart strategy to speed up this case (bruteforce search would be better) if len(indices) < limit: limit = len(indices) ids, dists = self._index.knn_query_with_filter( query, filters=indices, k=limit, dtables=pre_process_dtables ) else: ids, dists = self._index.knn_query( query, k=limit, dtables=pre_process_dtables ) # convert squared l2 into euclidean distance if self.metric == Metric.EUCLIDEAN: dists = np.sqrt(dists) return dists[0], ids[0] def delete(self, ids: List[int]): for i in ids: self._index.mark_deleted(i) def update_with_ids(self, x: 'np.ndarray', ids: List[int], **kwargs): raise RuntimeError( f'the update operation is not allowed for {self.__class__.__name__}!' ) def _expand_capacity(self, new_capacity: int): self._capacity = new_capacity self._index.resize_index(new_capacity) logger.debug( f'HNSW index capacity is expanded by {self.expand_step_size}', ) def reset(self, capacity: Optional[int] = None): super().reset(capacity=capacity) self._init_hnsw_index() @property def size(self): return self._index.element_count @property def space_name(self): if self.metric == Metric.EUCLIDEAN: return 'l2' elif self.metric == Metric.INNER_PRODUCT: return 'ip' return 'cosine' @property def pq_enable(self): return self.pq_codec is not None @property def normalization_enable(self): return self.metric == Metric.COSINE ================================================ FILE: annlite/core/index/pq_index.py ================================================ from typing import List, Optional import numpy as np from ...math import top_k from ..codec.pq import PQCodec from .flat_index import FlatIndex # TODO: deprecated this index class PQIndex(FlatIndex): # pragma: no cover def __init__( self, dim: int, pq_codec: PQCodec, **kwargs, ): assert pq_codec is not None self._dense_dim = dim super(PQIndex, self).__init__( pq_codec.n_subvectors, dtype=pq_codec.code_dtype, **kwargs ) self._pq_codec = pq_codec def add_with_ids(self, x: np.ndarray, ids: List[int]): x = self._pq_codec.encode(x) super(PQIndex, self).add_with_ids(x, ids) def search( self, x: np.ndarray, limit: int = 10, indices: Optional[np.ndarray] = None ): _dim = x.shape[-1] assert ( _dim == self._pq_codec.dim ), f'the query embedding dimension does not match with index dimension: {_dim} vs {self.dim}' precomputed = self._pq_codec.precompute_adc(x) codes = self._data data_idx = np.arange(self._capacity) if indices is not None: codes = self._data[indices] data_idx = data_idx[indices] dists = precomputed.adist(codes) # (10000, ) dists = np.expand_dims(dists, axis=0) dists, ids = top_k(dists, limit, descending=False) # TODO: change the shape of return ids = ids[0] if indices is not None: ids = data_idx[ids] return dists[0], ids ================================================ FILE: annlite/enums.py ================================================ from enum import IntEnum class BetterEnum(IntEnum): """The base class of Enum.""" def __str__(self): return self.name @classmethod def from_string(cls, s: str): """ Parse the enum from a string. :param s: string representation of the enum value :return: enum value """ try: return cls[s.upper()] except KeyError: raise ValueError( f'{s.upper()} is not a valid enum for {cls!r}, must be one of {list(cls)}' ) class Metric(BetterEnum): EUCLIDEAN = 1 INNER_PRODUCT = 2 COSINE = 3 class ExpandMode(BetterEnum): STEP = 1 DOUBLE = 2 ADAPTIVE = 3 ================================================ FILE: annlite/executor.py ================================================ import threading import time import traceback import warnings from threading import Thread from typing import Dict, List, Optional, Tuple, Union from docarray import Document, DocumentArray from jina import Executor, requests from jina.logging.logger import JinaLogger INDEX_BATCH_SIZE = 1024 class AnnLiteIndexer(Executor): """A simple indexer that wraps the AnnLite indexer and adds a simple interface for indexing and searching. :param n_dim: Dimensionality of vectors to index :param metric: Distance metric type. Can be 'euclidean', 'inner_product', or 'cosine' :param limit: Number of results to get for each query document in search :param n_components: Number of components to use for dimensionality reduction :param match_args: the arguments to `DocumentArray`'s match function :param data_path: the workspace of the AnnLiteIndexer but not support when shards > 1. :param ef_construction: The construction time/accuracy trade-off :param ef_search: The query time accuracy/speed trade-off :param max_connection: The maximum number of outgoing connections in the graph (the "M" parameter) :param include_metadata: If True, return the document metadata in response :param index_access_paths: Default traversal paths on docs (used for indexing, delete and update), e.g. '@r', '@c', '@r,c' :param search_access_paths: Default traversal paths on docs (used for search), e.g. '@r', '@c', '@r,c' :param columns: A list or dict of column names to index. :param dim: Deprecated, use n_dim instead """ def __init__( self, n_dim: int = 0, metric: str = 'cosine', limit: int = 10, n_components: Optional[int] = None, match_args: Optional[Dict] = None, data_path: Optional[str] = None, ef_construction: Optional[int] = None, ef_search: Optional[int] = None, max_connection: Optional[int] = None, include_metadata: bool = True, index_access_paths: str = '@r', search_access_paths: str = '@r', columns: Optional[Union[List[Tuple[str, str]], Dict[str, str]]] = None, list_like: Optional[bool] = False, dim: int = None, *args, **kwargs, ): super().__init__(*args, **kwargs) self.logger = JinaLogger(self.__class__.__name__) n_dim = n_dim or dim if not n_dim: raise ValueError('Please specify the dimension of the vectors to index!') self.n_components = n_components self.metric = metric self.match_args = match_args or {} self.include_metadata = include_metadata if limit: self.match_args.update({'limit': limit}) self.index_access_paths = index_access_paths if 'index_traversal_paths' in kwargs: warnings.warn( f'`index_traversal_paths` is deprecated. Use `index_access_paths` instead.' ) self.index_access_paths = kwargs['index_traversal_paths'] self.search_access_paths = search_access_paths if 'search_traversal_paths' in kwargs: warnings.warn( f'`search_traversal_paths` is deprecated. Use `search_access_paths` instead.' ) self.search_access_paths = kwargs['search_traversal_paths'] self._data_buffer = DocumentArray() self._index_batch_size = INDEX_BATCH_SIZE self._max_length_queue = 2 * self._index_batch_size self._index_lock = threading.Lock() self.logger = JinaLogger(getattr(self.metas, 'name', self.__class__.__name__)) if getattr(self.runtime_args, 'shards', 1) > 1 and data_path: raise ValueError( '`data_path` is not supported when shards > 1, please use `workspace` instead' ) config = { 'n_dim': n_dim, 'n_components': n_components, 'metric': metric, 'ef_construction': ef_construction, 'ef_search': ef_search, 'max_connection': max_connection, 'data_path': data_path or self.workspace or './workspace', 'columns': columns, 'list_like': list_like, } self._index = DocumentArray(storage='annlite', config=config) # start indexing thread in background to group indexing requests # together and perform batch indexing at once self._start_index_loop() @requests(on='/index') def index( self, docs: Optional[DocumentArray] = None, parameters: dict = {}, **kwargs ): """Index new documents :param docs: the Documents to index :param parameters: dictionary with options for indexing Keys accepted: - 'access_paths': traversal paths on docs, e.g. '@r', '@c', '@r,c' """ if not docs: return access_paths = parameters.get('access_paths', self.index_access_paths) flat_docs = docs[access_paths] if len(flat_docs) == 0: return while len(self._data_buffer) >= self._max_length_queue: time.sleep(0.001) with self._index_lock: self._data_buffer.extend(flat_docs) def _start_index_loop(self): """Start the indexing loop in background. This loop is responsible for batch indexing the documents in the buffer. """ def _index_loop(): try: while True: # if the buffer is none, will break the loop if self._data_buffer is None: break # if the buffer is empty, will wait for new documents to be added if len(self._data_buffer) == 0: time.sleep(0.1) # sleep for 100ms continue # acquire the lock to prevent threading issues with self._index_lock: batch_docs = self._data_buffer.pop( range( self._index_batch_size if len(self._data_buffer) > self._index_batch_size else len(self._data_buffer) ) ) self._index.extend(batch_docs) self.logger.debug(f'indexing {len(batch_docs)} docs done...') except Exception as e: self.logger.error(traceback.format_exc()) raise e self._index_thread = Thread(target=_index_loop, daemon=False) self._index_thread.start() @requests(on='/update') def update( self, docs: Optional[DocumentArray] = None, parameters: dict = {}, **kwargs ): """Update existing documents :param docs: the Documents to update :param parameters: dictionary with options for updating Keys accepted: - 'access_paths': traversal paths on docs, e.g. '@r', '@c', '@r,c' - 'raise_errors_on_not_found': if True, raise an error if a document is not found. Default is False. """ if not docs: return access_paths = parameters.get('access_paths', self.index_access_paths) raise_errors_on_not_found = parameters.get('raise_errors_on_not_found', False) flat_docs = docs[access_paths] if len(flat_docs) == 0: return with self._index_lock: if len(self._data_buffer) > 0: raise RuntimeError( f'Cannot update documents while the pending documents in the buffer are not indexed yet. ' 'Please wait for the pending documents to be indexed.' ) for doc in flat_docs: try: self._index[doc.id] = doc except IndexError: if raise_errors_on_not_found: raise Exception( f'The document (id={doc.id}) cannot be updated as' f'it is not found in the index' ) else: self.logger.warning( f'cannot update doc {doc.id} as it does not exist in storage' ) @requests(on='/delete') def delete(self, parameters: dict = {}, **kwargs): """Delete existing documents Delete entries from the index by id :param parameters: parameters to the request """ delete_ids = parameters.get('ids', []) if len(delete_ids) == 0: return with self._index_lock: if len(self._data_buffer) > 0: raise RuntimeError( f'Cannot delete documents while the pending documents in the buffer are not indexed yet. ' 'Please wait for the pending documents to be indexed.' ) del self._index[delete_ids] @requests(on='/search') def search( self, docs: Optional[DocumentArray] = None, parameters: dict = {}, **kwargs ): """Perform a vector similarity search and retrieve Document matches Search can be performed with candidate filtering. Filters are a triplet (column,operator,value). More than a filter can be applied during search. Therefore, conditions for a filter are specified as a list triplets. Each triplet contains: - column: Column used to filter. - operator: Binary operation between two values. Some supported operators include `['>','<','=','<=','>=']`. - value: value used to compare a candidate. :param docs: the Documents to search with :param parameters: dictionary for parameters for the search operation Keys accepted: - 'access_paths' (str): traversal paths on docs, e.g. '@r', '@c', '@r,c' - 'filter' (dict): the filtering conditions on document tags - 'limit' (int): nr of matches to get per Document """ if not docs: return access_paths = parameters.get('access_paths', self.search_access_paths) flat_docs = docs[access_paths] match_args = ( {**self.match_args, **parameters} if parameters is not None else self.match_args ) with self._index_lock: # if len(self._data_buffer) > 0: # raise RuntimeError( # f'Cannot search documents while the pending documents in the buffer are not indexed yet. ' # 'Please wait for the pending documents to be indexed.' # ) flat_docs.match(self._index, **match_args) @requests(on='/backup') def backup(self, parameters: Optional[Dict] = {}, **kwargs): """ Backup data to local or remote. Use api of Keys accepted: - 'target' (str): the name of indexer you want to backup as """ target_name = parameters.get('target_name', None) token = parameters.get('token', None) if target_name: target_name = f'{target_name}_{self.runtime_args.shard_id}' with self._index_lock: if len(self._data_buffer) > 0: raise RuntimeError( f'Cannot backup documents while the pending documents in the buffer are not indexed yet. ' 'Please wait for the pending documents to be indexed.' ) self._index._annlite.backup(target_name, token) if self._index._list_like: self._index._save_offset2ids() @requests(on='/restore') def restore(self, parameters: Optional[Dict] = {}, **kwargs): """ Restore data from local or remote. Use api of """ source_name = parameters.get('source_name', None) token = parameters.get('token', None) if source_name: source_name = f'{source_name}_{self.runtime_args.shard_id}' self._index._annlite.restore(source_name, token) if self._index._list_like: self._index._load_offset2ids() @requests(on='/filter') def filter(self, parameters: Dict, **kwargs): """ Query documents from the indexer by the filter `query` object in parameters. The `query` object must follow the specifications in the `find` method of `DocumentArray` using annlite: https://docarray.jina.ai/fundamentals/documentarray/find/#filter-with-query-operators :param parameters: Dictionary to define the `filter` that you want to use. """ return self._index.find(parameters.get('filter', None)) @requests(on='/fill_embedding') def fill_embedding(self, docs: DocumentArray, **kwargs): """ retrieve embedding of Documents by id :param docs: DocumentArray to search with """ for doc in docs: doc.embedding = self._index[doc.id].embedding @requests(on='/status') def status(self, **kwargs) -> DocumentArray: """Return the document containing status information about the indexer. The status will contain information on the total number of indexed and deleted documents, and on the number of (searchable) documents currently in the index. """ status = Document( tags={ 'appending_size': len(self._data_buffer), 'total_docs': len(self._index), 'index_size': len(self._index), } ) return DocumentArray([status]) def flush(self): """Flush all the data in the buffer to the index""" while len(self._data_buffer) > 0: time.sleep(0.1) @requests(on='/clear') def clear(self, **kwargs): """Clear the index of all entries.""" self.flush() with self._index_lock: self._data_buffer = None self._index_thread.join() self._data_buffer = DocumentArray() self._index.clear() self._start_index_loop() def close(self, **kwargs): """Close the index.""" super().close() self.flush() # wait for the index thread to finish with self._index_lock: self._data_buffer = None self._index_thread.join() # WARNING: the commented code below hangs the close in pytest `pytest tests/test_*.py` # But don't know why. It works fine in `pytest tests/test_executor.py` and normal python execution del self._index ================================================ FILE: annlite/filter.py ================================================ from typing import Dict LOGICAL_OPERATORS = {'$and': 'AND', '$or': 'OR'} COMPARISON_OPERATORS = { '$lt': '<', '$gt': '>', '$lte': '<=', '$gte': '>=', '$eq': '=', '$neq': '!=', } MEMBERSHIP_OPERATORS = {'$in': 'IN', '$nin': 'NOT IN'} def _sql_parsing(data, default_logic: str = 'AND'): """ :param data: JSON Object (dict). :param parameters: dict. :return: where clause (str) built from data """ where_clause = '' parameters = [] if isinstance(data, dict): for i, (key, value) in enumerate(data.items()): if key in LOGICAL_OPERATORS: clause, params = _sql_parsing( value, default_logic=LOGICAL_OPERATORS[key] ) if i == 0: where_clause += clause else: where_clause += f' {LOGICAL_OPERATORS[key]} {clause}' parameters.extend(params) elif key.startswith('$'): raise ValueError( f'The operator {key} is not supported yet, please double check the given filters!' ) else: if i > 0: where_clause += f' {default_logic} ' items = list(value.items()) if len(items) == 0: raise ValueError(f'The query express is illegal: {data}') elif len(items) > 1: clause_list, params_list = [], [] for op, val in items: _clause, _params = _sql_parsing({key: {op: val}}) clause_list.append(_clause) params_list.extend(_params) where_clause += f' AND '.join(clause_list) parameters.extend(params_list) else: op, val = items[0] if op in LOGICAL_OPERATORS: clause, params = _sql_parsing( val, default_logic=LOGICAL_OPERATORS[op] ) where_clause += clause parameters.extend(params) elif op in COMPARISON_OPERATORS: parameters.append(val) where_clause += f'({key} {COMPARISON_OPERATORS[op]} ?)' elif op in MEMBERSHIP_OPERATORS: parameters.extend(val) where_clause += f'({key} {MEMBERSHIP_OPERATORS[op]}({", ".join(["?"]*len(val))}))' else: raise ValueError( f'The operator {op} is not supported yet, please double check the given filters!' ) elif isinstance(data, list): clause_list, params_list = [], [] for d in data: _clause, _params = _sql_parsing(d) clause_list.append(_clause) params_list.extend(_params) where_clause += '(' + f' {default_logic} '.join(clause_list) + ')' parameters.extend(params_list) elif isinstance(data, str): return data, parameters else: raise ValueError(f'The query express is illegal: {data}') return where_clause, tuple(parameters) class Filter(object): """A class to parse query language to SQL where clause.""" def __init__(self, tree_data: Dict = {}): self.tree_data = tree_data def parse_where_clause(self): return _sql_parsing(self.tree_data or {}) ================================================ FILE: annlite/helper.py ================================================ import sys import numpy as np from loguru import logger def setup_logging(debug: bool): """ Setup the log formatter for AnnLite. """ log_level = 'INFO' if debug: log_level = 'DEBUG' logger.remove() logger.add( sys.stdout, colorize=True, level=log_level, ) def str2dtype(dtype_str: str): if dtype_str in ['double', 'float64']: dtype = np.float64 elif dtype_str in ['half', 'float16']: dtype = np.float16 elif dtype_str in ['float', 'float32']: dtype = np.float32 elif dtype_str in ['bfloat16']: dtype = np.bfloat16 elif dtype_str in ['long', 'int64']: dtype = np.int64 elif dtype_str in ['int', 'int32']: dtype = np.int32 elif dtype_str in ['int16']: dtype = np.int16 elif dtype_str in ['int8']: dtype = np.int8 elif dtype_str in ['uint8']: dtype = np.uint8 elif dtype_str in ['bool']: dtype = np.bool else: raise TypeError(f'Unrecognized dtype string: {dtype_str}') return dtype ================================================ FILE: annlite/hubble_tools.py ================================================ import os import platform import shutil import time from pathlib import Path from typing import Optional, Union from filesplit.merge import Merge from filesplit.split import Split from loguru import logger ignored_extn = ['.DS_Store'] def get_size(input: Path) -> float: import os return os.stat(str(input)).st_size / (1024 * 1024) def make_archive(input: Path, output_name: str) -> Path: """ This function will create a zip archive of the input file (tmp.zip) at the same folder of input path. """ output_path = shutil.make_archive( os.path.join(str(input.parent), output_name), 'zip', str(input.parent), str(input.name), ) return Path(output_path) class Uploader: def __init__(self, size_limit=1024, client=None): """ This class create a filesplit object to split the file into small pieces and upload them on to hubble. :params size_limit: The max size of split files. :params client: hubble client used for uploading. """ self.size_limit = size_limit self.client = client def upload_file( self, input: Path, target_name: str, type: str, cell_id: Union[int, str] ): logger.info(f'Start to upload single file: {input} to hubble ...') size = get_size(input) if size > self.size_limit: split_list = self._split_file(input) self.upload_directory(split_list, target_name, type, cell_id, merge=False) shutil.rmtree(split_list) else: if self._check_exists(target_name, type, input.name): return self._upload_hubble(input, target_name, type, input.name, cell_id) def upload_directory( self, input: Path, target_name: str, type: str, cell_id: Union[int, str], merge: bool = True, ): def _upload(): if self._check_exists(target_name, type, str(idx) + '.zip'): return Path.mkdir(input.parent / str(idx)) for f in split_list: shutil.copy(f, input.parent / str(idx)) output_path = make_archive(input.parent / str(idx), str(idx) + '.zip') self._upload_hubble( output_path, target_name, type, str(idx) + '.zip', cell_id ) Path(output_path).unlink() shutil.rmtree(input.parent / str(idx)) logger.info(f'Start to upload directory: {input} to hubble ...') if merge: size_list = list( zip(list(input.iterdir()), [get_size(f) for f in list(input.iterdir())]) ) sorted_size_list = sorted(size_list, key=lambda x: x[1]) split_list = [] total_size = 0 idx = 0 for file_name, file_size in sorted_size_list: for extn in ignored_extn: if extn in str(file_name): continue if total_size + file_size > self.size_limit: if len(split_list) == 0: raise Exception( f'The smallest file: {file_size} is bigger ' f'than size_limit. Please set a larger value ' f'of size_limit, now is {self.size_limit}MB.' ) _upload() idx += 1 total_size = 0 split_list = [file_name] else: split_list.append(file_name) total_size += file_size if len(split_list) > 0: _upload() else: for idx, file_name in enumerate(list(input.glob('*'))): if self._check_exists(target_name, type, str(file_name.name)): continue self._upload_hubble( file_name, target_name, type, str(file_name.name), cell_id ) def archive_and_upload( self, target_name: str, type: str, file_name: str, cell_id: Union[int, str], root_path: Path, upload_folder: str, ): if self._check_exists(target_name, type, file_name): return upload_file = shutil.make_archive( os.path.join(str(root_path), f'{target_name}_{type}'), 'zip', str(root_path), upload_folder, ) logger.info( f'Start to upload: {upload_file} to hubble. ' f'[target_name: {target_name}, ' f'type: {type}, ' f'file_name: {file_name}, ' f'cell_id: {cell_id}].' ) self.client.upload_artifact( f=upload_file, metadata={ 'name': target_name, 'type': type, 'file_name': file_name, 'cell': cell_id, }, ) Path(upload_file).unlink() def _check_exists(self, target_name: str, type: str, file_name: str) -> bool: art_list = self.client.list_artifacts( filter={ 'metaData.name': target_name, 'metaData.type': f'{type}', 'metaData.file_name': f'{file_name}', } ) if len(art_list['data']) != 0: logger.info( f'[target_name: {target_name}, type: {type}, file_name: {file_name}] ' f'already exists on hubble, will skip it ...' ) return True else: return False def _split_file(self, input: Path) -> Path: output_dir = input / f'{input}_split' if output_dir.exists(): logger.info( f'Origin file: {str(input)} has already been split to: {output_dir}, will skip ...' ) return output_dir Path.mkdir(output_dir) Split(str(input), str(output_dir)).bysize(size=self.size_limit * 1024 * 1024) num_files = len(list(output_dir.glob('*'))) logger.info( f'Origin file: {str(input)} has been split ' f'into {num_files} parts. Output file: {output_dir}' ) return output_dir def _upload_hubble( self, upload_file: Path, target_name: str, type: str, file_name: str, cell_id: Union[str, int], ): logger.info( f'Start to upload: {upload_file} to hubble. ' f'[target_name: {target_name}, ' f'type: {type}, ' f'file_name: {file_name}, ' f'cell_id: {cell_id}].' ) start_time = time.time() failed_times = 0 while True: try: self.client.upload_artifact( f=str(upload_file), metadata={ 'name': target_name, 'type': type, 'file_name': file_name, 'cell': cell_id, }, show_progress=True, ) break except Exception as e: logger.info(e) failed_times += 1 if failed_times == 3: logger.info( f'Tried more than 3 times to upload {upload_file}, type is: {type}, will exist...' ) return else: continue logger.info( f'Takes {time.time() - start_time} seconds to upload {upload_file}.' ) class Merger: def __init__(self, restore_path, client): """ This class creates an object to download and merge the split files from hubble. :param restore_path: tmp directory for downloading and merging files. :param client: hubble client used for merging files. """ self.restore_path = restore_path self.restore_path.mkdir(parents=True) self.client = client def merge_file(self, inputdir: Path, outputdir: Path, outputfilename: Path): Merge( inputdir=str(inputdir), outputdir=str(outputdir), outputfilename=str(outputfilename), ).merge() def get_artifact_ids(self, art_list, type: str, cell_id: Optional[int] = None): ids = [ [ art['_id'], art['metaData']['type'], art['metaData']['file_name'], art['metaData']['cell'], ] for art in art_list['data'] if type == art['metaData']['type'] ] if cell_id: ids = [item for item in ids if int(item[3]) == cell_id] ids = [[item[0], item[1], item[2]] for item in ids] return ids def download(self, ids, download_folder): Path.mkdir(self.restore_path / download_folder) for ids, type, file_name in ids: self.client.download_artifact( id=ids, f=str(self.restore_path / download_folder / file_name), show_progress=True, ) ================================================ FILE: annlite/index.py ================================================ import hashlib import logging import os import platform import warnings from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Optional, Union import numpy as np from docarray.math.ndarray import to_numpy_array from loguru import logger if TYPE_CHECKING: from docarray import DocumentArray from .container import CellContainer from .core import PQCodec, ProjectorCodec, VQCodec from .enums import Metric from .filter import Filter from .helper import setup_logging from .math import cdist, top_k MAX_TRAINING_DATA_SIZE = 10240 class AnnLite(CellContainer): """:class:`AnnLite` is an approximate nearest neighbor search library. To create a :class:`AnnLite` object, simply: .. highlight:: python .. code-block:: python ann = AnnLite(256, metric='cosine') :param n_dim: dimensionality of input vectors. there are 2 constraints on dim: (1) it needs to be divisible by n_subvectors; (2) it needs to be a multiple of 4.* :param metric: distance metric type, can be 'euclidean', 'inner_product', or 'cosine'. :param n_subvectors: number of sub-quantizers, essentially this is the byte size of each quantized vector, default is None. :param n_cells: number of coarse quantizer clusters, default is 1. :param n_probe: number of cells to search for each query, default is 16. :param n_components: number of components to keep. :param initial_size: initial capacity assigned to each voronoi cell of coarse quantizer. ``n_cells * initial_size`` is the number of vectors that can be stored initially. if any cell has reached its capacity, that cell will be automatically expanded. If you need to add vectors frequently, a larger value for init_size is recommended. :param columns: the columns to be indexed for fast filtering, default is None. :param filterable_attrs: a dict of attributes to be indexed for fast filtering, default is None. The key is the attribute name, and the value is the attribute type. And it only works when ``columns`` is None. :param data_path: path to the directory where the data is stored. :param create_if_missing: if False, do not create the directory path if it is missing. :param read_only: if True, the index is not writable. :param verbose: if True, will print the debug logging info. .. note:: Remember that the shape of any tensor that contains data points has to be `[n_data, dim]`. """ def __init__( self, n_dim: int, metric: Union[str, Metric] = 'cosine', n_cells: int = 1, n_subvectors: Optional[int] = None, n_clusters: Optional[int] = 256, n_probe: int = 16, n_components: Optional[int] = None, initial_size: Optional[int] = None, expand_step_size: int = 10240, columns: Optional[Union[Dict, List]] = None, filterable_attrs: Optional[Dict] = None, data_path: Union[Path, str] = Path('./data'), create_if_missing: bool = True, read_only: bool = False, verbose: bool = False, **kwargs, ): setup_logging(verbose) if 'dim' in kwargs: warnings.warn( 'The argument `dim` will be deprecated, please use `n_dim` instead.' ) n_dim = kwargs['dim'] if n_subvectors: assert ( n_dim % n_subvectors == 0 ), '"n_dim" needs to be divisible by "n_subvectors"' self.n_dim = n_dim self.n_components = n_components self.n_subvectors = n_subvectors self.n_clusters = n_clusters self.n_probe = max(n_probe, n_cells) self.n_cells = n_cells self.size_limit = 2048 if isinstance(metric, str): metric = Metric.from_string(metric) self.metric = metric self._use_smart_probing = True self.read_only = read_only data_path = Path(data_path) if create_if_missing: data_path.mkdir(parents=True, exist_ok=True) self.data_path = data_path self._projector_codec = None if self._projector_codec_path.exists(): logger.info( f'Load pre-trained projector codec (n_components={self.n_components}) from {self.model_path}' ) self._projector_codec = ProjectorCodec.load(self._projector_codec_path) elif n_components: logger.info( f'Initialize Projector codec (n_components={self.n_components})' ) self._projector_codec = ProjectorCodec( n_dim, n_components=self.n_components ) self._vq_codec = None if self._vq_codec_path.exists(): logger.info( f'Load trained VQ codec (K={self.n_cells}) from {self.model_path}' ) self._vq_codec = VQCodec.load(self._vq_codec_path) elif n_cells > 1: logger.info(f'Initialize VQ codec (K={self.n_cells})') self._vq_codec = VQCodec(self.n_cells, metric=self.metric) self._pq_codec = None if self._pq_codec_path.exists(): logger.info( f'Load trained PQ codec (n_subvectors={self.n_subvectors}) from {self.model_path}' ) self._pq_codec = PQCodec.load(self._pq_codec_path) elif n_subvectors: logger.info(f'Initialize PQ codec (n_subvectors={self.n_subvectors})') self._pq_codec = PQCodec( dim=n_dim if not self._projector_codec else self._projector_codec.n_components, n_subvectors=self.n_subvectors, n_clusters=self.n_clusters, metric=self.metric, ) if columns is not None: if filterable_attrs: logger.warning('`filterable_attrs` will be overwritten by `columns`.') filterable_attrs = {} for n, t in columns.items() if isinstance(columns, dict) else columns: filterable_attrs[n] = t super(AnnLite, self).__init__( n_dim, metric=metric, projector_codec=self._projector_codec, pq_codec=self._pq_codec, n_cells=n_cells, initial_size=initial_size, expand_step_size=expand_step_size, filterable_attrs=filterable_attrs, data_path=data_path, **kwargs, ) if not self.is_trained and self.total_docs > 0: # train the index from scratch based on the data in the data_path logger.info(f'Train the index by reading data from {self.data_path}') total_size = 0 # TODO: add a progress bar for docs in self.documents_generator(0, batch_size=1024): x = to_numpy_array(docs.embeddings) total_size += x.shape[0] self.partial_train(x, auto_save=True, force_train=True) if total_size >= MAX_TRAINING_DATA_SIZE: break logger.info(f'Total training data size: {total_size}') if self.total_docs > 0: self.restore() def _sanity_check(self, x: 'np.ndarray'): assert x.ndim == 2, 'inputs must be a 2D array' assert ( x.shape[1] == self.n_dim ), f'inputs must have the same dimension as the index , got {x.shape[1]}, expected {self.n_dim}' return x.shape def train(self, x: 'np.ndarray', auto_save: bool = True, force_train: bool = False): """Train the index with the given data. :param x: the ndarray data for training. :param auto_save: if False, will not dump the trained model to ``model_path``. :param force_train: if True, enforce to retrain the model, and overwrite the model if ``auto_save=True``. """ n_data, _ = self._sanity_check(x) if self.is_trained and not force_train: logger.warning( 'The indexer has been trained or is not trainable. Please use ``force_train=True`` to retrain.' ) return if self._projector_codec: logger.info( f'Start training Projector codec (n_components={self.n_components}) with {n_data} data...' ) self._projector_codec.fit(x) if self._vq_codec: logger.info( f'Start training VQ codec (K={self.n_cells}) with {n_data} data...' ) self._vq_codec.fit(x) if self._pq_codec: logger.info( f'Start training PQ codec (n_subvectors={self.n_subvectors}) with {n_data} data...' ) self._pq_codec.fit(x) logger.info(f'The annlite is successfully trained!') if auto_save: self.dump_model() def partial_train( self, x: np.ndarray, auto_save: bool = True, force_train: bool = False ): """Partially train the index with the given data. :param x: the ndarray data for training. :param auto_save: if False, will not dump the trained model to ``model_path``. :param force_train: if True, enforce to retrain the model, and overwrite the model if ``auto_save=True``. """ n_data, _ = self._sanity_check(x) if self.is_trained and not force_train: logger.warning( 'The annlite has been trained or is not trainable. Please use ``force_train=True`` to retrain.' ) return if self._projector_codec: logging.info( f'Partial training Projector codec (n_components={self.n_components}) with {n_data} data...' ) self._projector_codec.partial_fit(x) if self._vq_codec: logger.info( f'Partial training VQ codec (K={self.n_cells}) with {n_data} data...' ) self._vq_codec.partial_fit(x) if self._pq_codec: logger.info( f'Partial training PQ codec (n_subvectors={self.n_subvectors}) with {n_data} data...' ) self._pq_codec.partial_fit(x) if auto_save: self.dump_model() def index(self, docs: 'DocumentArray', **kwargs): """Add the documents to the index. :param docs: the document array to be indexed. """ if self.read_only: logger.error('The indexer is readonly, cannot add new documents') return if not self.is_trained: raise RuntimeError(f'The indexer is not trained, cannot add new documents') x = to_numpy_array(docs.embeddings) n_data, _ = self._sanity_check(x) assigned_cells = ( self._vq_codec.encode(x) if self._vq_codec else np.zeros(n_data, dtype=np.int64) ) return super(AnnLite, self).insert(x, assigned_cells, docs) def update( self, docs: 'DocumentArray', raise_errors_on_not_found: bool = False, insert_if_not_found: bool = True, **kwargs, ): """Update the documents in the index. :param insert_if_not_found: whether to raise error when updated id is not found. :param raise_errors_on_not_found: whether to raise exception when id not found. :param docs: the document array to be updated. """ if self.read_only: logger.error('The indexer is readonly, cannot update documents') return if not self.is_trained: raise RuntimeError(f'The indexer is not trained, cannot add new documents') x = to_numpy_array(docs.embeddings) n_data, _ = self._sanity_check(x) assigned_cells = ( self._vq_codec.encode(x) if self._vq_codec else np.zeros(n_data, dtype=np.int64) ) return super(AnnLite, self).update( x, assigned_cells, docs, raise_errors_on_not_found=raise_errors_on_not_found, insert_if_not_found=insert_if_not_found, ) def search( self, docs: 'DocumentArray', filter: Optional[dict] = None, limit: int = 10, include_metadata: bool = True, **kwargs, ): """Search the index, and attach matches to the query Documents in `docs` :param docs: the document array to be searched. :param filter: the filter to be applied to the search. :param limit: the number of results to get for each query document in search :param include_metadata: whether to return document metadata in response. """ if not self.is_trained: raise RuntimeError(f'The indexer is not trained, cannot add new documents') query_np = to_numpy_array(docs.embeddings) match_dists, match_docs = self.search_by_vectors( query_np, filter=filter, limit=limit, include_metadata=include_metadata ) for doc, matches in zip(docs, match_docs): doc.matches = matches def search_by_vectors( self, query_np: 'np.ndarray', filter: Optional[dict] = None, limit: int = 10, include_metadata: bool = True, ): """Search the index by vectors, and return the matches. :param query_np: the query vectors. :param filter: the filter to be applied to the search. :param limit: the number of results to get for each query document in search :param include_metadata: whether to return document metadata in response. """ cells = self._cell_selection(query_np, limit) where_clause, where_params = Filter(filter or {}).parse_where_clause() match_dists, match_docs = self.search_cells( query=query_np, cells=cells, where_clause=where_clause, where_params=where_params, limit=limit, include_metadata=include_metadata, ) return match_dists, match_docs def filter( self, filter: Dict, limit: int = 10, offset: int = 0, order_by: Optional[str] = None, ascending: bool = True, include_metadata: bool = True, ): """Find the documents by the filter. :param filter: the filter to be applied to the search. :param limit: the number of results. :param offset: the offset of the results. :param order_by: the field to order the results. :param ascending: whether to order the results in ascending order. :param include_metadata: whether to return document metadata in response. """ cells = [x for x in range(self.n_cells)] where_clause, where_params = Filter(filter or {}).parse_where_clause() match_docs = self.filter_cells( cells=cells, where_clause=where_clause, where_params=where_params, limit=limit, offset=offset, order_by=order_by, ascending=ascending, include_metadata=include_metadata, ) if limit > 0: return match_docs[:limit] return match_docs def get_doc_by_id(self, doc_id: str): """Get the document by id. :param doc_id: the document id. """ return self._get_doc_by_id(doc_id) def get_docs( self, filter: Optional[dict] = None, limit: int = 10, offset: int = 0, order_by: Optional[str] = None, ascending: bool = True, ): """Get the documents. :param filter: the filter to be applied to the search. :param limit: the number of results. :param offset: the offset of the results. :param order_by: the field to order the results. :param ascending: whether to order the results in ascending order. It only works when `order_by` is specified. """ return self.filter( filter=filter, limit=limit, offset=offset, order_by=order_by, ascending=ascending, include_metadata=True, ) def _cell_selection(self, query_np, limit): n_data, _ = self._sanity_check(query_np) if self._vq_codec: dists = cdist( query_np, self._vq_codec.codebook, metric=self.metric.name.lower() ) dists, cells = top_k(dists, k=self.n_probe) else: cells = np.zeros((n_data, 1), dtype=np.int64) # if self.use_smart_probing and self.n_probe > 1: # p = -topk_sims.abs().sqrt() # p = torch.softmax(p / self.smart_probing_temperature, dim=-1) # # # p_norm = p.norm(dim=-1) # # sqrt_d = self.n_probe ** 0.5 # # score = 1 - (p_norm * sqrt_d - 1) / (sqrt_d - 1) - 1e-6 # # n_probe_list = torch.ceil(score * (self.n_probe) ).long() # # max_n_probe = torch.tensor(self.n_probe, device=self.device) # normalized_entropy = - torch.sum(p * torch.log2(p) / torch.log2(max_n_probe), dim=-1) # n_probe_list = torch.ceil(normalized_entropy * max_n_probe).long() # else: # n_probe_list = None return cells def search_numpy( self, query_np: 'np.ndarray', filter: Dict = {}, limit: int = 10, **kwargs, ): """Search the index and return distances to the query and ids of the closest documents. :param query_np: matrix containing query vectors as rows :param filter: the filtering conditions :param limit: the number of results to get for each query document in search """ if not self.is_trained: raise RuntimeError(f'The indexer is not trained, cannot add new documents') dists, doc_ids = self._search_numpy(query_np, filter, limit) return dists, doc_ids def _search_numpy(self, query_np: 'np.ndarray', filter: Dict = {}, limit: int = 10): """Search approximate nearest vectors in different cells, returns distances and ids :param query_np: matrix containing query vectors as rows :param filter: the filtering conditions :param limit: the number of results to get for each query document in search """ cells = self._cell_selection(query_np, limit) where_clause, where_params = Filter(filter).parse_where_clause() dists, ids = self._search_cells( query=query_np, cells=cells, where_clause=where_clause, where_params=where_params, limit=limit, ) return dists, ids def delete( self, docs: Union['DocumentArray', List[str]], raise_errors_on_not_found: bool = False, ): """Delete entries from the index by id :param raise_errors_on_not_found: whether to raise exception when id not found. :param docs: the documents to delete """ super().delete( docs if isinstance(docs, list) else docs[:, 'id'], raise_errors_on_not_found ) def clear(self): """Clear the whole database""" for cell_id in range(self.n_cells): self.vec_index(cell_id).reset() self.cell_table(cell_id).clear() self.doc_store(cell_id).clear() self.meta_table.clear() def close(self): for cell_id in range(self.n_cells): self.doc_store(cell_id).close() def encode(self, x: 'np.ndarray'): n_data, _ = self._sanity_check(x) if self._projector_codec: x = self._projector_codec.encode(x) if self._vq_codec: x = self._pq_codec.encode(x) return x def decode(self, x: 'np.ndarray'): assert len(x.shape) == 2 assert x.shape[1] == self.n_subvectors if self._pq_codec: x = self._pq_codec.decode(x) if self._projector_codec: x = self._projector_codec.decode(x) return x @property def params_hash(self): model_metas = ( f'n_dim: {self.n_dim} ' f'metric: {self.metric} ' f'n_cells: {self.n_cells} ' f'n_components: {self.n_components} ' f'n_subvectors: {self.n_subvectors}' ) return hashlib.md5(f'{model_metas}'.encode()).hexdigest() @property def model_path(self): return self.data_path / f'parameters-{self.params_hash}' @property def _vq_codec_path(self): return self.model_path / f'vq_codec.params' @property def _pq_codec_path(self): return self.model_path / f'pq_codec.params' @property def _projector_codec_path(self): return self.model_path / f'projector_codec.params' @property def index_hash(self): latest_commit = self.meta_table.get_latest_commit() date_time = latest_commit[-1] if latest_commit else None if date_time: if platform.system() == 'Windows': return date_time.isoformat('#', 'hours') return date_time.isoformat('#', 'seconds') else: import datetime return ( datetime.datetime.utcnow().isoformat('#', 'hours') if platform.system() == 'Windows' else datetime.datetime.utcnow().isoformat('#', 'seconds') ) @property def index_path(self): if self.index_hash: return ( self.data_path / f'snapshot-{self.params_hash}' / f'{self.index_hash}-SNAPSHOT' ) return None @property def snapshot_path(self): paths = list( (self.data_path / f'snapshot-{self.params_hash}').glob(f'*-SNAPSHOT') ) if paths: paths = sorted(paths, key=lambda x: x.name) return paths[-1] return None @property def remote_store_client(self): try: import hubble os.environ['JINA_AUTH_TOKEN'] = self.token client = hubble.Client(max_retries=None, jsonify=True) client.get_user_info() return client except Exception as ex: logger.error(f'Not login to hubble yet.') raise ex def backup(self, target_name: Optional[str] = None, token: Optional[str] = None): # file lock will be released when backup to remote, this will # release the file lock. And it's only needed in Windows # since we need to release file lock before we can access rocksdb files. if not target_name: logger.info('dump to local ...') self.dump() else: if token is None: logger.error(f'back up to remote needs token') logger.info(f'dump to remote: {target_name}') self.close() self._backup_index_to_remote(target_name, token) def restore(self, source_name: Optional[str] = None, token: Optional[str] = None): # file lock will be released when restore from remote if not source_name: if self.total_docs > 0: logger.info(f'restore Annlite from local') self._rebuild_index_from_local() else: if token is None: logger.error(f'restore from remote needs token') logger.info(f'restore Annlite from artifact: {source_name}') self.close() self._rebuild_index_from_remote(source_name, token) def dump_model(self): logger.info(f'Save the parameters to {self.model_path}') self.model_path.mkdir(parents=True, exist_ok=True) if self._projector_codec: self._projector_codec.dump(self._projector_codec_path) if self._vq_codec: self._vq_codec.dump(self._vq_codec_path) if self._pq_codec: self._pq_codec.dump(self._pq_codec_path) def dump_index(self): import shutil logger.info(f'Save the indexer to {self.index_path}') try: if Path.exists(self.index_path): logger.info( f'Index path {self.index_path} already exists, will be ' f'overwritten' ) shutil.rmtree(self.index_path) self.index_path.mkdir(parents=True) for cell_id in range(self.n_cells): self.vec_index(cell_id).dump(self.index_path / f'cell_{cell_id}.hnsw') self.cell_table(cell_id).dump(self.index_path / f'cell_{cell_id}.db') self.meta_table.dump(self.index_path / f'meta.db') except Exception as ex: logger.error(f'Failed to dump the indexer, {ex!r}') if self.index_path: shutil.rmtree(self.index_path) def dump(self): self.dump_model() self.dump_index() def _backup_index_to_remote(self, target_name: str, token: str): self.dump() from .hubble_tools import Uploader self.token = token client = self.remote_store_client uploader = Uploader(size_limit=self.size_limit, client=client) for cell_id in range(self.n_cells): # upload database uploader.upload_directory( Path(self.data_path) / f'cell_{cell_id}', target_name=target_name, type='database', cell_id=cell_id, ) # upload hnsw file uploader.upload_file( Path(self.index_path) / f'cell_{cell_id}.hnsw', target_name=target_name, type='hnsw', cell_id=cell_id, ) # upload cell_table uploader.upload_file( Path(self.index_path) / f'cell_{cell_id}.db', target_name=target_name, type='cell_table', cell_id=cell_id, ) # upload meta table uploader.upload_file( Path(self.index_path) / 'meta.db', target_name=target_name, type='meta_table', cell_id=0, ) # upload training model uploader.archive_and_upload( target_name, 'model', 'model.zip', 'all', self.model_path.parent, str(self.model_path.name), ) def _rebuild_index_from_local(self): if self.snapshot_path: logger.info(f'Load the indexer from snapshot {self.snapshot_path}') for cell_id in range(self.n_cells): self.vec_index(cell_id).load( self.snapshot_path / f'cell_{cell_id}.hnsw' ) self.cell_table(cell_id).load(self.snapshot_path / f'cell_{cell_id}.db') self.meta_table.load(self.snapshot_path / f'meta.db') else: logger.info(f'Rebuild the indexer from scratch') for cell_id in range(self.n_cells): cell_size = self.doc_store(cell_id).size if cell_size == 0: continue # skip empty cell logger.debug( f'Rebuild the index of cell-{cell_id} ({cell_size} docs)...' ) for docs in self.documents_generator(cell_id, batch_size=10240): x = to_numpy_array(docs.embeddings) assigned_cells = np.ones(len(docs), dtype=np.int64) * cell_id super().insert(x, assigned_cells, docs, only_index=True) logger.debug(f'Rebuild the index of cell-{cell_id} done') if self.model_path: logger.info(f'Load the model from {self.model_path}') self._reload_models() def _rebuild_index_from_remote(self, source_name: str, token: str): import shutil from .hubble_tools import Merger self.token = token client = self.remote_store_client art_list = client.list_artifacts( filter={'metaData.name': source_name}, pageSize=100 ) if len(art_list['data']) == 0: logger.info(f'The indexer `{source_name}` not found. ') else: logger.info(f'Load the indexer `{source_name}` from remote store') restore_path = self.data_path / 'restore' merger = Merger(restore_path=restore_path, client=client) for cell_id in range(self.n_cells): # download hnsw files and merge and load logger.info(f'Load the hnsw `{source_name}` from remote store') hnsw_ids = merger.get_artifact_ids( art_list, type='hnsw', cell_id=cell_id ) merger.download(ids=hnsw_ids, download_folder=f'hnsw_{cell_id}') if len(hnsw_ids) > 1: merger.merge_file( inputdir=restore_path / f'hnsw_{cell_id}', outputdir=restore_path / f'hnsw_{cell_id}', outputfilename=Path(f'cell_{cell_id}.hnsw'), ) self.vec_index(cell_id).load( restore_path / f'hnsw_{cell_id}' / f'cell_{cell_id}.hnsw' ) shutil.rmtree(restore_path / f'hnsw_{cell_id}') # download cell_table files and merge and load logger.info(f'Load the cell_table `{source_name}` from remote store') cell_table_ids = merger.get_artifact_ids( art_list, type='cell_table', cell_id=cell_id ) merger.download( ids=cell_table_ids, download_folder=f'cell_table_{cell_id}' ) if len(cell_table_ids) > 1: merger.merge_file( inputdir=restore_path / f'cell_table_{cell_id}', outputdir=restore_path / f'cell_table_{cell_id}', outputfilename=Path(f'cell_{cell_id}.db'), ) self.cell_table(cell_id).load( restore_path / f'cell_table_{cell_id}' / f'cell_{cell_id}.db' ) shutil.rmtree(restore_path / f'cell_table_{cell_id}') # download database files and rebuild logger.info(f'Load the database `{source_name}` from remote store') database_ids = merger.get_artifact_ids( art_list, type='database', cell_id=cell_id ) merger.download(ids=database_ids, download_folder='database') for zip_file in list((restore_path / 'database').iterdir()): # default has only one cell shutil.unpack_archive(zip_file, self.data_path / f'cell_{cell_id}') for f in list( ( self.data_path / f'cell_{cell_id}' / zip_file.name.split('.zip')[0] ).iterdir() ): origin_database_path = ( self.data_path / f'cell_{cell_id}' / f.name ) if origin_database_path.exists(): origin_database_path.unlink() f.rename(self.data_path / f'cell_{cell_id}' / f.name) shutil.rmtree( self.data_path / f'cell_{cell_id}' / zip_file.name.split('.zip')[0] ) Path(zip_file).unlink() self._rebuild_database() # download meta_table files logger.info(f'Load the meta_table `{source_name}` from remote store') meta_table_ids = merger.get_artifact_ids( art_list, type='meta_table', cell_id=0 ) merger.download(ids=meta_table_ids, download_folder='meta_table') if len(meta_table_ids) > 1: merger.merge_file( inputdir=restore_path / 'meta_table', outputdir=restore_path / 'meta_table', outputfilename=Path('meta.db'), ) self._meta_table.load(restore_path / 'meta_table' / 'meta.db') shutil.rmtree(restore_path / 'meta_table') # download model files logger.info(f'Load the model `{source_name}` from remote store') file_name = str(self.model_path.parent / f'{source_name}_model.zip') model_id = [ art['_id'] for art in art_list['data'] if 'model' in art['metaData']['type'] ] assert len(model_id) == 1 client.download_artifact( id=model_id[0], f=file_name, show_progress=True, ) shutil.unpack_archive(file_name, self.model_path.parent) self._reload_models() Path(file_name).unlink() shutil.rmtree(restore_path) @property def is_trained(self): if self._projector_codec and (not self._projector_codec.is_trained): return False if self._vq_codec and (not self._vq_codec.is_trained): return False if self._pq_codec and (not self._pq_codec.is_trained): return False return True def _reload_models(self): if self._projector_codec_path.exists(): self._projector_codec = ProjectorCodec.load(self._projector_codec_path) if self._vq_codec_path.exists(): self._vq_codec = VQCodec.load(self._vq_codec_path) if self._pq_codec_path.exists(): self._pq_codec = PQCodec.load(self._pq_codec_path) @property def use_smart_probing(self): return self._use_smart_probing @use_smart_probing.setter def use_smart_probing(self, value): assert type(value) is bool self._use_smart_probing = value @property def stat(self): """Get information on status of the indexer.""" return { 'total_docs': self.total_docs, 'index_size': self.index_size, 'n_cells': self.n_cells, 'n_dim': self.n_dim, 'n_components': self.n_components, 'metric': self.metric.name, 'is_trained': self.is_trained, } # @property # def smart_probing_temperature(self): # return self._smart_probing_temperature # # @smart_probing_temperature.setter # def smart_probing_temperature(self, value): # assert value > 0 # assert self.use_smart_probing, 'set use_smart_probing to True first' # self._smart_probing_temperature = value ================================================ FILE: annlite/math.py ================================================ from typing import Tuple import numpy as np def l2_normalize(x: 'np.ndarray', eps: float = np.finfo(np.float32).eps): """Scale input vectors individually to unit norm. :param x: The data to normalize :param eps: a small jitter to avoid divde by zero :return: Normalized input X """ norms = np.einsum('ij,ij->i', x, x) np.sqrt(norms, norms) constant_mask = norms < 10 * eps norms[constant_mask] = 1.0 return x / norms[:, np.newaxis] def cosine( x_mat: 'np.ndarray', y_mat: 'np.ndarray', eps: float = np.finfo(np.float32).eps ) -> 'np.ndarray': """Cosine distance between each row in x_mat and each row in y_mat. :param x_mat: np.ndarray with ndim=2 :param y_mat: np.ndarray with ndim=2 :param eps: a small jitter to avoid divde by zero :return: np.ndarray with ndim=2 """ return 1 - np.clip( (np.dot(x_mat, y_mat.T) + eps) / ( np.outer(np.linalg.norm(x_mat, axis=1), np.linalg.norm(y_mat, axis=1)) + eps ), -1, 1, ) def sqeuclidean(x_mat: 'np.ndarray', y_mat: 'np.ndarray') -> 'np.ndarray': """Squared Euclidean distance between each row in x_mat and each row in y_mat. :param x_mat: np.ndarray with ndim=2 :param y_mat: np.ndarray with ndim=2 :return: np.ndarray with ndim=2 """ return ( np.sum(y_mat**2, axis=1) + np.sum(x_mat**2, axis=1)[:, np.newaxis] - 2 * np.dot(x_mat, y_mat.T) ) def euclidean(x_mat: 'np.ndarray', y_mat: 'np.ndarray') -> 'np.ndarray': """Euclidean distance between each row in x_mat and each row in y_mat. :param x_mat: scipy.sparse like array with ndim=2 :param y_mat: scipy.sparse like array with ndim=2 :return: np.ndarray with ndim=2 """ return np.sqrt(sqeuclidean(x_mat, y_mat)) def pdist( x_mat: 'np.ndarray', metric: str, ) -> 'np.ndarray': """Computes Pairwise distances between observations in n-dimensional space. :param x_mat: Union['np.ndarray','scipy.sparse.csr_matrix', 'scipy.sparse.coo_matrix'] of ndim 2 :param metric: string describing the metric type :return: np.ndarray of ndim 2 """ return cdist(x_mat, x_mat, metric) def cdist(x_mat: 'np.ndarray', y_mat: 'np.ndarray', metric: str) -> 'np.ndarray': """Computes the pairwise distance between each row of X and each row on Y according to `metric`. - Let `n_x = x_mat.shape[0]` - Let `n_y = y_mat.shape[0]` - Returns a matrix `dist` of shape `(n_x, n_y)` with `dist[i,j] = metric(x_mat[i], y_mat[j])`. :param x_mat: numpy or scipy array of ndim 2 :param y_mat: numpy or scipy array of ndim 2 :param metric: string describing the metric type :return: np.ndarray of ndim 2 """ dists = {'cosine': cosine, 'sqeuclidean': sqeuclidean, 'euclidean': euclidean}[ metric ](x_mat, y_mat) return dists def top_k( values: 'np.ndarray', k: int, descending: bool = False ) -> Tuple['np.ndarray', 'np.ndarray']: """Finds values and indices of the k largest entries for the last dimension. :param values: array of distances :param k: number of values to retrieve :param descending: find top k biggest values :return: indices and distances """ if descending: values = -values if k >= values.shape[1]: idx = values.argsort(axis=1)[:, :k] values = np.take_along_axis(values, idx, axis=1) else: idx_ps = values.argpartition(kth=k, axis=1)[:, :k] values = np.take_along_axis(values, idx_ps, axis=1) idx_fs = values.argsort(axis=1) idx = np.take_along_axis(idx_ps, idx_fs, axis=1) values = np.take_along_axis(values, idx_fs, axis=1) if descending: values = -values return values, idx ================================================ FILE: annlite/profile.py ================================================ import cProfile import pstats import random from functools import wraps random.seed(20) try: import builtins line_profile = builtins.profile except AttributeError: # No line profiler, provide a pass-through version def profile(func): return func line_profile = profile def time_profile( output_file=None, sort_by='cumulative', lines_to_print=None, strip_dirs=False ): """A time profiler decorator. Inspired by and modified the profile decorator of Giampaolo Rodola: https://github.com/ekhoda/profile_decorator Args: output_file: str or None. Default is None Path of the output file. If only name of the file is given, it's saved in the current directory. If it's None, the name of the decorated function is used. sort_by: str or SortKey enum or tuple/list of str/SortKey enum Sorting criteria for the Stats object. For a list of valid string and SortKey refer to: https://docs.python.org/3/library/profile.html#pstats.Stats.sort_stats lines_to_print: int or None Number of lines to print. Default (None) is for all the lines. This is useful in reducing the size of the printout, especially that sorting by 'cumulative', the time consuming operations are printed toward the top of the file. strip_dirs: bool Whether to remove the leading path info from file names. This is also useful in reducing the size of the printout Returns: Profile of the decorated function """ def inner(func): @wraps(func) def wrapper(*args, **kwargs): _output_file = output_file or func.__name__ + '.prof' pr = cProfile.Profile() pr.enable() retval = func(*args, **kwargs) pr.disable() pr.dump_stats(_output_file) with open(_output_file, 'w') as f: ps = pstats.Stats(pr, stream=f) if strip_dirs: ps.strip_dirs() if isinstance(sort_by, (tuple, list)): ps.sort_stats(*sort_by) else: ps.sort_stats(sort_by) ps.print_stats(lines_to_print) return retval return wrapper return inner ================================================ FILE: annlite/storage/__init__.py ================================================ ================================================ FILE: annlite/storage/base.py ================================================ import abc from typing import TYPE_CHECKING, List, Optional if TYPE_CHECKING: import numpy as np from ..enums import ExpandMode class Storage(abc.ABC): def __init__( self, initial_size: Optional[int] = None, expand_step_size: int = 10240, expand_mode: ExpandMode = ExpandMode.ADAPTIVE, ): if initial_size is None: initial_size = expand_step_size assert initial_size >= 0 assert expand_step_size > 0 self.initial_size = initial_size self.expand_step_size = expand_step_size self.expand_mode = expand_mode @property @abc.abstractmethod def capacity(self) -> int: ... @property @abc.abstractmethod def size(self): ... @abc.abstractmethod def clean(self): ... @abc.abstractmethod def add( self, data: 'np.ndarray', cells: 'np.ndarray', ids: List[str], doc_tags: Optional[List[dict]] = None, ): ... @abc.abstractmethod def delete(self, ids: List[str]): ... @abc.abstractmethod def update( self, data: 'np.ndarray', cells: 'np.ndarray', ids: List[str], doc_tags: Optional[List[dict]] = None, ): ... ================================================ FILE: annlite/storage/kv.py ================================================ import time import warnings from pathlib import Path from typing import Dict, List, Union from docarray import Document, DocumentArray from rocksdict import Options, Rdict, ReadOptions, WriteBatch, WriteOptions class DocStorage: """The backend storage engine of Documents""" def __init__( self, path: Union[str, Path], serialize_config: Dict = {}, create_if_missing: bool = True, **kwargs, ): self._path = str(path) self._serialize_config = serialize_config self._kwargs = kwargs self._init_db(create_if_missing=create_if_missing, **self._kwargs) def _init_db(self, create_if_missing: bool = True, **kwargs): opt = Options(raw_mode=True) opt.optimize_for_point_lookup(1024) opt.set_inplace_update_support(True) opt.set_allow_concurrent_memtable_write(False) # configure mem-table to a large value (256 MB) opt.set_write_buffer_size(0x10000000) # 256 MB file size opt.set_target_file_size_base(0x10000000) # # set to plain-table for better performance # opt.set_plain_table_factory(PlainTableFactoryOptions()) opt.create_if_missing(create_if_missing) self._db = Rdict(path=self._path, options=opt) # get the size of the database, if it is not created, set it to 0 self._size = len(list(self._db.keys())) self._is_closed = False def insert(self, docs: 'DocumentArray'): write_batch = WriteBatch(raw_mode=True) write_opt = WriteOptions() write_opt.sync = True batch_size = 0 for doc in docs: write_batch.put(doc.id.encode(), doc.to_bytes(**self._serialize_config)) batch_size += 1 self._db.write(write_batch, write_opt=write_opt) self._size += batch_size def update(self, docs: 'DocumentArray'): write_batch = WriteBatch(raw_mode=True) write_opt = WriteOptions() write_opt.sync = True for doc in docs: key = doc.id.encode() if key not in self._db: raise ValueError(f'The Doc ({doc.id}) does not exist in database!') write_batch.put(key, doc.to_bytes(**self._serialize_config)) self._db.write(write_batch, write_opt=write_opt) def delete(self, doc_ids: List[str]): write_batch = WriteBatch(raw_mode=True) write_opt = WriteOptions() write_opt.sync = True for doc_id in doc_ids: write_batch.delete(doc_id.encode()) self._db.write(write_batch, write_opt=write_opt) self._size -= len(doc_ids) def get(self, doc_ids: Union[str, list]) -> DocumentArray: docs = DocumentArray() if isinstance(doc_ids, str): doc_ids = [doc_ids] for doc_bytes in self._db[[k.encode() for k in doc_ids]]: if doc_bytes: docs.append(Document.from_bytes(doc_bytes, **self._serialize_config)) return docs def clear(self): if self._is_closed: warnings.warn( '`DocStorage` had been closed already, will skip this close operation.' ) else: self._db.close() self._db.destroy(self._path) # re-initialize the database for the next usage self._init_db(create_if_missing=True, **self._kwargs) def close(self): if self._is_closed: warnings.warn( '`DocStorage` had been closed already, will skip this close operation.' ) return try: self._db.flush(wait=True) self._db.close() except Exception as ex: if 'No such file or directory' not in str(ex): # this is a known bug, we can safely ignore it raise ex self._is_closed = True def __len__(self): return self._size @property def stat(self): return {'entries': len(self)} @property def size(self): return self.stat['entries'] @property def last_transaction_id(self): return self._db.latest_sequence_number() def batched_iterator(self, batch_size: int = 1, **kwargs) -> 'DocumentArray': count = 0 docs = DocumentArray() read_opt = ReadOptions() for value in self._db.values(read_opt=read_opt): doc = Document.from_bytes(value, **self._serialize_config) docs.append(doc) count += 1 if count == batch_size: yield docs count = 0 docs = DocumentArray() if count > 0: yield docs ================================================ FILE: annlite/storage/table.py ================================================ import datetime import sqlite3 import threading from pathlib import Path from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union import numpy as np if TYPE_CHECKING: from docarray import DocumentArray sqlite3.register_adapter(np.int64, lambda x: int(x)) sqlite3.register_adapter(np.int32, lambda x: int(x)) COLUMN_TYPE_MAPPING = { float: 'FLOAT', int: 'INTEGER', bool: 'INTEGER', str: 'TEXT', bytes.__class__: 'BLOB', bytes: 'BLOB', memoryview: 'BLOB', # datetime.datetime: 'TEXT', # datetime.date: 'TEXT', # datetime.time: 'TEXT', None.__class__: 'TEXT', # SQLite explicit types 'TEXT': 'TEXT', 'INTEGER': 'INTEGER', 'FLOAT': 'FLOAT', 'BLOB': 'BLOB', 'text': 'TEXT', 'integer': 'INTEGER', 'float': 'FLOAT', 'blob': 'BLOB', } # If numpy is available, add more types if np: COLUMN_TYPE_MAPPING.update( { np.int8: 'INTEGER', np.int16: 'INTEGER', np.int32: 'INTEGER', np.int64: 'INTEGER', np.uint8: 'INTEGER', np.uint16: 'INTEGER', np.uint32: 'INTEGER', np.uint64: 'INTEGER', np.float16: 'FLOAT', np.float32: 'FLOAT', np.float64: 'FLOAT', } ) def _converting(value: Any) -> str: if isinstance(value, bool): if value: return 1 else: return 0 return str(value) def time_now(): return datetime.datetime.utcnow() def _get_table_names( conn: 'sqlite3.Connection', fts4: bool = False, fts5: bool = False ) -> List[str]: """A list of string table names in this database.""" where = ["type = 'table'"] if fts4: where.append("sql like '%USING FTS4%'") if fts5: where.append("sql like '%USING FTS5%'") sql = 'select name from sqlite_master where {}'.format(' AND '.join(where)) return [r[0] for r in conn.execute(sql).fetchall()] class Table: def __init__( self, name: str, data_path: Optional[Union[Path, str]] = None, detect_types: int = 0, in_memory: bool = True, ): if in_memory: self._conn_name = ':memory:' else: if isinstance(data_path, str): data_path = Path(data_path) self._conn_name = data_path / f'{name}.db' self._name = name self.detect_types = detect_types self._conn = sqlite3.connect( self._conn_name, detect_types=detect_types, check_same_thread=False ) self._conn_lock = threading.Lock() def execute(self, sql: str, commit: bool = True): self._conn.execute(sql) if commit: self.commit() def execute_many(self, sql: str, parameters: List[Tuple], commit: bool = True): self._conn.executemany(sql, parameters) if commit: self.commit() def commit(self): self._conn.commit() def create_table(self): ... def drop_table(self): self._conn.execute(f'DROP table {self.name}') self._conn.commit() def clear(self): """Drop the table and create a new one""" self.drop_table() self.create_table() def load(self, data_file: Union[str, Path]): disk_db = sqlite3.connect(data_file, detect_types=self.detect_types) disk_db.backup(self._conn) disk_db.close() def dump(self, data_file: Union[str, Path]): backup_db = sqlite3.connect(data_file, detect_types=self.detect_types) self._conn.backup(backup_db) backup_db.close() def close(self): self._conn.close() @property def name(self): return self._name @property def schema(self): """SQL schema for this database""" result = [] for row in self._conn.execute( f'''PRAGMA table_info("{self.name}")''' ).fetchall(): result.append(', '.join([str(_) for _ in row])) return '\n'.join(result) class CellTable(Table): def __init__( self, name: str, columns: Optional[List[tuple]] = None, in_memory: bool = True, data_path: Optional[Path] = None, lazy_create: bool = False, ): super().__init__(name, data_path=data_path, in_memory=in_memory) self._columns = [] self._indexed_keys = set() if columns is not None: for name, dtype in columns: self.add_column(name, dtype, True) if not lazy_create: self.create_table() @property def columns(self) -> List[str]: return ['_id', '_doc_id'] + [c.split()[0] for c in self._columns] def existed(self): return self.name in _get_table_names(self._conn) def add_column(self, name: str, dtype: str, create_index: bool = True): self._columns.append(f'{name} {COLUMN_TYPE_MAPPING[dtype]}') if create_index: self._indexed_keys.add(name) def create_index(self, column: str, commit: bool = True): sql_statement = f'''CREATE INDEX idx_{column}_ ON {self.name}({column})''' self._conn.execute(sql_statement) if commit: self._conn.commit() def create_table(self): sql = f'''CREATE TABLE {self.name} (_id INTEGER PRIMARY KEY AUTOINCREMENT, _doc_id TEXT NOT NULL UNIQUE''' if len(self._columns) > 0: sql += ', ' + ', '.join(self._columns) sql += ')' self._conn.execute(sql) for name in self._indexed_keys: self.create_index(name, commit=False) self._conn.commit() def insert( self, docs: 'DocumentArray', commit: bool = True, ) -> List[int]: """Add a single record into the table. :param docs: The list of dict docs :param commit: If set, commit is applied """ sql_template = 'INSERT INTO {table}({columns}) VALUES ({placeholders});' column_names = self.columns[1:] columns = ', '.join(column_names) placeholders = ', '.join('?' for c in column_names) sql = sql_template.format( table=self.name, columns=columns, placeholders=placeholders ) values = [] docs_size = 0 for doc in docs: doc_value = tuple( [doc.id] + [ _converting(doc.tags[c]) if c in doc.tags else None for c in self.columns[2:] ] ) values.append(doc_value) docs_size += 1 with self._conn_lock: cursor = self._conn.cursor() if docs_size > 1: cursor.executemany(sql, values[:-1]) cursor.execute(sql, values[-1]) last_row_id = cursor.lastrowid row_ids = list(range(last_row_id - len(docs), last_row_id)) if commit: self._conn.commit() return row_ids def query( self, where_clause: str = '', where_params: Tuple = (), limit: int = -1, offset: int = 0, order_by: Optional[str] = None, ascending: bool = True, ) -> List[int]: """Query the records which matches the given conditions :param where_clause: where clause for query :param where_params: where parameters for query :param limit: limit the number of results :param offset: offset the number of results :param order_by: order the results by the given column :param ascending: order the results in ascending or descending order :return: offsets list of matched docs """ where_conds = [] where = None if where_clause: where_conds.append(where_clause) where = ' and '.join(where_conds) _order_by = f'{order_by or "_id"} {"ASC" if ascending else "DESC"}' _limit = f'LIMIT {limit}' if limit > 0 else '' _offset = f'OFFSET {offset}' if offset > 0 else '' if where: sql = f'SELECT _id from {self.name} WHERE {where} ORDER BY {_order_by} {_limit} {_offset}' else: sql = f'SELECT _id from {self.name} ORDER BY {_order_by} {_limit} {_offset}' params = tuple([_converting(p) for p in where_params]) # # EXPLAIN SQL query # for row in self._conn.execute('EXPLAIN QUERY PLAN ' + sql, params): # print(row) # Use `row_factor` # https://docs.python.org/3.6/library/sqlite3.html#sqlite3.Connection.row_factory def _offset_factory(_, record): return record[0] - 1 self._conn.row_factory = _offset_factory cursor = self._conn.cursor() try: if where: offsets = cursor.execute(sql, params).fetchall() else: offsets = cursor.execute(sql).fetchall() self._conn.row_factory = None return offsets if offsets else [] except Exception as e: self._conn.row_factory = None raise e def delete(self, doc_ids: List[str]): """Delete the docs :param doc_ids: The IDs of docs """ sql = f'DELETE from {self.name} WHERE _doc_id = ?' self._conn.executemany(sql, doc_ids) self._conn.commit() def get_docid_by_offset(self, offset: int): sql = f'SELECT _doc_id from {self.name} WHERE _id = ? LIMIT 1;' result = self._conn.execute(sql, (offset + 1,)).fetchone() if result: return result[0] return None def delete_by_offset(self, offset: int): """Delete the doc with specific offset :param offset: The offset of the doc """ sql = f'DELETE FROM {self.name} WHERE _id = ?' self._conn.execute(sql, (offset + 1,)) self._conn.commit() def exist(self, doc_id: str): sql = f'SELECT count(*) from {self.name} WHERE _doc_id = ?;' return self._conn.execute(sql, (doc_id,)).fetchone()[0] > 0 def count(self, where_clause: str = '', where_params: Tuple = ()): """Return the total number of records which match with the given conditions. :param where_clause: where clause for query :param where_params: where parameters for query :return: the total number of matched records """ if where_clause: sql = 'SELECT count(_id) from {table} WHERE {where} LIMIT 1;' where = where_clause sql = sql.format(table=self.name, where=where) params = tuple([_converting(p) for p in where_params]) # # EXPLAIN SQL query # for row in self._conn.execute('EXPLAIN QUERY PLAN ' + sql, params): # print(row) return self._conn.execute(sql, params).fetchone()[0] else: sql = f'SELECT count(_id) from {self.name};' result = self._conn.execute(sql).fetchone() if result[0]: return result[0] return 0 @property def size(self): return self.count() class MetaTable(Table): def __init__( self, name: str = 'meta', data_path: Optional[Path] = None, in_memory: bool = False, ): super().__init__( name, data_path=data_path, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES, in_memory=in_memory, ) self.create_table() def create_table(self): sql = f'''CREATE TABLE if not exists {self.name} (_doc_id TEXT NOT NULL PRIMARY KEY, cell_id INTEGER NOT NULL, offset INTEGER NOT NULL, time_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''' self._conn.execute(sql) self._conn.execute( f'CREATE INDEX if not exists idx_time_at_ ON {self.name}(time_at)' ) self._conn.commit() def iter_addresses( self, time_since: 'datetime.datetime' = datetime.datetime(2020, 2, 2, 0, 0) ): sql = f'SELECT _doc_id, cell_id, offset from {self.name} WHERE time_at >= ? ORDER BY time_at ASC;' cursor = self._conn.cursor() for doc_id, cell_id, offset in cursor.execute(sql, (time_since,)): yield doc_id, cell_id, offset def get_latest_commit(self): sql = f'SELECT _doc_id, cell_id, offset, time_at from {self.name} ORDER BY time_at DESC LIMIT 1;' cursor = self._conn.execute(sql) row = cursor.fetchone() return row def get_address(self, doc_id: str): sql = f'SELECT cell_id, offset from {self.name} WHERE _doc_id = ? LIMIT 1;' cursor = self._conn.execute(sql, (doc_id,)) row = cursor.fetchone() return (row[0], row[1]) if row else (None, None) def delete_address(self, doc_id: str, commit: bool = True): sql = f'DELETE from {self.name} WHERE _doc_id = ?' self._conn.execute(sql, (doc_id,)) if commit: self._conn.commit() def add_address(self, doc_id: str, cell_id: int, offset: int, commit: bool = True): sql = f'INSERT OR REPLACE INTO {self.name}(_doc_id, cell_id, offset, time_at) VALUES (?, ?, ?, ?);' self._conn.execute( sql, (doc_id, cell_id, offset, time_now()), ) if commit: self._conn.commit() def bulk_add_address( self, doc_ids: List[str], cell_ids: Union[List[int], np.ndarray], offsets: Union[List[int], np.ndarray], commit: bool = True, ): sql = f'INSERT OR REPLACE INTO {self.name}(_doc_id, cell_id, offset, time_at) VALUES (?, ?, ?, ?);' self._conn.executemany( sql, [ (doc_id, cell_id, offset, time_now()) for doc_id, cell_id, offset in zip(doc_ids, cell_ids, offsets) ], ) if commit: self._conn.commit() ================================================ FILE: annlite/utils.py ================================================ import os import shutil import numpy as np from docarray import Document, DocumentArray def clean_workspace(): if os.path.exists('./data'): shutil.rmtree('./data') if os.path.exists('./workspace'): shutil.rmtree('./workspace') def docs_with_tags(N, D, probs, categories): all_docs = [] start_current = 0 for k, prob in enumerate(probs): n_current = int(N * prob) X = np.random.random((n_current, D)).astype(np.float32) docs = [ Document( embedding=X[i], id=f'{i+start_current}', tags={ 'category': categories[k], }, ) for i in range(n_current) ] all_docs.extend(docs) start_current += n_current return DocumentArray(all_docs) def _precision(predicted, relevant, eval_at): """ fraction of retrieved documents that are relevant to the query """ if eval_at == 0: return 0.0 predicted_at_k = predicted[:eval_at] n_predicted_and_relevant = len(set(predicted_at_k).intersection(set(relevant))) return n_predicted_and_relevant / len(predicted) def _recall(predicted, relevant, eval_at): """ fraction of the relevant documents that are successfully retrieved """ if eval_at == 0: return 0.0 predicted_at_k = predicted[:eval_at] n_predicted_and_relevant = len(set(predicted_at_k).intersection(set(relevant))) return n_predicted_and_relevant / len(relevant) def evaluate(predicts, relevants, top_k): recall = 0 precision = 0 for _predict, _relevant in zip(predicts, relevants): _predict = np.array([int(x) for x in _predict]) recall += _recall(_predict, _relevant, top_k) precision += _precision(_predict, _relevant, top_k) return recall / len(predicts), precision / len(predicts) ================================================ FILE: benchmarks/filtering_bench.py ================================================ import os import shutil import tempfile import numpy as np from jina import Document, DocumentArray from jina.logging.profile import TimeContext from annlite import AnnLite n_index = [10_000, 100_000, 500_000, 1_000_000] n_query = [1, 8, 64] D = 768 R = 5 B = 5000 n_cells = 1 probs = [[0.20, 0.30, 0.50], [0.05, 0.15, 0.80]] categories = ['comic', 'movie', 'audiobook'] def docs_with_tags(N, D, probs, categories): all_docs = [] for k, prob in enumerate(probs): n_current = int(N * prob) X = np.random.random((n_current, D)).astype(np.float32) docs = [ Document( embedding=X[i], tags={ 'category': categories[k], }, ) for i in range(n_current) ] all_docs.extend(docs) return DocumentArray(all_docs) results = [] for n_i in n_index: results_ni = [] for current_probs in probs: with tempfile.TemporaryDirectory() as tmpdir: columns = [('category', str)] idxer = AnnLite( D, initial_size=n_i, n_cells=n_cells, columns=columns, data_path=tmpdir, ) da = docs_with_tags(n_i, D, current_probs, categories) with TimeContext(f'indexing {n_i} docs') as t_i: for i, _batch in enumerate(da.batch(batch_size=B)): idxer.index(_batch) for cat, prob in zip(categories, current_probs): f = {'category': {'$eq': cat}} query_times = [] for n_q in n_query: qa = DocumentArray.empty(n_q) q_embs = np.random.random([n_q, D]).astype(np.float32) qa.embeddings = q_embs t_qs = [] for _ in range(R): with TimeContext(f'searching {n_q} docs') as t_q: idxer.search(qa, filter=f) t_qs.append(t_q.duration) query_times.append(np.mean(t_qs[1:])) print(f'\n\nprob={prob}, current_probs={current_probs}, n_i={n_i}\n\n') results_ni.append([n_i, int(100 * prob), t_i.duration] + query_times) results.append(results_ni) title = '| Stored data |% same filter| Indexing time | Query size=1 | Query size=8 | Query size=64|' print(title) print('|-----' * 6 + '|') for block in results: sorted_elements_in_block = np.argsort([b[1] for b in block]) for pos in sorted_elements_in_block: res = block[pos] print( ''.join( [f'| {x} ' for x in res[0:2]] + [f'| {x:.3f} ' for x in res[2:]] + ['|'] ) ) ================================================ FILE: benchmarks/hnsw_bench.py ================================================ import tempfile import time from datetime import date import numpy as np import pandas as pd from docarray import Document, DocumentArray from sklearn.datasets import make_blobs from sklearn.model_selection import train_test_split from annlite import AnnLite from annlite.math import cdist from annlite.math import top_k as _top_k def _precision(predicted, relevant, eval_at): """ fraction of retrieved documents that are relevant to the query """ if eval_at == 0: return 0.0 predicted_at_k = predicted[:eval_at] n_predicted_and_relevant = len(set(predicted_at_k).intersection(set(relevant))) return n_predicted_and_relevant / len(predicted) def _recall(predicted, relevant, eval_at): """ fraction of the relevant documents that are successfully retrieved """ if eval_at == 0: return 0.0 predicted_at_k = predicted[:eval_at] n_predicted_and_relevant = len(set(predicted_at_k).intersection(set(relevant))) return n_predicted_and_relevant / len(relevant) def evaluate(predicts, relevants, eval_at): recall = 0 precision = 0 for _predict, _relevant in zip(predicts, relevants): _predict = np.array([int(x) for x in _predict]) recall += _recall(_predict, _relevant, top_k) precision += _precision(_predict, _relevant, top_k) return recall / len(predicts), precision / len(predicts) # N = 100_000 # number of data points Nt = 125_000 Nq = 1 D = 128 # dimentionality / number of features top_k = 10 n_cells = 64 n_subvectors = 64 n_queries = 1000 # 2,000 128-dim vectors for training np.random.seed(123) Xtr, Xte = train_test_split( make_blobs(n_samples=Nt, n_features=D)[0].astype(np.float32), test_size=20 ) print(f'Xtr: {Xtr.shape} vs Xte: {Xte.shape}') def get_documents(nr=10, index_start=0, embeddings=None): for i in range(index_start, nr + index_start): d = Document() d.id = f'{i}' # to test it supports non-int ids d.embedding = embeddings[i - index_start] yield d precision_per_query = [] recall_per_query = [] results = [] for n_cells in [1, 8, 16, 32, 64, 128]: with tempfile.TemporaryDirectory() as tmpdir: pq = AnnLite( D, metric='euclidean', n_cells=n_cells, data_path=tmpdir, ) t0 = time.time() pq.train(Xtr[:20480]) train_time = abs(time.time() - t0) t0 = time.time() pq.index(DocumentArray(get_documents(len(Xtr), embeddings=Xtr))) index_time = abs(t0 - time.time()) dists = cdist(Xte, Xtr, metric='euclidean') true_dists, true_ids = _top_k(dists, top_k, descending=False) t0 = time.time() docs = DocumentArray(get_documents(len(Xte), embeddings=Xte)) pq.search(docs, limit=top_k) query_time = abs(t0 - time.time()) pq_ids = [] for doc in docs: pq_ids.append([m.id for m in doc.matches]) recall, precision = evaluate(pq_ids, true_ids, top_k) results_dict = { 'precision': precision, 'recall': recall, 'train_time': train_time, 'index_time': index_time, 'query_time': query_time, 'query_qps': len(Xte) / query_time, 'index_qps': len(Xtr) / index_time, 'indexer_hyperparams': {'n_cells': n_cells}, } print(results_dict) results.append(results_dict) pq.clear() pq.close() today = date.today() results_df = pd.DataFrame(results) results_df.sort_values('recall', ascending=False) results_df.to_csv(f'bench-results-{today.strftime("%b-%d-%Y")}.csv') ================================================ FILE: bindings/hnsw_bindings.cpp ================================================ #include "hnswlib.h" #include #include #include #include #include #include #include #include #include #include #include namespace py = pybind11; using namespace pybind11::literals; // needed to bring in _a literal /* * replacement for the openmp '#pragma omp parallel for' directive * only handles a subset of functionality (no reductions etc) * Process ids from start (inclusive) to end (EXCLUSIVE) * * The method is borrowed from nmslib */ template inline void ParallelFor(size_t start, size_t end, size_t numThreads, Function fn) { if (numThreads <= 0) { numThreads = std::thread::hardware_concurrency(); } if (numThreads == 1) { for (size_t id = start; id < end; id++) { fn(id, 0); } } else { std::vector threads; std::atomic current(start); // keep track of exceptions in threads // https://stackoverflow.com/a/32428427/1713196 std::exception_ptr lastException = nullptr; std::mutex lastExceptMutex; for (size_t threadId = 0; threadId < numThreads; ++threadId) { threads.push_back(std::thread([&, threadId] { while (true) { size_t id = current.fetch_add(1); if ((id >= end)) { break; } try { fn(id, threadId); } catch (...) { std::unique_lock lastExcepLock(lastExceptMutex); lastException = std::current_exception(); /* * This will work even when current is the largest value that * size_t can fit, because fetch_add returns the previous value * before the increment (what will result in overflow * and produce 0 instead of current + 1). */ current = end; break; } } })); } for (auto &thread : threads) { thread.join(); } if (lastException) { std::rethrow_exception(lastException); } } } inline void assert_true(bool expr, const std::string &msg) { if (expr == false) throw std::runtime_error("Unpickle Error: " + msg); return; } template class Index { public: Index(const std::string &space_name, const int dim) : space_name(space_name), dim(dim) { normalize = false; if (space_name == "l2") { l2space = new hnswlib::L2Space(dim); } else if (space_name == "ip") { l2space = new hnswlib::InnerProductSpace(dim); } else if (space_name == "cosine") { l2space = new hnswlib::InnerProductSpace(dim); normalize = true; } else { throw new std::runtime_error( "Space name must be one of l2, ip, or cosine."); } appr_alg = NULL; ep_added = true; index_inited = false; num_threads_default = std::thread::hardware_concurrency(); default_ef = 10; pq_enable = false; pq_n_clusters = -1; pq_n_subvectors = -1; pq_d_subvector = -1; pq_codec = py::none(); } static const int ser_version = 1; // serialization version std::string space_name; int dim; size_t seed; size_t default_ef; bool index_inited; bool ep_added; bool normalize; size_t maxElements, M, efConstruction; int num_threads_default; hnswlib::labeltype cur_l; hnswlib::HierarchicalNSW *appr_alg; hnswlib::SpaceInterface *l2space; // quantization setting bool pq_enable; size_t pq_n_subvectors; size_t pq_n_clusters; size_t pq_d_subvector; py::object pq_codec; ~Index() { delete l2space; if (appr_alg) delete appr_alg; // WARNING: will python release the pq_codec? } void init_new_index(const size_t maxElements, const size_t M, const size_t efConstruction, const size_t random_seed, const py::object &pq_codec) { if (appr_alg) { throw new std::runtime_error("The index is already initiated."); } if (!pq_codec.is_none()) { _loadPQ(pq_codec); } this->cur_l = 0; this->appr_alg = new hnswlib::HierarchicalNSW( l2space, maxElements, M, efConstruction, random_seed); this->index_inited = true; this->ep_added = false; this->appr_alg->ef_ = default_ef; this->maxElements = maxElements; this->M = M; this->efConstruction = efConstruction; this->seed = random_seed; } void loadPQ(const py::object &pq_codec) { if (this->appr_alg) { delete this->appr_alg; } if (pq_codec.is_none()) { throw new std::runtime_error("Passed PQ class is none"); } _loadPQ(pq_codec); this->cur_l = 0; this->ep_added = false; this->appr_alg = new hnswlib::HierarchicalNSW( l2space, maxElements, M, efConstruction, seed); this->appr_alg->ef_ = default_ef; } void set_ef(size_t ef) { default_ef = ef; if (appr_alg) appr_alg->ef_ = ef; } void set_num_threads(int num_threads) { this->num_threads_default = num_threads; } void saveIndex(const std::string &path_to_index) { appr_alg->saveIndex(path_to_index); } void loadIndex(const std::string &path_to_index, size_t max_elements) { if (appr_alg) { std::cerr << "Warning: Calling load_index for an already inited index. " "Old index is being deallocated."; delete appr_alg; } appr_alg = new hnswlib::HierarchicalNSW(l2space, path_to_index, false, max_elements); cur_l = appr_alg->cur_element_count; index_inited = true; } void normalize_vector(float *data, float *norm_array) { float norm = 0.0f; for (int i = 0; i < dim; i++) norm += data[i] * data[i]; norm = 1.0f / (sqrtf(norm) + 1e-30f); for (int i = 0; i < dim; i++) norm_array[i] = data[i] * norm; } template void addRows_(int dim, int num_threads, const py::object &ids_, const py::object &input, const py::object dtables) { py::array_t items(input); auto buffer = items.request(); size_t rows, features; rows = buffer.shape[0]; features = buffer.shape[1]; if (!dtables.is_none()) { hnswlib::pq_local_data_t pq_param; py::array_t dtable_items(dtables); auto dtable_buffer = dtable_items.request(); pq_param.data = (float *)dtable_buffer.ptr; pq_param.batch_len = rows; this->l2space->attach_local_data(&pq_param); } if (features != dim) throw std::runtime_error("wrong dimensionality of the vectors"); if (num_threads <= 0) num_threads = num_threads_default; // avoid using threads when the number of searches is small: if (rows <= num_threads * 4) { num_threads = 1; } std::vector ids; if (!ids_.is_none()) { py::array_t items( ids_); auto ids_numpy = items.request(); if (ids_numpy.ndim == 1 && ids_numpy.shape[0] == rows) { std::vector ids1(ids_numpy.shape[0]); for (size_t i = 0; i < ids1.size(); i++) { ids1[i] = items.data()[i]; } ids.swap(ids1); } else if (ids_numpy.ndim == 0 && rows == 1) { ids.push_back(*items.data()); } else throw std::runtime_error("wrong dimensionality of the labels"); } { int start = 0; if (!ep_added) { size_t id = ids.size() ? ids.at(0) : (cur_l); appr_alg->addPoint((void *)items.data(0), (size_t)id, 0); start = 1; ep_added = true; } py::gil_scoped_release l; ParallelFor(start, rows, num_threads, [&](size_t row, size_t threadId) { size_t id = ids.size() ? ids.at(row) : (cur_l + row); appr_alg->addPoint((void *)items.data(row), (size_t)id, row); }); cur_l += rows; } if (!dtables.is_none()) { this->l2space->detach_local_data(); } } void addItems(const py::object &input, py::object ids_ = py::none(), int num_threads = -1, py::object dtables = py::none()) { // move dim check and normalization to python if (!pq_enable) { addRows_(this->dim, num_threads, ids_, input, dtables); } else { if (pq_n_clusters <= (UINT8_MAX + 1)) { addRows_(pq_n_subvectors, num_threads, ids_, input, dtables); } else if (pq_n_clusters <= (UINT16_MAX + 1)) { addRows_(pq_n_subvectors, num_threads, ids_, input, dtables); } else if (pq_n_clusters <= (UINT32_MAX + 1)) { addRows_(pq_n_subvectors, num_threads, ids_, input, dtables); } } } template py::object knnQuery_return_numpy_(size_t k, int num_threads, const py::object &input, const py::object dtables) { py::array_t items(input); auto buffer = items.request(); hnswlib::labeltype *data_numpy_l; dist_t *data_numpy_d; size_t rows, features; { py::gil_scoped_release l; rows = buffer.shape[0]; features = buffer.shape[1]; if (!dtables.is_none()) { hnswlib::pq_local_data_t pq_param; py::array_t dtable_items(dtables); auto dtable_buffer = dtable_items.request(); pq_param.data = (float *)dtable_buffer.ptr; pq_param.batch_len = rows; this->l2space->attach_local_data(&pq_param); } if (num_threads <= 0) num_threads = num_threads_default; // avoid using threads when the number of searches is small: if (rows <= num_threads * 4) { num_threads = 1; } data_numpy_l = new hnswlib::labeltype[rows * k]; data_numpy_d = new dist_t[rows * k]; ParallelFor(0, rows, num_threads, [&](size_t row, size_t threadId) { std::priority_queue> result = appr_alg->searchKnn((void *)items.data(row), k, row); if (result.size() != k) throw std::runtime_error( "Cannot return the results in a contigious 2D array. Probably " "ef or M is too small"); for (int i = k - 1; i >= 0; i--) { auto &result_tuple = result.top(); data_numpy_d[row * k + i] = result_tuple.first; data_numpy_l[row * k + i] = result_tuple.second; result.pop(); } }); } if (!dtables.is_none()) { this->l2space->detach_local_data(); } py::capsule free_when_done_l(data_numpy_l, [](void *f) { delete[] f; }); py::capsule free_when_done_d(data_numpy_d, [](void *f) { delete[] f; }); return py::make_tuple( py::array_t( {rows, k}, // shape {k * sizeof(hnswlib::labeltype), sizeof( hnswlib::labeltype)}, // C-style contiguous strides for double data_numpy_l, // the data pointer free_when_done_l), py::array_t( {rows, k}, // shape {k * sizeof(dist_t), sizeof(dist_t)}, // C-style contiguous strides for double data_numpy_d, // the data pointer free_when_done_d)); } py::object knnQuery_return_numpy(const py::object &input, size_t k = 1, int num_threads = -1, py::object dtables = py::none()) { // move dim check and normalization to python if (!pq_enable) { return knnQuery_return_numpy_(k, num_threads, input, dtables); } else { if (pq_n_clusters <= (UINT8_MAX + 1)) { return knnQuery_return_numpy_(k, num_threads, input, dtables); } else if (pq_n_clusters <= (UINT16_MAX + 1)) { return knnQuery_return_numpy_(k, num_threads, input, dtables); } else if (pq_n_clusters <= (UINT32_MAX + 1)) { return knnQuery_return_numpy_(k, num_threads, input, dtables); } } } template py::object knnQuery_with_filter_(size_t k, int num_threads, const py::object &candidate_ids_, const py::object &input, const py::object dtables) { py::array_t items(input); auto buffer = items.request(); hnswlib::labeltype *data_numpy_l; dist_t *data_numpy_d; if (num_threads <= 0) num_threads = num_threads_default; size_t rows; size_t features; rows = buffer.shape[0]; features = buffer.shape[1]; if (!dtables.is_none()) { hnswlib::pq_local_data_t pq_param; py::array_t dtable_items(dtables); auto dtable_buffer = dtable_items.request(); pq_param.data = (float *)dtable_buffer.ptr; pq_param.batch_len = rows; this->l2space->attach_local_data(&pq_param); } // avoid using threads when the number of searches is small: if (rows <= num_threads * 4) { num_threads = 1; } // FuseFilter constructing binary_fuse16_t filter(appr_alg->max_elements_); if (!candidate_ids_.is_none()) { py::array_t items( candidate_ids_); auto ids_numpy = items.request(); if (ids_numpy.ndim == 1) { const size_t size = ids_numpy.shape[0]; std::vector big_set; big_set.reserve(size); for (size_t i = 0; i < size; i++) { big_set[i] = items.data()[i]; // we use contiguous values } if (!binary_fuse16_populate(big_set.data(), size, &filter)) { throw std::runtime_error("failure to populate the fuse filter"); } } else throw std::runtime_error("wrong dimensionality of the filter labels"); } { py::gil_scoped_release l; // would like to check the ownership of this data in more detail data_numpy_l = new hnswlib::labeltype[rows * k]; data_numpy_d = new dist_t[rows * k]; ParallelFor(0, rows, num_threads, [&](size_t row, size_t threadId) { std::priority_queue> result = appr_alg->searchKnnWithFilter((void *)items.data(row), &filter, k, row); if (result.size() != k) throw std::runtime_error( "Cannot return the results in a contigious 2D array. Probably " "ef or M is too small"); for (int i = k - 1; i >= 0; i--) { auto &result_tuple = result.top(); data_numpy_d[row * k + i] = result_tuple.first; data_numpy_l[row * k + i] = result_tuple.second; result.pop(); } }); } if (!dtables.is_none()) { this->l2space->detach_local_data(); } py::capsule free_when_done_l(data_numpy_l, [](void *f) { delete[] f; }); py::capsule free_when_done_d(data_numpy_d, [](void *f) { delete[] f; }); return py::make_tuple( py::array_t( {rows, k}, // shape {k * sizeof(hnswlib::labeltype), sizeof( hnswlib::labeltype)}, // C-style contiguous strides for double data_numpy_l, // the data pointer free_when_done_l), py::array_t( {rows, k}, // shape {k * sizeof(dist_t), sizeof(dist_t)}, // C-style contiguous strides for double data_numpy_d, // the data pointer free_when_done_d)); } py::object knnQuery_with_filter(py::object input, py::object candidate_ids_ = py::none(), size_t k = 1, int num_threads = -1, py::object dtables = py::none()) { // move dim check and normalization to python if (!pq_enable) { return knnQuery_with_filter_(k, num_threads, candidate_ids_, input, dtables); } else { if (pq_n_clusters <= (UINT8_MAX + 1)) { return knnQuery_with_filter_(k, num_threads, candidate_ids_, input, dtables); } else if (pq_n_clusters <= (UINT16_MAX + 1)) { return knnQuery_with_filter_(k, num_threads, candidate_ids_, input, dtables); } else if (pq_n_clusters <= (UINT32_MAX + 1)) { return knnQuery_with_filter_(k, num_threads, candidate_ids_, input, dtables); } } } std::vector> getDataReturnList(py::object ids_ = py::none()) { std::vector ids; if (!ids_.is_none()) { py::array_t items( ids_); auto ids_numpy = items.request(); std::vector ids1(ids_numpy.shape[0]); for (size_t i = 0; i < ids1.size(); i++) { ids1[i] = items.data()[i]; } ids.swap(ids1); } std::vector> data; for (auto id : ids) { data.push_back(appr_alg->template getDataByLabel(id)); } return data; } std::vector getIdsList() { std::vector ids; for (auto kv : appr_alg->label_lookup_) { ids.push_back(kv.first); } return ids; } py::dict getAnnData() const { /* WARNING: Index::getAnnData is not thread-safe with Index::addItems */ std::unique_lock templock(appr_alg->global); unsigned int level0_npy_size = appr_alg->cur_element_count * appr_alg->size_data_per_element_; unsigned int link_npy_size = 0; std::vector link_npy_offsets(appr_alg->cur_element_count); for (size_t i = 0; i < appr_alg->cur_element_count; i++) { unsigned int linkListSize = appr_alg->element_levels_[i] > 0 ? appr_alg->size_links_per_element_ * appr_alg->element_levels_[i] : 0; link_npy_offsets[i] = link_npy_size; if (linkListSize) link_npy_size += linkListSize; } char *data_level0_npy = (char *)malloc(level0_npy_size); char *link_list_npy = (char *)malloc(link_npy_size); int *element_levels_npy = (int *)malloc(appr_alg->element_levels_.size() * sizeof(int)); hnswlib::labeltype *label_lookup_key_npy = (hnswlib::labeltype *)malloc( appr_alg->label_lookup_.size() * sizeof(hnswlib::labeltype)); hnswlib::tableint *label_lookup_val_npy = (hnswlib::tableint *)malloc( appr_alg->label_lookup_.size() * sizeof(hnswlib::tableint)); memset(label_lookup_key_npy, -1, appr_alg->label_lookup_.size() * sizeof(hnswlib::labeltype)); memset(label_lookup_val_npy, -1, appr_alg->label_lookup_.size() * sizeof(hnswlib::tableint)); size_t idx = 0; for (auto it = appr_alg->label_lookup_.begin(); it != appr_alg->label_lookup_.end(); ++it) { label_lookup_key_npy[idx] = it->first; label_lookup_val_npy[idx] = it->second; idx++; } memset(link_list_npy, 0, link_npy_size); memcpy(data_level0_npy, appr_alg->data_level0_memory_, level0_npy_size); memcpy(element_levels_npy, appr_alg->element_levels_.data(), appr_alg->element_levels_.size() * sizeof(int)); for (size_t i = 0; i < appr_alg->cur_element_count; i++) { unsigned int linkListSize = appr_alg->element_levels_[i] > 0 ? appr_alg->size_links_per_element_ * appr_alg->element_levels_[i] : 0; if (linkListSize) { memcpy(link_list_npy + link_npy_offsets[i], appr_alg->linkLists_[i], linkListSize); } } py::capsule free_when_done_l0(data_level0_npy, [](void *f) { delete[] f; }); py::capsule free_when_done_lvl(element_levels_npy, [](void *f) { delete[] f; }); py::capsule free_when_done_lb(label_lookup_key_npy, [](void *f) { delete[] f; }); py::capsule free_when_done_id(label_lookup_val_npy, [](void *f) { delete[] f; }); py::capsule free_when_done_ll(link_list_npy, [](void *f) { delete[] f; }); /* TODO: serialize state of random generators appr_alg->level_generator_ * and appr_alg->update_probability_generator_ */ /* for full reproducibility / to avoid re-initializing generators * inside Index::createFromParams */ return py::dict( "offset_level0"_a = appr_alg->offsetLevel0_, "max_elements"_a = appr_alg->max_elements_, "cur_element_count"_a = appr_alg->cur_element_count, "size_data_per_element"_a = appr_alg->size_data_per_element_, "label_offset"_a = appr_alg->label_offset_, "offset_data"_a = appr_alg->offsetData_, "max_level"_a = appr_alg->maxlevel_, "enterpoint_node"_a = appr_alg->enterpoint_node_, "max_M"_a = appr_alg->maxM_, "max_M0"_a = appr_alg->maxM0_, "M"_a = appr_alg->M_, "mult"_a = appr_alg->mult_, "ef_construction"_a = appr_alg->ef_construction_, "ef"_a = appr_alg->ef_, "has_deletions"_a = appr_alg->has_deletions_, "size_links_per_element"_a = appr_alg->size_links_per_element_, "label_lookup_external"_a = py::array_t( {appr_alg->label_lookup_.size()}, // shape {sizeof( hnswlib::labeltype)}, // C-style contiguous strides for double label_lookup_key_npy, // the data pointer free_when_done_lb), "label_lookup_internal"_a = py::array_t( {appr_alg->label_lookup_.size()}, // shape {sizeof( hnswlib::tableint)}, // C-style contiguous strides for double label_lookup_val_npy, // the data pointer free_when_done_id), "element_levels"_a = py::array_t( {appr_alg->element_levels_.size()}, // shape {sizeof(int)}, // C-style contiguous strides for double element_levels_npy, // the data pointer free_when_done_lvl), // linkLists_,element_levels_,data_level0_memory_ "data_level0"_a = py::array_t( {level0_npy_size}, // shape {sizeof(char)}, // C-style contiguous strides for double data_level0_npy, // the data pointer free_when_done_l0), "link_lists"_a = py::array_t( {link_npy_size}, // shape {sizeof(char)}, // C-style contiguous strides for double link_list_npy, // the data pointer free_when_done_ll) ); } py::dict getIndexParams() const { /* WARNING: Index::getAnnData is not thread-safe with Index::addItems */ auto params = py::dict( "ser_version"_a = py::int_(Index::ser_version), // serialization version "space"_a = space_name, "dim"_a = dim, "index_inited"_a = index_inited, "ep_added"_a = ep_added, "normalize"_a = normalize, "num_threads"_a = num_threads_default, "seed"_a = seed); if (index_inited == false) return py::dict(**params, "ef"_a = default_ef); auto ann_params = getAnnData(); return py::dict(**params, **ann_params); } static Index *createFromParams(const py::dict d) { // check serialization version assert_true(((int)py::int_(Index::ser_version)) >= d["ser_version"].cast(), "Invalid serialization version!"); auto space_name_ = d["space"].cast(); auto dim_ = d["dim"].cast(); auto index_inited_ = d["index_inited"].cast(); Index *new_index = new Index(space_name_, dim_); /* TODO: deserialize state of random generators into * new_index->level_generator_ and new_index->update_probability_generator_ */ /* for full reproducibility / state of generators is serialized * inside Index::getIndexParams */ new_index->seed = d["seed"].cast(); if (index_inited_) { new_index->appr_alg = new hnswlib::HierarchicalNSW( new_index->l2space, d["max_elements"].cast(), d["M"].cast(), d["ef_construction"].cast(), new_index->seed); new_index->cur_l = d["cur_element_count"].cast(); } new_index->index_inited = index_inited_; new_index->ep_added = d["ep_added"].cast(); new_index->num_threads_default = d["num_threads"].cast(); new_index->default_ef = d["ef"].cast(); if (index_inited_) new_index->setAnnData(d); return new_index; } static Index *createFromIndex(const Index &index) { return createFromParams(index.getIndexParams()); } void setAnnData(const py::dict d) { /* WARNING: Index::setAnnData is not thread-safe with Index::addItems */ std::unique_lock templock(appr_alg->global); assert_true(appr_alg->offsetLevel0_ == d["offset_level0"].cast(), "Invalid value of offsetLevel0_ "); assert_true(appr_alg->max_elements_ == d["max_elements"].cast(), "Invalid value of max_elements_ "); appr_alg->cur_element_count = d["cur_element_count"].cast(); assert_true(appr_alg->size_data_per_element_ == d["size_data_per_element"].cast(), "Invalid value of size_data_per_element_ "); assert_true(appr_alg->label_offset_ == d["label_offset"].cast(), "Invalid value of label_offset_ "); assert_true(appr_alg->offsetData_ == d["offset_data"].cast(), "Invalid value of offsetData_ "); appr_alg->maxlevel_ = d["max_level"].cast(); appr_alg->enterpoint_node_ = d["enterpoint_node"].cast(); assert_true(appr_alg->maxM_ == d["max_M"].cast(), "Invalid value of maxM_ "); assert_true(appr_alg->maxM0_ == d["max_M0"].cast(), "Invalid value of maxM0_ "); assert_true(appr_alg->M_ == d["M"].cast(), "Invalid value of M_ "); assert_true(appr_alg->mult_ == d["mult"].cast(), "Invalid value of mult_ "); assert_true(appr_alg->ef_construction_ == d["ef_construction"].cast(), "Invalid value of ef_construction_ "); appr_alg->ef_ = d["ef"].cast(); appr_alg->has_deletions_ = d["has_deletions"].cast(); assert_true(appr_alg->size_links_per_element_ == d["size_links_per_element"].cast(), "Invalid value of size_links_per_element_ "); auto label_lookup_key_npy = d["label_lookup_external"] .cast>(); auto label_lookup_val_npy = d["label_lookup_internal"] .cast>(); auto element_levels_npy = d["element_levels"] .cast< py::array_t>(); auto data_level0_npy = d["data_level0"] .cast< py::array_t>(); auto link_list_npy = d["link_lists"] .cast< py::array_t>(); for (size_t i = 0; i < appr_alg->cur_element_count; i++) { if (label_lookup_val_npy.data()[i] < 0) { throw std::runtime_error("internal id cannot be negative!"); } else { appr_alg->label_lookup_.insert(std::make_pair( label_lookup_key_npy.data()[i], label_lookup_val_npy.data()[i])); } } memcpy(appr_alg->element_levels_.data(), element_levels_npy.data(), element_levels_npy.nbytes()); unsigned int link_npy_size = 0; std::vector link_npy_offsets(appr_alg->cur_element_count); for (size_t i = 0; i < appr_alg->cur_element_count; i++) { unsigned int linkListSize = appr_alg->element_levels_[i] > 0 ? appr_alg->size_links_per_element_ * appr_alg->element_levels_[i] : 0; link_npy_offsets[i] = link_npy_size; if (linkListSize) link_npy_size += linkListSize; } memcpy(appr_alg->data_level0_memory_, data_level0_npy.data(), data_level0_npy.nbytes()); for (size_t i = 0; i < appr_alg->max_elements_; i++) { unsigned int linkListSize = appr_alg->element_levels_[i] > 0 ? appr_alg->size_links_per_element_ * appr_alg->element_levels_[i] : 0; if (linkListSize == 0) { appr_alg->linkLists_[i] = nullptr; } else { appr_alg->linkLists_[i] = (char *)malloc(linkListSize); if (appr_alg->linkLists_[i] == nullptr) throw std::runtime_error( "Not enough memory: loadIndex failed to allocate linklist"); memcpy(appr_alg->linkLists_[i], link_list_npy.data() + link_npy_offsets[i], linkListSize); } } } void markDeleted(size_t label) { appr_alg->markDelete(label); } void resizeIndex(size_t new_size) { appr_alg->resizeIndex(new_size); } size_t getMaxElements() const { return appr_alg->max_elements_; } size_t getCurrentCount() const { return appr_alg->cur_element_count; } void _loadPQ(const py::object &pq_abstract) { int exist_attr = 1, attr_correct = 1; PyObject *pq_raw_ptr = pq_abstract.ptr(); exist_attr *= PyObject_HasAttrString(pq_raw_ptr, "encode"); exist_attr *= PyObject_HasAttrString(pq_raw_ptr, "get_codebook"); exist_attr *= PyObject_HasAttrString(pq_raw_ptr, "get_subspace_splitting"); if (exist_attr <= 0) { throw py::index_error( "PQ class should at least have the following attributes:\n" "(encode, get_codebook, get_subspace_splitting)"); } attr_correct *= PyMethod_Check(PyObject_GetAttrString(pq_raw_ptr, "encode")); attr_correct *= PyMethod_Check(PyObject_GetAttrString(pq_raw_ptr, "get_codebook")); attr_correct *= PyMethod_Check( PyObject_GetAttrString(pq_raw_ptr, "get_subspace_splitting")); if (attr_correct <= 0) { throw py::attribute_error( "PQ class have at least one of the following attributes' type " "INCORRECT:\n(encode: ,\n codebook: " ",\n get_subspace_splitting: )"); } this->pq_enable = true; this->pq_codec = pq_abstract; py::tuple subspaces_param = pq_abstract.attr("get_subspace_splitting")(); this->pq_n_subvectors = py::cast(subspaces_param[0]); this->pq_n_clusters = py::cast(subspaces_param[1]); this->pq_d_subvector = py::cast(subspaces_param[2]); size_t pq_total_dims = (this->pq_n_subvectors * this->pq_d_subvector); if (this->dim != pq_total_dims) { throw py::value_error( "Initialization Error, expect HNSW.dim == " "PQ.n_subvector*PQ.d_subvector, but got:\n" "HNSW.dim =" + std::to_string(this->dim) + ", PQ.n_subvector*PQ.d_subvector=" + std::to_string(pq_total_dims)); } // reading codebook into float buffer py::array_t items( pq_abstract.attr("get_codebook")()); auto buffer = items.request(); if (buffer.ndim != 3 || buffer.shape[0] != pq_n_subvectors || buffer.shape[1] != pq_n_clusters || buffer.shape[2] != pq_d_subvector) { py::print("Expect the codebook with shape (", pq_n_subvectors, pq_n_clusters, pq_d_subvector, "seq"_a = ","); py::print(" but got shape ", buffer.shape); throw py::attribute_error( "PQ class returning the codebook with wrong dimension"); } // std::shared_ptr codebook_buffer((float *)buffer.ptr); float *codebook_buffer = (float *)buffer.ptr; if (l2space) { delete l2space; } if (pq_n_clusters <= (UINT8_MAX + 1)) { l2space = new hnswlib::PQ_Space(space_name, pq_n_subvectors, pq_n_clusters, pq_d_subvector, codebook_buffer); } else if (pq_n_clusters <= (UINT16_MAX + 1)) { l2space = new hnswlib::PQ_Space(space_name, pq_n_subvectors, pq_n_clusters, pq_d_subvector, codebook_buffer); } else if (pq_n_clusters <= (UINT32_MAX + 1)) { l2space = new hnswlib::PQ_Space(space_name, pq_n_subvectors, pq_n_clusters, pq_d_subvector, codebook_buffer); } else { throw py::value_error( "PQ clustering exceed the maximum, annlite set the maximum of " "clusters = " + std::to_string(UINT16_MAX + 1) + ", but got PQ.n_clusters=" + std::to_string(pq_n_clusters)); } } }; PYBIND11_PLUGIN(hnsw_bind) { py::module m("hnsw_bind"); py::class_>(m, "Index") .def(py::init(&Index::createFromParams), py::arg("params")) /* WARNING: Index::createFromIndex is not thread-safe with Index::addItems */ .def(py::init(&Index::createFromIndex), py::arg("index")) .def(py::init(), py::arg("space"), py::arg("dim")) .def("init_index", &Index::init_new_index, py::arg("max_elements"), py::arg("M") = 16, py::arg("ef_construction") = 200, py::arg("random_seed") = 100, py::arg("pq_codec") = py::none()) .def("knn_query", &Index::knnQuery_return_numpy, py::arg("data"), py::arg("k") = 1, py::arg("num_threads") = -1, py::arg("dtables") = py::none()) .def("knn_query_with_filter", &Index::knnQuery_with_filter, py::arg("data"), py::arg("filters") = py::none(), py::arg("k") = 1, py::arg("num_threads") = -1, py::arg("dtables") = py::none()) .def("add_items", &Index::addItems, py::arg("data"), py::arg("ids") = py::none(), py::arg("num_threads") = -1, py::arg("dtables") = py::none()) .def("get_items", &Index::getDataReturnList, py::arg("ids") = py::none()) .def("get_ids_list", &Index::getIdsList) .def("set_ef", &Index::set_ef, py::arg("ef")) .def("set_num_threads", &Index::set_num_threads, py::arg("num_threads")) .def("save_index", &Index::saveIndex, py::arg("path_to_index")) .def("load_index", &Index::loadIndex, py::arg("path_to_index"), py::arg("max_elements") = 0) .def("mark_deleted", &Index::markDeleted, py::arg("label")) .def("resize_index", &Index::resizeIndex, py::arg("new_size")) .def("get_max_elements", &Index::getMaxElements) .def("get_current_count", &Index::getCurrentCount) .def("loadPQ", &Index::loadPQ) .def_readonly("space", &Index::space_name) .def_readonly("dim", &Index::dim) .def_readonly("pq_enable", &Index::pq_enable) .def_readwrite("num_threads", &Index::num_threads_default) .def_property( "ef", [](const Index &index) { return index.index_inited ? index.appr_alg->ef_ : index.default_ef; }, [](Index &index, const size_t ef_) { index.default_ef = ef_; if (index.appr_alg) index.appr_alg->ef_ = ef_; }) .def_property_readonly("max_elements", [](const Index &index) { return index.index_inited ? index.appr_alg->max_elements_ : 0; }) .def_property_readonly("element_count", [](const Index &index) { return index.index_inited ? index.appr_alg->cur_element_count : 0; }) .def_property_readonly("ef_construction", [](const Index &index) { return index.index_inited ? index.appr_alg->ef_construction_ : 0; }) .def_property_readonly("M", [](const Index &index) { return index.index_inited ? index.appr_alg->M_ : 0; }) .def(py::pickle( [](const Index &ind) { // __getstate__ return py::make_tuple( ind.getIndexParams()); /* Return dict (wrapped in a tuple) that fully encodes state of the Index object */ }, [](py::tuple t) { // __setstate__ if (t.size() != 1) throw std::runtime_error("Invalid state!"); return Index::createFromParams(t[0].cast()); })) .def("__repr__", [](const Index &a) { return ""; }); return m.ptr(); } ================================================ FILE: bindings/pq_bindings.pyx ================================================ # distutils: language = c++ import numpy as np cimport cython from libc.stdint cimport ( int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t, ) from libcpp.vector cimport vector ctypedef fused any_int: uint8_t uint16_t uint32_t uint64_t int8_t int16_t int32_t int64_t @cython.boundscheck(False) @cython.wraparound(False) cdef inline dist_pqcode_to_codebook(long M,const float[:,:] adtable, any_int[:] pq_code): """Compute the distance between each codevector and the pq_code of a query. :param M: Number of sub-vectors in the original feature space. :param adtable: 2D Memoryview[float] containing precomputed Asymmetric Distances. :param pq_code: 1D Memoriview[any_int] containing a pq code. :return: Distance between pq code and query according to the Asymmetric Distance table. """ cdef: float dist = 0 int m for m in range(M): dist += adtable[m, pq_code[m]] return dist @cython.boundscheck(False) @cython.wraparound(False) cpdef dist_pqcodes_to_codebooks(const float[:,:] adtable, any_int[:,:] pq_codes): """ Compute the distance between each row in pq_codes and each codevector using a adtable. :param adtable: 2D Memoryview of precomputed Asymmetric Distances. :param pq_codes: 2D Memoryview of pq_codes. :return: List of Asymmetric Distances distances between pq_codes and the query. This function is equivalent to: ''' dists = np.zeros((N, )).astype(np.float32) for n in range(N): for m in range(M): dists[n] += self.adtable[m][codes[n][m]] ''' """ cdef: int m int N = pq_codes.shape[0] int M = pq_codes.shape[1] vector[float] dists for n in range(N): dists.push_back(dist_pqcode_to_codebook(M, adtable, pq_codes[n,:])) return dists @cython.boundscheck(False) @cython.wraparound(False) cpdef precompute_adc_table(const float[:] query, long d_subvector, long n_clusters, const float[:,:,:] codebooks): """ Compute the Asymmetric Distance Table between a query and a PQ space. :param query: Memoryview a query in the original feature space (not a pqcode). :param d_subvector: Number of dimensions in a subvector. :param n_clusters: Number of clusters per sub-space (number of prototypes per sub-space). :param codebooks: Memoryview containing the learned codevectors for each slice. This is a 3D view with (slice index, prototype index, vector values). :return: Memoryview with a 2D matrix containing the Asymmetric Distance Computation. This function is equivalent to ''' def numpy_adc_table(query, n_subvectors, n_clusters, d_subvector, codebooks): adtable = np.empty((n_subvectors, n_clusters), dtype=np.float32) for m in range(n_subvectors): query_sub = query[m * d_subvector: (m + 1) * d_subvector] adtable[m, :] = np.linalg.norm(codebooks[m] - query_sub, axis=1) ** 2 return adtable ''' But avoids generating views and calling numpy functions. """ cdef: int D = len(query) int M = int(D/d_subvector) int n_subvectors = int(D/d_subvector) int m, i, k, ind_prototype, j float[:, ::1] adtable = np.empty((M, n_clusters), dtype=np.float32) float[:] query_subvec = np.empty(d_subvector, dtype=np.float32) float[:] query_subcodeword = np.empty(d_subvector, dtype=np.float32) float dist_subprototype_to_subquery, coord_j for m in range(n_subvectors): # load m'th subquery i = 0 for k in range(m * d_subvector, (m + 1) * d_subvector): query_subvec[i] = query[k] i += 1 for ind_prototype in range(n_clusters): # load prototype ind_prototype for the m'th subspace for i in range(d_subvector): query_subcodeword[i] = codebooks[m, ind_prototype, i] # compute the distance between subprototype and subquery dist_subprototype_to_subquery = 0. for j in range(d_subvector): coord_j = query_subcodeword[j] - query_subvec[j] dist_subprototype_to_subquery += coord_j * coord_j adtable[m, ind_prototype] = dist_subprototype_to_subquery return adtable @cython.boundscheck(False) @cython.wraparound(False) cpdef batch_precompute_adc_table(const float[:, :] queries, long d_subvector, long n_clusters, const float[:,:,:] codebooks): """ Compute the Asymmetric Distance Table between a query and a PQ space. :param query: Memoryview a query in the original feature space (not a pqcode). :param d_subvector: Number of dimensions in a subvector. :param n_clusters: Number of clusters per sub-space (number of prototypes per sub-space). :param codebooks: Memoryview containing the learned codevectors for each slice. This is a 3D view with (slice index, prototype index, vector values). :return: Memoryview with a 2D matrix containing the Asymmetric Distance Computation. This function is equivalent to ''' def numpy_adc_table(query, n_subvectors, n_clusters, d_subvector, codebooks): adtable = np.empty((n_subvectors, n_clusters), dtype=np.float32) for m in range(n_subvectors): query_sub = query[m * d_subvector: (m + 1) * d_subvector] adtable[m, :] = np.linalg.norm(codebooks[m] - query_sub, axis=1) ** 2 return adtable ''' But avoids generating views and calling numpy functions. """ cdef: int N = queries.shape[0] int D = queries.shape[1] int n_subvectors = int(D/d_subvector) int m, i, k, ind_prototype, j float[:, :, :] adtable = np.empty((N, n_subvectors, n_clusters), dtype=np.float32) float[:] query_subvec = np.empty(d_subvector, dtype=np.float32) float[:] query_subcodeword = np.empty(d_subvector, dtype=np.float32) float dist_subprototype_to_subquery, coord_j for index in range(N): for m in range(n_subvectors): # load m'th subquery i = 0 for k in range(m * d_subvector, (m + 1) * d_subvector): query_subvec[i] = queries[index, k] i += 1 for ind_prototype in range(n_clusters): # load prototype ind_prototype for the m'th subspace for i in range(d_subvector): query_subcodeword[i] = codebooks[m, ind_prototype, i] # compute the distance between subprototype and subquery dist_subprototype_to_subquery = 0. for j in range(d_subvector): coord_j = query_subcodeword[j] - query_subvec[j] dist_subprototype_to_subquery += coord_j * coord_j adtable[index, m, ind_prototype] = dist_subprototype_to_subquery return adtable @cython.boundscheck(False) @cython.wraparound(False) cpdef batch_precompute_adc_table_ip(const float[:, :] queries, long d_subvector, long n_clusters, const float[:,:,:] codebooks): """ Compute the Asymmetric Distance Table between a query and a PQ space. :param query: Memoryview a query in the original feature space (not a pqcode). :param d_subvector: Number of dimensions in a subvector. :param n_clusters: Number of clusters per sub-space (number of prototypes per sub-space). :param codebooks: Memoryview containing the learned codevectors for each slice. This is a 3D view with (slice index, prototype index, vector values). :return: Memoryview with a 2D matrix containing the Asymmetric Distance Computation. This function is equivalent to ''' def numpy_adc_table(query, n_subvectors, n_clusters, d_subvector, codebooks): adtable = np.empty((n_subvectors, n_clusters), dtype=np.float32) for m in range(n_subvectors): query_sub = query[m * d_subvector: (m + 1) * d_subvector] adtable[m, :] = np.linalg.norm(codebooks[m] - query_sub, axis=1) ** 2 return adtable ''' But avoids generating views and calling numpy functions. """ cdef: int N = queries.shape[0] int D = queries.shape[1] int n_subvectors = int(D/d_subvector) int m, i, k, ind_prototype, j float[:, :, :] adtable = np.empty((N, n_subvectors, n_clusters), dtype=np.float32) float[:] query_subvec = np.empty(d_subvector, dtype=np.float32) float[:] query_subcodeword = np.empty(d_subvector, dtype=np.float32) float dist_subprototype_to_subquery for index in range(N): for m in range(n_subvectors): # load m'th subquery i = 0 for k in range(m * d_subvector, (m + 1) * d_subvector): query_subvec[i] = queries[index, k] i += 1 for ind_prototype in range(n_clusters): # load prototype ind_prototype for the m'th subspace for i in range(d_subvector): query_subcodeword[i] = codebooks[m, ind_prototype, i] # compute the distance between subprototype and subquery dist_subprototype_to_subquery = 0. for j in range(d_subvector): dist_subprototype_to_subquery += (query_subcodeword[j] * query_subvec[j]) adtable[index, m, ind_prototype] = dist_subprototype_to_subquery return adtable ================================================ FILE: examples/annlite_vs_simpleindexer.py ================================================ import os import shutil import tempfile import time import numpy as np import pandas as pd from jina import Document, DocumentArray, Flow from sklearn.datasets import make_blobs from sklearn.model_selection import train_test_split from executor.executor import AnnLiteIndexer Nq = 1 D = 128 top_k = 10 R = 5 n_cells = 64 n_subvectors = 64 n_queries = 1 BENCHMARK_SIMPLEINDEXER = False BENCHMARK_ANNLITE = True def _precision(predicted, relevant, eval_at): """ fraction of retrieved documents that are relevant to the query """ if eval_at == 0: return 0.0 predicted_at_k = predicted[:eval_at] n_predicted_and_relevant = len(set(predicted_at_k).intersection(set(relevant))) return n_predicted_and_relevant / len(predicted) def _recall(predicted, relevant, eval_at): """ fraction of the relevant documents that are successfully retrieved """ if eval_at == 0: return 0.0 predicted_at_k = predicted[:eval_at] n_predicted_and_relevant = len(set(predicted_at_k).intersection(set(relevant))) return n_predicted_and_relevant / len(relevant) def evaluate(predicts, relevants, eval_at): recall = 0 precision = 0 for _predict, _relevant in zip(predicts, relevants): _predict = np.array([int(x) for x in _predict]) recall += _recall(_predict, _relevant, top_k) precision += _precision(_predict, _relevant, top_k) return recall / len(predicts), precision / len(predicts) def create_data(n_examples, D): np.random.seed(123) Xtr, Xte = train_test_split( make_blobs(n_samples=n_examples, n_features=D)[0].astype(np.float32), test_size=1, ) return Xtr, Xte def create_data_online(n_examples, D, batch_size): np.random.seed(123) num = 0 while True: Xtr_batch = make_blobs(n_samples=batch_size, n_features=D)[0].astype(np.float32) yield DocumentArray([Document(embedding=x) for x in Xtr_batch]) num += batch_size if num + batch_size >= n_examples: break if num < n_examples: Xtr_batch = make_blobs(n_samples=n_examples - num, n_features=D)[0].astype( np.float32 ) yield DocumentArray([Document(embedding=x) for x in Xtr_batch]) def create_test_data(D, Nq): np.random.seed(123) Xte = make_blobs(n_samples=Nq, n_features=D)[0].astype(np.float32) return DocumentArray([Document(embedding=x) for x in Xte]) if BENCHMARK_SIMPLEINDEXER: ################ SimpleIndexer Benchmark BEGIN ################# n_datasets = [10001, 50001, 200001, 400001] times = [] for n_examples in n_datasets: time_taken = 0 Xtr, Xte = create_data(n_examples, D) with tempfile.TemporaryDirectory() as tmpdir: f = Flow().add( uses='jinahub://SimpleIndexer', uses_with={'match_args': {'metric': 'euclidean', 'limit': 10}}, workspace=tmpdir, ) docs = [Document(id=f'{i}', embedding=Xtr[i]) for i in range(len(Xtr))] with f: resp = f.post( on='/index', inputs=docs, ) with f: t0 = time.time() resp = f.post( on='/search', inputs=DocumentArray([Document(embedding=Xte[0])]), return_results=True, ) time_taken = time.time() - t0 times.append(time_taken) df = pd.DataFrame({'n_examples': n_datasets, 'times': times}) df.to_csv('simpleindexer.csv') print(df) ################ SimpleIndexer Benchmark END ################# if BENCHMARK_ANNLITE: ################ AnnLite Benchmark BEGIN ###################### n_datasets = [10_000, 100_000, 500_000, 1_000_000, 10_000_000] # n_datasets = [10_000, 100_000] n_queries = [1, 8, 64] batch_size = 4096 times = [] results = {} for n_examples in n_datasets: print(f'\n\nWorking with n_examples={n_examples}\n\n') time_taken = 0 with tempfile.TemporaryDirectory() as tmpdir: f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, 'limit': 10, }, workspace=tmpdir, ) docs = create_data_online(n_examples, D, batch_size) results_current = {} with f: time_taken = 0 for batch in docs: t0 = time.time() resp = f.post(on='/index', inputs=batch, request_size=10240) # This is done to avoid data creation time loaded in index time time_taken += time.time() - t0 results_current['index_time'] = time_taken times_per_n_query = [] with f: for n_query in n_queries: da_queries = create_test_data(D, n_query) t_qs = [] for _ in range(R): t0 = time.time() resp = f.post( on='/search', inputs=da_queries, return_results=True, ) time_taken = time.time() - t0 t_qs.append(time_taken) # remove warm-up times_per_n_query.append(np.mean(t_qs[1:])) results_current['query_times'] = times_per_n_query print(f'==> query_times: {times_per_n_query}') df = pd.DataFrame({'results': results_current}) df.to_csv(f'annlite_{n_examples}.csv') results[n_examples] = results_current df = pd.DataFrame(results) df.to_csv('annlite.csv') clean_workspace() ################ AnnLite Benchmark END ######################### ================================================ FILE: examples/filter_example.py ================================================ import os import random import shutil import numpy as np from jina import Document, DocumentArray from jina.logging.profile import TimeContext from annlite import AnnLite n_index = [10_000, 100_000, 500_000, 1_000_000] n_index = [100_000] n_query = [1, 8, 64] n_query = [1] D = 768 R = 5 B = 5000 n_cells = 1 # probs =[[0.20, 0.30, 0.50], # [0.05, 0.15, 0.80]] categories = ['comic', 'movie', 'audiobook'] def clean_workspace(): if os.path.exists('./data'): shutil.rmtree('./data') if os.path.exists('./workspace'): shutil.rmtree('./workspace') def docs_with_tags(N, D, probs, categories): all_docs = [] for k, prob in enumerate(probs): n_current = int(N * prob) X = np.random.random((n_current, D)).astype(np.float32) docs = [ Document( embedding=X[i], tags={'category': categories[k], 'x': random.randint(0, 5)}, ) for i in range(n_current) ] all_docs.extend(docs) return DocumentArray(all_docs) results = [] for n_i in n_index: clean_workspace() results_ni = [] current_probs = [0.05, 0.15, 0.80] columns = [('category', str)] idxer = AnnLite( D, initial_size=n_i, n_cells=n_cells, data_path='./workspace', columns=columns, ) da = docs_with_tags(n_i, D, current_probs, categories) with TimeContext(f'indexing {n_i} docs') as t_i: for i, _batch in enumerate(da.batch(batch_size=B)): idxer.index(_batch) for cat, prob in zip(categories, current_probs): f = {'category': {'$eq': cat}} query_times = [] for n_q in n_query: qa = DocumentArray.empty(n_q) q_embs = np.random.random([n_q, D]).astype(np.float32) qa.embeddings = q_embs t_qs = [] for _ in range(R): with TimeContext(f'searching {n_q} docs') as t_q: idxer.search(qa, filter=f) t_qs.append(t_q.duration) query_times.append(np.mean(t_qs[1:])) print(f'\n\nprob={prob}, current_probs={current_probs}, n_i={n_i}\n\n') results_ni.append([n_i, prob, t_i.duration] + query_times) results.append(results_ni) title = '| Stored data |% same filter| Indexing time | Query size=1 | Query size=8 | Query size=64|' print(title) print('|-----' * 6 + '|') for block in results: sorted_elements_in_block = np.argsort([b[1] for b in block]) for pos in sorted_elements_in_block: res = block[pos] print(''.join([f'| {x:.3f} ' for x in res] + ['|'])) ================================================ FILE: examples/hnsw_example.py ================================================ import random import tempfile import numpy as np from docarray import Document, DocumentArray from annlite import AnnLite N = 1000 # number of data points Nq = 5 Nt = 2000 D = 128 # dimensionality / number of features dirpath = tempfile.mkdtemp() with tempfile.TemporaryDirectory() as tmpdirname: index = AnnLite( D, columns=[('x', float)], data_path=tmpdirname, include_metadata=True ) X = np.random.random((N, D)).astype( np.float32 ) # 10,000 128-dim vectors to be indexed docs = DocumentArray( [ Document(id=f'{i}', embedding=X[i], tags={'x': random.random()}) for i in range(N) ] ) index.index(docs) X = np.random.random((Nq, D)).astype(np.float32) # a 128-dim query vector query = DocumentArray([Document(embedding=X[i]) for i in range(5)]) index.search(query, filter={'x': {'$lt': 0.2}}, limit=10, include_metadata=True) for m in query[0].matches: print(f'{m.scores["euclidean"].value} -> x={m.tags["x"]}') assert m.tags['x'] < 0.2 print(f'====') index.search(query, filter={'x': {'$gte': 0.9}}, limit=10, include_metadata=True) for m in query[0].matches: print(f'{m.scores["euclidean"].value} -> x={m.tags["x"]}') assert m.tags['x'] >= 0.9 # # print(f'{[m.scores["euclidean"].value for m in query[0].matches]}') # for i in range(len(query[0].matches) - 1): # assert ( # query[0].matches[i].scores['euclidean'].value # <= query[0].matches[i + 1].scores['euclidean'].value # ) ================================================ FILE: examples/pq_benchmark.py ================================================ import time from datetime import date import numpy as np import pandas as pd from docarray import Document, DocumentArray from sklearn.datasets import make_blobs from sklearn.model_selection import train_test_split from utils import evaluate from annlite import AnnLite from annlite.math import cdist from annlite.math import top_k as _top_k # N = 100_000 # number of data points Nt = 100_020 Nq = 1 D = 128 # dimentionality / number of features top_k = 10 n_cells = 64 n_subvectors = 64 n_queries = 1000 # 2,000 128-dim vectors for training np.random.seed(123) Xtr, Xte = train_test_split( make_blobs(n_samples=Nt, n_features=D)[0].astype(np.float32), test_size=20 ) print(f'Xtr: {Xtr.shape} vs Xte: {Xte.shape}') def get_documents(nr=10, index_start=0, embeddings=None): for i in range(index_start, nr + index_start): d = Document() d.id = f'{i}' # to test it supports non-int ids d.embedding = embeddings[i - index_start] yield d precision_per_query = [] recall_per_query = [] results = [] for n_cells in [1, 4, 8]: for n_subvectors in [64, 128]: pq = AnnLite(D, metric='euclidean', n_cells=n_cells, n_subvectors=n_subvectors) t0 = time.time() pq.train(Xtr[:20480]) train_time = abs(time.time() - t0) t0 = time.time() pq.index(DocumentArray(get_documents(len(Xtr), embeddings=Xtr))) index_time = abs(t0 - time.time()) dists = cdist(Xte, Xtr, metric='euclidean') true_dists, true_ids = _top_k(dists, top_k, descending=False) docs = DocumentArray(get_documents(len(Xte), embeddings=Xte)) t0 = time.time() pq.search(docs, limit=top_k) query_time = abs(t0 - time.time()) pq_ids = [] for doc in docs: pq_ids.append([m.id for m in doc.matches]) recall, precision = evaluate(pq_ids, true_ids, top_k) results_dict = { 'precision': precision, 'recall': recall, 'train_time': train_time, 'index_time': index_time, 'query_time': query_time, 'query_qps': len(Xte) / query_time, 'index_qps': len(Xtr) / index_time, 'indexer_hyperparams': {'n_cells': n_cells, 'n_subvectors': n_subvectors}, } print(results_dict) results.append(results_dict) pq.clear() pq.close() today = date.today() results_df = pd.DataFrame(results) results_df.sort_values('recall', ascending=False) results_df.to_csv(f'bench-results-{today.strftime("%b-%d-%Y")}.csv') ================================================ FILE: examples/pqlinearscann_benchmark_with_filtering.py ================================================ import numpy as np from docarray import Document, DocumentArray from docarray.math.distance import cdist from docarray.math.helper import top_k as _top_k from jina.logging.profile import TimeContext from utils import clean_workspace, docs_with_tags, evaluate from annlite import AnnLite n_index = [10_000, 100_000, 500_000, 1_000_000] n_query = [1, 8, 64] D = 768 R = 5 B = 100_000 n_cells = 1 probs = [[0.20, 0.30, 0.50], [0.05, 0.15, 0.80]] categories = ['comic', 'movie', 'audiobook'] top_k = 20 n_cells = 1 n_subvectors = D results = [] for n_i in n_index: results_ni = [] for current_probs in probs: clean_workspace() columns = [('category', str)] indexer = AnnLite( D, initial_size=n_i, n_subvectors=n_subvectors, n_cells=n_cells, metas={'workspace': './workspace'}, columns=columns, ) da = docs_with_tags(n_i, D, current_probs, categories) da_embeddings = da.embeddings with TimeContext(f'indexing {n_i} docs') as t_i: n_train_quantizer = min(n_i, 20_000) row_ids = np.random.choice(range(n_i), n_train_quantizer, replace=False) indexer.partial_train(da_embeddings[row_ids, :]) indexer.build_codebook() for i, _batch in enumerate(da.batch(batch_size=B)): indexer.index(_batch) for cat, prob in zip(categories, current_probs): f = {'category': {'$eq': cat}} indices_cat = ( np.array([t['category'] for t in da.get_attributes('tags')]) == cat ) ids_indices_cat = np.array( [d.id for d in da if d.tags['category'] == cat], dtype='int' ) da_embeddings_cat = da_embeddings[indices_cat, :] query_times = [] for n_q in n_query: qa = DocumentArray.empty(n_q) q_embs = np.random.random([n_q, D]).astype(np.float32) qa.embeddings = q_embs t_qs = [] for _ in range(R): with TimeContext(f'searching {n_q} docs') as t_q: indexer.search(qa, filter=f, limit=top_k) t_qs.append(t_q.duration) query_times.append(np.mean(t_qs[1:])) if n_q == 8: dists = cdist(q_embs, da_embeddings_cat, metric='euclidean') true_dists, true_local_ids = _top_k(dists, top_k, descending=False) # Note:x # `true_ids` are not really positions within the original data # but positions within the subset `da_embeddings_cat` # We need to go from these positions to the original positions true_ids = ids_indices_cat[true_local_ids] ids = [] for doc in qa: ids.append([m.id for m in doc.matches]) recall, precision = evaluate(ids, true_ids, top_k) print( f'\n\nprob={prob}, current_probs={current_probs}, n_i={n_i}, recall={recall}\n\n' ) results_ni.append([n_i, prob, t_i.duration] + query_times + [recall]) results.append(results_ni) title = '| Stored data |% same filter| Indexing time | Query size=1 | Query size=8 | Query size=64| Recall |' print(title) print('|-----' * 6 + '|') for block in results: sorted_elements_in_block = np.argsort([b[1] for b in block]) for pos in sorted_elements_in_block: res = block[pos] print(''.join([f'| {x:.3f} ' for x in res] + ['|'])) ================================================ FILE: examples/utils.py ================================================ import os import shutil import numpy as np from docarray import Document, DocumentArray def clean_workspace(): if os.path.exists('./data'): shutil.rmtree('./data') if os.path.exists('./workspace'): shutil.rmtree('./workspace') def docs_with_tags(N, D, probs, categories): all_docs = [] start_current = 0 for k, prob in enumerate(probs): n_current = int(N * prob) X = np.random.random((n_current, D)).astype(np.float32) docs = [ Document( embedding=X[i], id=f'{i+start_current}', tags={ 'category': categories[k], }, ) for i in range(n_current) ] all_docs.extend(docs) start_current += n_current return DocumentArray(all_docs) def _precision(predicted, relevant, eval_at): """ fraction of retrieved documents that are relevant to the query """ if eval_at == 0: return 0.0 predicted_at_k = predicted[:eval_at] n_predicted_and_relevant = len(set(predicted_at_k).intersection(set(relevant))) return n_predicted_and_relevant / len(predicted) def _recall(predicted, relevant, eval_at): """ fraction of the relevant documents that are successfully retrieved """ if eval_at == 0: return 0.0 predicted_at_k = predicted[:eval_at] n_predicted_and_relevant = len(set(predicted_at_k).intersection(set(relevant))) return n_predicted_and_relevant / len(relevant) def evaluate(predicts, relevants, top_k): recall = 0 precision = 0 for _predict, _relevant in zip(predicts, relevants): _predict = np.array([int(x) for x in _predict]) recall += _recall(_predict, _relevant, top_k) precision += _precision(_predict, _relevant, top_k) return recall / len(predicts), precision / len(predicts) ================================================ FILE: executor/Dockerfile ================================================ FROM jinaai/jina:3-py38-perf RUN apt-get update && apt-get install --no-install-recommends -y gcc g++ git \ && rm -rf /var/lib/apt/lists/* COPY . /workspace WORKDIR /workspace RUN pip install -r requirements.txt --no-cache-dir ENTRYPOINT ["jina", "executor", "--uses", "config.yml"] ================================================ FILE: executor/README.md ================================================ # AnnLiteIndexer `AnnLiteIndexer` uses the [AnnLite](https://github.com/jina-ai/annlite) class for indexing Jina `Document` objects. The `AnnLite` class partitions the data into cells at index time, and instantiates a "sub-indexer" in each cell. Search is performed aggregating results retrieved from cells. This indexer is recommended to be used when an application requires **search with filters** applied on `Document` tags. The `filtering query language` is based on [MongoDB's query and projection operators](https://docs.mongodb.com/manual/reference/operator/query/). We currently support a subset of those selectors. The tags filters can be combined with `$and` and `$or`: - `$eq` - Equal to (number, string) - `$ne` - Not equal to (number, string) - `$gt` - Greater than (number) - `$gte` - Greater than or equal to (number) - `$lt` - Less than (number) - `$lte` - Less than or equal to (number) - `$in` - Included in an array - `$nin` - Not included in an array For example, we want to search for a product with a price no more than `50$`. ```python index.search(query, filter={"price": {"$lte": 50}}) ``` More example filter expresses - A Nike shoes with white color ```JSON { "brand": {"$eq": "Nike"}, "category": {"$eq": "Shoes"}, "color": {"$eq": "White"} } ``` Or ```JSON { "$and": { "brand": {"$eq": "Nike"}, "category": {"$eq": "Shoes"}, "color": {"$eq": "White"} } } ``` - A Nike shoes or price less than `100$` ```JSON { "$or": { "brand": {"$eq": "Nike"}, "price": {"$lt": 100} } } ``` ## Performance One can run `benchmark.py` to get a quick performance overview. |Stored data| Indexing time | Query size=1 | Query size=8 | Query size=64| |---|---|---|---|---| |10000 | 2.970 | 0.002 | 0.013 | 0.100| |100000 | 76.474 | 0.011 | 0.078 | 0.649| |500000 | 467.936 | 0.046 | 0.356 | 2.823| |1000000 | 1025.506 | 0.091 | 0.695 | 5.778| ## Getting Started For an in-depth overview of the features of AnnLite you can follow along with one of the examples below: | Name | Link | |----------------------------------------------|---| | E-commerce product image search with AnnLite | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/jina-ai/pqlite/blob/main/notebooks/fashion_product_search.ipynb)| ## Quick Start `AnnLiteIndexer` stores `Document` objects at the `workspace` directory, specified under the [`metas`](https://docs.jina.ai/fundamentals/executor/executor-built-in-features/#meta-attributes) attribute. #### Example: Selecting items whose 'price' is less than 50 If documents have a tag `'price'` that stores floating point values this indexer allows searching documents with a filter, such as `price <= 50`. ```python columns = [('price', 'float')] f = Flow().add( uses='jinahub://AnnLiteIndexer/latest', uses_with={ 'dim': 256, 'columns': columns, 'metric': 'cosine' }, uses_metas={'workspace': '/my/tmp_folder'}, install_requirements=True ) search_filter = {"price": {"$lte": 50}} with f: f.post(on='/index', inputs=docs) query_res = f.post(on='/search', inputs=query_docs, return_results=True, parameters={'filter': search_filter}) ``` ## CRUD operations You can perform all the usual operations on the respective endpoints - `/index` Add documents - `/search` Search with query documents. - `/update` Update documents - `/delete` Delete documents - `/clear` Clear the index - `/status` Return the status of index - `total_docs`: the total number of indexed documents - `dim`: the dimension of the embeddings - `metric`: the distance metric type - `is_trained`: whether the index is already trained ================================================ FILE: executor/benchmark.py ================================================ import tempfile import time import numpy as np from jina import DocumentArray from jina.logging.profile import TimeContext n_index = [10_000, 100_000, 500_000, 1_000_000] n_query = [1, 8, 64] D = 768 R = 5 B = 4096 n_cells = 1 from annlite.executor import AnnLiteIndexer times = {} for n_i in n_index: with tempfile.TemporaryDirectory() as tempdir: idxer = AnnLiteIndexer( n_dim=D, initial_size=n_i, n_cells=n_cells, data_path=str(tempdir), ) # build index docs i_embs = np.random.random([n_i, D]).astype(np.float32) if n_cells > 1: idxer._index.vq_codec.fit(i_embs) da = DocumentArray.empty(n_i) da.embeddings = i_embs with TimeContext(f'indexing {n_i} docs') as t_i: for _batch in da.batch(batch_size=B): idxer.index(_batch) times[n_i] = {} times[n_i]['index'] = t_i.duration # waiting for the index to be ready time.sleep(5) for n_q in n_query: q_embs = np.random.random([n_q, D]).astype(np.float32) qa = DocumentArray.empty(n_q) qa.embeddings = q_embs t_qs = [] for _ in range(R): with TimeContext(f'searching {n_q} docs') as t_q: idxer.search(qa) t_qs.append(t_q.duration) # # check if it return the full doc # assert qa[0].matches # assert qa[0].matches.embeddings.shape times[n_i][f'query_{n_q}'] = np.mean(t_qs[1:]) # remove warm-up idxer.clear() idxer.close() print('|Stored data| Indexing time | Query size=1 | Query size=8 | Query size=64|') print('|---' * (len(list(times.values())[0]) + 1) + '|') for k, v in times.items(): s = ' | '.join(f'{v[vv]:.3f}' for vv in ['index', 'query_1', 'query_8', 'query_64']) print(f'|{k} | {s}|') ================================================ FILE: executor/config.yml ================================================ jtype: AnnLiteIndexer py_modules: - ./executor.py metas: name: AnnLiteIndexer_v3 description: A similarity search based on Annlite url: https://github.com/jina-ai/annlite keywords: [ann, similarity_search, indexer, pq, hnsw, pre-filtering] ================================================ FILE: executor/executor.py ================================================ import threading import time import traceback import warnings from threading import Thread from typing import Dict, List, Optional, Tuple, Union from docarray import Document, DocumentArray from jina import Executor, requests from jina.logging.logger import JinaLogger INDEX_BATCH_SIZE = 1024 class AnnLiteIndexer(Executor): """A simple indexer that wraps the AnnLite indexer and adds a simple interface for indexing and searching. :param n_dim: Dimensionality of vectors to index :param metric: Distance metric type. Can be 'euclidean', 'inner_product', or 'cosine' :param limit: Number of results to get for each query document in search :param n_components: Number of components to use for dimensionality reduction :param match_args: the arguments to `DocumentArray`'s match function :param data_path: the workspace of the AnnLiteIndexer but not support when shards > 1. :param ef_construction: The construction time/accuracy trade-off :param ef_search: The query time accuracy/speed trade-off :param max_connection: The maximum number of outgoing connections in the graph (the "M" parameter) :param include_metadata: If True, return the document metadata in response :param index_access_paths: Default traversal paths on docs (used for indexing, delete and update), e.g. '@r', '@c', '@r,c' :param search_access_paths: Default traversal paths on docs (used for search), e.g. '@r', '@c', '@r,c' :param columns: A list or dict of column names to index. :param dim: Deprecated, use n_dim instead """ def __init__( self, n_dim: int = 0, metric: str = 'cosine', limit: int = 10, n_components: Optional[int] = None, match_args: Optional[Dict] = None, data_path: Optional[str] = None, ef_construction: Optional[int] = None, ef_search: Optional[int] = None, max_connection: Optional[int] = None, include_metadata: bool = True, index_access_paths: str = '@r', search_access_paths: str = '@r', columns: Optional[Union[List[Tuple[str, str]], Dict[str, str]]] = None, dim: int = None, *args, **kwargs, ): super().__init__(*args, **kwargs) self.logger = JinaLogger(self.__class__.__name__) n_dim = n_dim or dim if not n_dim: raise ValueError('Please specify the dimension of the vectors to index!') self.n_components = n_components self.metric = metric self.match_args = match_args or {} self.include_metadata = include_metadata if limit: self.match_args.update({'limit': limit}) self.index_access_paths = index_access_paths if 'index_traversal_paths' in kwargs: warnings.warn( f'`index_traversal_paths` is deprecated. Use `index_access_paths` instead.' ) self.index_access_paths = kwargs['index_traversal_paths'] self.search_access_paths = search_access_paths if 'search_traversal_paths' in kwargs: warnings.warn( f'`search_traversal_paths` is deprecated. Use `search_access_paths` instead.' ) self.search_access_paths = kwargs['search_traversal_paths'] self._data_buffer = DocumentArray() self._index_batch_size = INDEX_BATCH_SIZE self._max_length_queue = 2 * self._index_batch_size self._index_lock = threading.Lock() self.logger = JinaLogger(getattr(self.metas, 'name', self.__class__.__name__)) if getattr(self.runtime_args, 'shards', 1) > 1 and data_path: raise ValueError( '`data_path` is not supported when shards > 1, please use `workspace` instead' ) config = { 'n_dim': n_dim, 'n_components': n_components, 'metric': metric, 'ef_construction': ef_construction, 'ef_search': ef_search, 'max_connection': max_connection, 'data_path': data_path or self.workspace or './workspace', 'columns': columns, } self._index = DocumentArray(storage='annlite', config=config) # start indexing thread in background to group indexing requests # together and perform batch indexing at once self._start_index_loop() @requests(on='/index') def index( self, docs: Optional[DocumentArray] = None, parameters: dict = {}, **kwargs ): """Index new documents :param docs: the Documents to index :param parameters: dictionary with options for indexing Keys accepted: - 'access_paths': traversal paths on docs, e.g. '@r', '@c', '@r,c' """ if not docs: return access_paths = parameters.get('access_paths', self.index_access_paths) flat_docs = docs[access_paths] if len(flat_docs) == 0: return while len(self._data_buffer) >= self._max_length_queue: time.sleep(0.001) with self._index_lock: self._data_buffer.extend(flat_docs) def _start_index_loop(self): """Start the indexing loop in background. This loop is responsible for batch indexing the documents in the buffer. """ def _index_loop(): try: while True: # if the buffer is none, will break the loop if self._data_buffer is None: break # if the buffer is empty, will wait for new documents to be added if len(self._data_buffer) == 0: time.sleep(0.1) # sleep for 100ms continue # acquire the lock to prevent threading issues with self._index_lock: batch_docs = self._data_buffer.pop( range( self._index_batch_size if len(self._data_buffer) > self._index_batch_size else len(self._data_buffer) ) ) self._index.extend(batch_docs) self.logger.debug(f'indexing {len(batch_docs)} docs done...') except Exception as e: self.logger.error(traceback.format_exc()) raise e self._index_thread = Thread(target=_index_loop, daemon=False) self._index_thread.start() @requests(on='/update') def update( self, docs: Optional[DocumentArray] = None, parameters: dict = {}, **kwargs ): """Update existing documents :param docs: the Documents to update :param parameters: dictionary with options for updating Keys accepted: - 'access_paths': traversal paths on docs, e.g. '@r', '@c', '@r,c' - 'raise_errors_on_not_found': if True, raise an error if a document is not found. Default is False. """ if not docs: return access_paths = parameters.get('access_paths', self.index_access_paths) raise_errors_on_not_found = parameters.get('raise_errors_on_not_found', False) flat_docs = docs[access_paths] if len(flat_docs) == 0: return with self._index_lock: if len(self._data_buffer) > 0: raise RuntimeError( f'Cannot update documents while the pending documents in the buffer are not indexed yet. ' 'Please wait for the pending documents to be indexed.' ) for doc in flat_docs: try: self._index[doc.id] = doc except IndexError: if raise_errors_on_not_found: raise Exception( f'The document (id={doc.id}) cannot be updated as' f'it is not found in the index' ) else: self.logger.warning( f'cannot update doc {doc.id} as it does not exist in storage' ) @requests(on='/delete') def delete(self, parameters: dict = {}, **kwargs): """Delete existing documents Delete entries from the index by id :param parameters: parameters to the request """ delete_ids = parameters.get('ids', []) if len(delete_ids) == 0: return with self._index_lock: if len(self._data_buffer) > 0: raise RuntimeError( f'Cannot delete documents while the pending documents in the buffer are not indexed yet. ' 'Please wait for the pending documents to be indexed.' ) del self._index[delete_ids] @requests(on='/search') def search( self, docs: Optional[DocumentArray] = None, parameters: dict = {}, **kwargs ): """Perform a vector similarity search and retrieve Document matches Search can be performed with candidate filtering. Filters are a triplet (column,operator,value). More than a filter can be applied during search. Therefore, conditions for a filter are specified as a list triplets. Each triplet contains: - column: Column used to filter. - operator: Binary operation between two values. Some supported operators include `['>','<','=','<=','>=']`. - value: value used to compare a candidate. :param docs: the Documents to search with :param parameters: dictionary for parameters for the search operation Keys accepted: - 'access_paths' (str): traversal paths on docs, e.g. '@r', '@c', '@r,c' - 'filter' (dict): the filtering conditions on document tags - 'limit' (int): nr of matches to get per Document """ if not docs: return access_paths = parameters.get('access_paths', self.search_access_paths) flat_docs = docs[access_paths] match_args = ( {**self.match_args, **parameters} if parameters is not None else self.match_args ) with self._index_lock: # if len(self._data_buffer) > 0: # raise RuntimeError( # f'Cannot search documents while the pending documents in the buffer are not indexed yet. ' # 'Please wait for the pending documents to be indexed.' # ) flat_docs.match(self._index, **match_args) @requests(on='/backup') def backup(self, parameters: Optional[Dict] = {}, **kwargs): """ Backup data to local or remote. Use api of Keys accepted: - 'target' (str): the name of indexer you want to backup as """ target_name = parameters.get('target_name', None) token = parameters.get('token', None) if target_name: target_name = f'{target_name}_{self.runtime_args.shard_id}' with self._index_lock: if len(self._data_buffer) > 0: raise RuntimeError( f'Cannot backup documents while the pending documents in the buffer are not indexed yet. ' 'Please wait for the pending documents to be indexed.' ) self._index._annlite.backup(target_name, token) @requests(on='/restore') def restore(self, parameters: Optional[Dict] = {}, **kwargs): """ Restore data from local or remote. Use api of """ source_name = parameters.get('source_name', None) token = parameters.get('token', None) if source_name: source_name = f'{source_name}_{self.runtime_args.shard_id}' self._index._annlite.restore(source_name, token) @requests(on='/filter') def filter(self, parameters: Dict, **kwargs): """ Query documents from the indexer by the filter `query` object in parameters. The `query` object must follow the specifications in the `find` method of `DocumentArray` using annlite: https://docarray.jina.ai/fundamentals/documentarray/find/#filter-with-query-operators :param parameters: Dictionary to define the `filter` that you want to use. """ return self._index.find(parameters.get('filter', None)) @requests(on='/fill_embedding') def fill_embedding(self, docs: DocumentArray, **kwargs): """ retrieve embedding of Documents by id :param docs: DocumentArray to search with """ for doc in docs: doc.embedding = self._index[doc.id].embedding @requests(on='/status') def status(self, **kwargs) -> DocumentArray: """Return the document containing status information about the indexer. The status will contain information on the total number of indexed and deleted documents, and on the number of (searchable) documents currently in the index. """ status = Document( tags={ 'appending_size': len(self._data_buffer), 'total_docs': len(self._index), 'index_size': len(self._index), } ) return DocumentArray([status]) def flush(self): """Flush all the data in the buffer to the index""" while len(self._data_buffer) > 0: time.sleep(0.1) @requests(on='/clear') def clear(self, **kwargs): """Clear the index of all entries.""" self.flush() with self._index_lock: self._data_buffer = None self._index_thread.join() self._data_buffer = DocumentArray() self._index.clear() self._start_index_loop() def close(self, **kwargs): """Close the index.""" super().close() self.flush() # wait for the index thread to finish with self._index_lock: self._data_buffer = None self._index_thread.join() # WARNING: the commented code below hangs the close in pytest `pytest tests/test_*.py` # But don't know why. It works fine in `pytest tests/test_executor.py` and normal python execution del self._index ================================================ FILE: executor/requirements.txt ================================================ annlite certifi docarray ================================================ FILE: include/hnswlib/bruteforce.h ================================================ #pragma once #include #include #include #include namespace hnswlib { template class BruteforceSearch : public AlgorithmInterface { public: BruteforceSearch(SpaceInterface *s) { } BruteforceSearch(SpaceInterface *s, const std::string &location) { loadIndex(location, s); } BruteforceSearch(SpaceInterface *s, size_t maxElements) { maxelements_ = maxElements; data_size_ = s->get_data_size(); fstdistfunc_ = s->get_dist_func(); dist_func_param_ = s->get_dist_func_param(); size_per_element_ = data_size_ + sizeof(labeltype); data_ = (char *) malloc(maxElements * size_per_element_); if (data_ == nullptr) std::runtime_error("Not enough memory: BruteforceSearch failed to allocate data"); cur_element_count = 0; } ~BruteforceSearch() { free(data_); } char *data_; size_t maxelements_; size_t cur_element_count; size_t size_per_element_; size_t data_size_; DISTFUNC fstdistfunc_; void *dist_func_param_; std::mutex index_lock; std::unordered_map dict_external_to_internal; void addPoint(const void *datapoint, labeltype label) { int idx; { std::unique_lock lock(index_lock); auto search=dict_external_to_internal.find(label); if (search != dict_external_to_internal.end()) { idx=search->second; } else{ if (cur_element_count >= maxelements_) { throw std::runtime_error("The number of elements exceeds the specified limit\n"); } idx=cur_element_count; dict_external_to_internal[label] = idx; cur_element_count++; } } memcpy(data_ + size_per_element_ * idx + data_size_, &label, sizeof(labeltype)); memcpy(data_ + size_per_element_ * idx, datapoint, data_size_); }; void removePoint(labeltype cur_external) { size_t cur_c=dict_external_to_internal[cur_external]; dict_external_to_internal.erase(cur_external); labeltype label=*((labeltype*)(data_ + size_per_element_ * (cur_element_count-1) + data_size_)); dict_external_to_internal[label]=cur_c; memcpy(data_ + size_per_element_ * cur_c, data_ + size_per_element_ * (cur_element_count-1), data_size_+sizeof(labeltype)); cur_element_count--; } std::priority_queue> searchKnn(const void *query_data, size_t k, size_t batch_index) const { std::priority_queue> topResults; if (cur_element_count == 0) return topResults; for (int i = 0; i < k; i++) { dist_t dist = fstdistfunc_(query_data, data_ + size_per_element_ * i, dist_func_param_, nullptr); topResults.push(std::pair(dist, *((labeltype *)(data_ + size_per_element_ * i + data_size_)))); } dist_t lastdist = topResults.top().first; for (int i = k; i < cur_element_count; i++) { dist_t dist = fstdistfunc_(query_data, data_ + size_per_element_ * i, dist_func_param_, nullptr); if (dist <= lastdist) { topResults.push(std::pair(dist, *((labeltype *) (data_ + size_per_element_ * i + data_size_)))); if (topResults.size() > k) topResults.pop(); lastdist = topResults.top().first; } } return topResults; }; void saveIndex(const std::string &location) { std::ofstream output(location, std::ios::binary); std::streampos position; writeBinaryPOD(output, maxelements_); writeBinaryPOD(output, size_per_element_); writeBinaryPOD(output, cur_element_count); output.write(data_, maxelements_ * size_per_element_); output.close(); } void loadIndex(const std::string &location, SpaceInterface *s) { std::ifstream input(location, std::ios::binary); std::streampos position; readBinaryPOD(input, maxelements_); readBinaryPOD(input, size_per_element_); readBinaryPOD(input, cur_element_count); data_size_ = s->get_data_size(); fstdistfunc_ = s->get_dist_func(); dist_func_param_ = s->get_dist_func_param(); size_per_element_ = data_size_ + sizeof(labeltype); data_ = (char *) malloc(maxelements_ * size_per_element_); if (data_ == nullptr) std::runtime_error("Not enough memory: loadIndex failed to allocate data"); input.read(data_, maxelements_ * size_per_element_); input.close(); } }; } ================================================ FILE: include/hnswlib/fusefilter.h ================================================ #ifndef BINARYFUSEFILTER_H #define BINARYFUSEFILTER_H #include #include #include #include #include #include #include #ifndef XOR_MAX_ITERATIONS #define XOR_MAX_ITERATIONS \ 100 // probabillity of success should always be > 0.5 so 100 iterations is // highly unlikely #endif #ifdef _MSC_VER #include #include #endif /** * We start with a few utilities. ***/ static inline uint64_t binary_fuse_murmur64(uint64_t h) { h ^= h >> 33; h *= UINT64_C(0xff51afd7ed558ccd); h ^= h >> 33; h *= UINT64_C(0xc4ceb9fe1a85ec53); h ^= h >> 33; return h; } static inline uint64_t binary_fuse_mix_split(uint64_t key, uint64_t seed) { return binary_fuse_murmur64(key + seed); } static inline uint64_t binary_fuse_rotl64(uint64_t n, unsigned int c) { return (n << (c & 63)) | (n >> ((-c) & 63)); } static inline uint32_t binary_fuse_reduce(uint32_t hash, uint32_t n) { // http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ return (uint32_t)(((uint64_t)hash * n) >> 32); } static inline uint64_t binary_fuse8_fingerprint(uint64_t hash) { return hash ^ (hash >> 32); } /** * We need a decent random number generator. **/ // returns random number, modifies the seed static inline uint64_t binary_fuse_rng_splitmix64(uint64_t *seed) { uint64_t z = (*seed += UINT64_C(0x9E3779B97F4A7C15)); z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9); z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB); return z ^ (z >> 31); } typedef struct binary_fuse8_s { uint64_t Seed; uint32_t SegmentLength; uint32_t SegmentLengthMask; uint32_t SegmentCount; uint32_t SegmentCountLength; uint32_t ArrayLength; uint8_t *Fingerprints; } binary_fuse8_t; #ifdef _MSC_VER // Windows programmers who target 32-bit platform may need help: static inline uint64_t binary_fuse_mulhi(uint64_t a, uint64_t b) { return __umulh(a, b); } #else static inline uint64_t binary_fuse_mulhi(uint64_t a, uint64_t b) { return ((__uint128_t)a * b) >> 64; } #endif typedef struct binary_hashes_s { uint32_t h0; uint32_t h1; uint32_t h2; } binary_hashes_t; static inline binary_hashes_t binary_fuse8_hash_batch(uint64_t hash, const binary_fuse8_t *filter) { uint64_t hi = binary_fuse_mulhi(hash, filter->SegmentCountLength); binary_hashes_t ans; ans.h0 = (uint32_t)hi; ans.h1 = ans.h0 + filter->SegmentLength; ans.h2 = ans.h1 + filter->SegmentLength; ans.h1 ^= (uint32_t)(hash >> 18) & filter->SegmentLengthMask; ans.h2 ^= (uint32_t)(hash)&filter->SegmentLengthMask; return ans; } static inline uint32_t binary_fuse8_hash(int index, uint64_t hash, const binary_fuse8_t *filter) { uint64_t h = binary_fuse_mulhi(hash, filter->SegmentCountLength); h += index * filter->SegmentLength; // keep the lower 36 bits uint64_t hh = hash & ((1UL << 36) - 1); // index 0: right shift by 36; index 1: right shift by 18; index 2: no shift h ^= (size_t)((hh >> (36 - 18 * index)) & filter->SegmentLengthMask); return h; } // Report if the key is in the set, with false positive rate. static inline bool binary_fuse8_contain(uint64_t key, const binary_fuse8_t *filter) { uint64_t hash = binary_fuse_mix_split(key, filter->Seed); uint8_t f = binary_fuse8_fingerprint(hash); binary_hashes_t hashes = binary_fuse8_hash_batch(hash, filter); f ^= filter->Fingerprints[hashes.h0] ^ filter->Fingerprints[hashes.h1] ^ filter->Fingerprints[hashes.h2]; return f == 0; } static inline uint32_t binary_fuse_calculate_segment_length(uint32_t arity, uint32_t size) { // These parameters are very sensitive. Replacing 'floor' by 'round' can // substantially affect the construction time. if (arity == 3) { return ((uint32_t)1) << (int)(floor(log((double)(size)) / log(3.33) + 2.25)); } else if (arity == 4) { return ((uint32_t)1) << (int)(floor(log((double)(size)) / log(2.91) - 0.5)); } else { return 65536; } } static inline double binary_fuse8_max(double a, double b) { if (a < b) { return b; } return a; } static inline double binary_fuse_calculate_size_factor(uint32_t arity, uint32_t size) { if (arity == 3) { return binary_fuse8_max(1.125, 0.875 + 0.25 * log(1000000.0) / log((double)size)); } else if (arity == 4) { return binary_fuse8_max(1.075, 0.77 + 0.305 * log(600000.0) / log((double)size)); } else { return 2.0; } } // allocate enough capacity for a set containing up to 'size' elements // caller is responsible to call binary_fuse8_free(filter) // size should be at least 2. static inline bool binary_fuse8_allocate(uint32_t size, binary_fuse8_t *filter) { uint32_t arity = 3; filter->SegmentLength = size == 0 ? 4 : binary_fuse_calculate_segment_length(arity, size); if (filter->SegmentLength > 262144) { filter->SegmentLength = 262144; } filter->SegmentLengthMask = filter->SegmentLength - 1; double sizeFactor = binary_fuse_calculate_size_factor(arity, size); uint32_t capacity = size <= 1 ? 0 : (uint32_t)(round((double)size * sizeFactor)); uint32_t initSegmentCount = (capacity + filter->SegmentLength - 1) / filter->SegmentLength - (arity - 1); filter->ArrayLength = (initSegmentCount + arity - 1) * filter->SegmentLength; filter->SegmentCount = (filter->ArrayLength + filter->SegmentLength - 1) / filter->SegmentLength; if (filter->SegmentCount <= arity - 1) { filter->SegmentCount = 1; } else { filter->SegmentCount = filter->SegmentCount - (arity - 1); } filter->ArrayLength = (filter->SegmentCount + arity - 1) * filter->SegmentLength; filter->SegmentCountLength = filter->SegmentCount * filter->SegmentLength; filter->Fingerprints = (uint8_t *)malloc(filter->ArrayLength); return filter->Fingerprints != NULL; } // report memory usage static inline size_t binary_fuse8_size_in_bytes(const binary_fuse8_t *filter) { return filter->ArrayLength * sizeof(uint8_t) + sizeof(binary_fuse8_t); } // release memory static inline void binary_fuse8_free(binary_fuse8_t *filter) { free(filter->Fingerprints); filter->Fingerprints = NULL; filter->Seed = 0; filter->SegmentLength = 0; filter->SegmentLengthMask = 0; filter->SegmentCount = 0; filter->SegmentCountLength = 0; filter->ArrayLength = 0; } static inline uint8_t binary_fuse_mod3(uint8_t x) { return x > 2 ? x - 3 : x; } // construct the filter, returns true on success, false on failure. // most likely, a failure is due to too high a memory usage // size is the number of keys // The caller is responsable for calling binary_fuse8_allocate(size,filter) // before. The caller is responsible to ensure that there are not too many duplicated // keys. The inner loop will run up to XOR_MAX_ITERATIONS times (default on // 100), it should never fail, except if there are many duplicated keys. If it fails, // a return value of false is provided. // // // If there are many duplicated keys and you do not want to remove them, you can first // sort your input, the algorithm will then work adequately. bool binary_fuse8_populate(const uint64_t *keys, uint32_t size, binary_fuse8_t *filter) { uint64_t rng_counter = 0x726b2b9d438b9d4d; filter->Seed = binary_fuse_rng_splitmix64(&rng_counter); uint64_t *reverseOrder = (uint64_t *)calloc((size + 1), sizeof(uint64_t)); uint32_t capacity = filter->ArrayLength; uint32_t *alone = (uint32_t *)malloc(capacity * sizeof(uint32_t)); uint8_t *t2count = (uint8_t *)calloc(capacity, sizeof(uint8_t)); uint8_t *reverseH = (uint8_t *)malloc(size * sizeof(uint8_t)); uint64_t *t2hash = (uint64_t *)calloc(capacity, sizeof(uint64_t)); uint32_t blockBits = 1; while (((uint32_t)1 << blockBits) < filter->SegmentCount) { blockBits += 1; } uint32_t block = ((uint32_t)1 << blockBits); uint32_t *startPos = (uint32_t *)malloc((1 << blockBits) * sizeof(uint32_t)); uint32_t h012[5]; if ((alone == NULL) || (t2count == NULL) || (reverseH == NULL) || (t2hash == NULL) || (reverseOrder == NULL) || (startPos == NULL)) { free(alone); free(t2count); free(reverseH); free(t2hash); free(reverseOrder); free(startPos); return false; } reverseOrder[size] = 1; for (int loop = 0; true; ++loop) { if (loop + 1 > XOR_MAX_ITERATIONS) { fprintf(stderr, "Too many iterations. Are all your keys unique?"); free(alone); free(t2count); free(reverseH); free(t2hash); free(reverseOrder); free(startPos); return false; } for (uint32_t i = 0; i < block; i++) { // important : i * size would overflow as a 32-bit number in some // cases. startPos[i] = ((uint64_t)i * size) >> blockBits; } uint64_t maskblock = block - 1; for (uint32_t i = 0; i < size; i++) { uint64_t hash = binary_fuse_murmur64(keys[i] + filter->Seed); uint64_t segment_index = hash >> (64 - blockBits); while (reverseOrder[startPos[segment_index]] != 0) { segment_index++; segment_index &= maskblock; } reverseOrder[startPos[segment_index]] = hash; startPos[segment_index]++; } int error = 0; uint32_t duplicates = 0; for (uint32_t i = 0; i < size; i++) { uint64_t hash = reverseOrder[i]; uint32_t h0 = binary_fuse8_hash(0, hash, filter); t2count[h0] += 4; t2hash[h0] ^= hash; uint32_t h1 = binary_fuse8_hash(1, hash, filter); t2count[h1] += 4; t2count[h1] ^= 1; t2hash[h1] ^= hash; uint32_t h2 = binary_fuse8_hash(2, hash, filter); t2count[h2] += 4; t2hash[h2] ^= hash; t2count[h2] ^= 2; if ((t2hash[h0] & t2hash[h1] & t2hash[h2]) == 0) { if (((t2hash[h0] == 0) && (t2count[h0] == 8)) || ((t2hash[h1] == 0) && (t2count[h1] == 8)) || ((t2hash[h2] == 0) && (t2count[h2] == 8))) { duplicates += 1; t2count[h0] -= 4; t2hash[h0] ^= hash; t2count[h1] -= 4; t2count[h1] ^= 1; t2hash[h1] ^= hash; t2count[h2] -= 4; t2count[h2] ^= 2; t2hash[h2] ^= hash; } } error = (t2count[h0] < 4) ? 1 : error; error = (t2count[h1] < 4) ? 1 : error; error = (t2count[h2] < 4) ? 1 : error; } if (error) { memset(reverseOrder, 0, sizeof(uint64_t)*size); memset(t2count, 0, sizeof(uint8_t)*capacity); memset(t2hash, 0, sizeof(uint64_t)*capacity); filter->Seed = binary_fuse_rng_splitmix64(&rng_counter); continue; } // End of key addition uint32_t Qsize = 0; // Add sets with one key to the queue. for (uint32_t i = 0; i < capacity; i++) { alone[Qsize] = i; Qsize += ((t2count[i] >> 2) == 1) ? 1 : 0; } uint32_t stacksize = 0; while (Qsize > 0) { Qsize--; uint32_t index = alone[Qsize]; if ((t2count[index] >> 2) == 1) { uint64_t hash = t2hash[index]; // h012[0] = binary_fuse8_hash(0, hash, filter); h012[1] = binary_fuse8_hash(1, hash, filter); h012[2] = binary_fuse8_hash(2, hash, filter); h012[3] = binary_fuse8_hash(0, hash, filter); // == h012[0]; h012[4] = h012[1]; uint8_t found = t2count[index] & 3; reverseH[stacksize] = found; reverseOrder[stacksize] = hash; stacksize++; uint32_t other_index1 = h012[found + 1]; alone[Qsize] = other_index1; Qsize += ((t2count[other_index1] >> 2) == 2 ? 1 : 0); t2count[other_index1] -= 4; t2count[other_index1] ^= binary_fuse_mod3(found + 1); t2hash[other_index1] ^= hash; uint32_t other_index2 = h012[found + 2]; alone[Qsize] = other_index2; Qsize += ((t2count[other_index2] >> 2) == 2 ? 1 : 0); t2count[other_index2] -= 4; t2count[other_index2] ^= binary_fuse_mod3(found + 2); t2hash[other_index2] ^= hash; } } if (stacksize + duplicates == size) { // success size = stacksize; break; } memset(reverseOrder, 0, sizeof(uint64_t)*size); memset(t2count, 0, sizeof(uint8_t)*capacity); memset(t2hash, 0, sizeof(uint64_t)*capacity); filter->Seed = binary_fuse_rng_splitmix64(&rng_counter); } for (uint32_t i = size - 1; i < size; i--) { // the hash of the key we insert next uint64_t hash = reverseOrder[i]; uint8_t xor2 = binary_fuse8_fingerprint(hash); uint8_t found = reverseH[i]; h012[0] = binary_fuse8_hash(0, hash, filter); h012[1] = binary_fuse8_hash(1, hash, filter); h012[2] = binary_fuse8_hash(2, hash, filter); h012[3] = h012[0]; h012[4] = h012[1]; filter->Fingerprints[h012[found]] = xor2 ^ filter->Fingerprints[h012[found + 1]] ^ filter->Fingerprints[h012[found + 2]]; } free(alone); free(t2count); free(reverseH); free(t2hash); free(reverseOrder); free(startPos); return true; } ////////////////// // fuse16 ////////////////// typedef struct binary_fuse16_s { uint64_t Seed; uint32_t SegmentLength; uint32_t SegmentLengthMask; uint32_t SegmentCount; uint32_t SegmentCountLength; uint32_t ArrayLength; uint16_t *Fingerprints; binary_fuse16_s(uint32_t size) { uint32_t arity = 3; SegmentLength = size == 0 ? 4 : binary_fuse_calculate_segment_length(arity, size); if (SegmentLength > 262144) { SegmentLength = 262144; } SegmentLengthMask = SegmentLength - 1; double sizeFactor = size <= 1 ? 0 : binary_fuse_calculate_size_factor(arity, size); uint32_t capacity = (uint32_t)(round((double)size * sizeFactor)); uint32_t initSegmentCount = (capacity + SegmentLength - 1) / SegmentLength - (arity - 1); ArrayLength = (initSegmentCount + arity - 1) * SegmentLength; SegmentCount = (ArrayLength + SegmentLength - 1) / SegmentLength; if (SegmentCount <= arity - 1) { SegmentCount = 1; } else { SegmentCount = SegmentCount - (arity - 1); } ArrayLength = (SegmentCount + arity - 1) * SegmentLength; SegmentCountLength = SegmentCount * SegmentLength; Fingerprints = (uint16_t *)malloc(ArrayLength * sizeof(uint16_t)); memset(Fingerprints, 0, ArrayLength * sizeof(uint16_t)); if (Fingerprints == NULL) { throw std::runtime_error("not enough memory to hold the fuse filter"); } } ~binary_fuse16_s() { free(Fingerprints); Fingerprints = NULL; Seed = 0; SegmentLength = 0; SegmentLengthMask = 0; SegmentCount = 0; SegmentCountLength = 0; ArrayLength = 0; } } binary_fuse16_t; static inline uint64_t binary_fuse16_fingerprint(uint64_t hash) { return hash ^ (hash >> 32); } static inline binary_hashes_t binary_fuse16_hash_batch(uint64_t hash, const binary_fuse16_t *filter) { uint64_t hi = binary_fuse_mulhi(hash, filter->SegmentCountLength); binary_hashes_t ans; ans.h0 = (uint32_t)hi; ans.h1 = ans.h0 + filter->SegmentLength; ans.h2 = ans.h1 + filter->SegmentLength; ans.h1 ^= (uint32_t)(hash >> 18) & filter->SegmentLengthMask; ans.h2 ^= (uint32_t)(hash)&filter->SegmentLengthMask; return ans; } static inline uint32_t binary_fuse16_hash(int index, uint64_t hash, const binary_fuse16_t *filter) { uint64_t h = binary_fuse_mulhi(hash, filter->SegmentCountLength); h += index * filter->SegmentLength; // keep the lower 36 bits uint64_t hh = hash & ((1UL << 36) - 1); // index 0: right shift by 36; index 1: right shift by 18; index 2: no shift h ^= (size_t)((hh >> (36 - 18 * index)) & filter->SegmentLengthMask); return h; } // Report if the key is in the set, with false positive rate. static inline bool binary_fuse16_contain(uint64_t key, const binary_fuse16_t *filter) { uint64_t hash = binary_fuse_mix_split(key, filter->Seed); uint16_t f = binary_fuse16_fingerprint(hash); binary_hashes_t hashes = binary_fuse16_hash_batch(hash, filter); f ^= filter->Fingerprints[hashes.h0] ^ filter->Fingerprints[hashes.h1] ^ filter->Fingerprints[hashes.h2]; return f == 0; } // allocate enough capacity for a set containing up to 'size' elements // caller is responsible to call binary_fuse16_free(filter) // size should be at least 2. static inline bool binary_fuse16_allocate(uint32_t size, binary_fuse16_t *filter) { uint32_t arity = 3; filter->SegmentLength = size == 0 ? 4 : binary_fuse_calculate_segment_length(arity, size); if (filter->SegmentLength > 262144) { filter->SegmentLength = 262144; } filter->SegmentLengthMask = filter->SegmentLength - 1; double sizeFactor = size <= 1 ? 0 : binary_fuse_calculate_size_factor(arity, size); uint32_t capacity = (uint32_t)(round((double)size * sizeFactor)); uint32_t initSegmentCount = (capacity + filter->SegmentLength - 1) / filter->SegmentLength - (arity - 1); filter->ArrayLength = (initSegmentCount + arity - 1) * filter->SegmentLength; filter->SegmentCount = (filter->ArrayLength + filter->SegmentLength - 1) / filter->SegmentLength; if (filter->SegmentCount <= arity - 1) { filter->SegmentCount = 1; } else { filter->SegmentCount = filter->SegmentCount - (arity - 1); } filter->ArrayLength = (filter->SegmentCount + arity - 1) * filter->SegmentLength; filter->SegmentCountLength = filter->SegmentCount * filter->SegmentLength; filter->Fingerprints = (uint16_t*)malloc(filter->ArrayLength * sizeof(uint16_t)); return filter->Fingerprints != NULL; } // report memory usage static inline size_t binary_fuse16_size_in_bytes(const binary_fuse16_t *filter) { return filter->ArrayLength * sizeof(uint16_t) + sizeof(binary_fuse16_t); } // release memory static inline void binary_fuse16_free(binary_fuse16_t *filter) { free(filter->Fingerprints); filter->Fingerprints = NULL; filter->Seed = 0; filter->SegmentLength = 0; filter->SegmentLengthMask = 0; filter->SegmentCount = 0; filter->SegmentCountLength = 0; filter->ArrayLength = 0; } // construct the filter, returns true on success, false on failure. // most likely, a failure is due to too high a memory usage // size is the number of keys // The caller is responsable for calling binary_fuse8_allocate(size,filter) // before. The caller is responsible to ensure that there are not too many duplicated // keys. The inner loop will run up to XOR_MAX_ITERATIONS times (default on // 100), it should never fail, except if there are many duplicated keys. If it fails, // a return value of false is provided. // // If there are many duplicated keys and you do not want to remove them, you can first // sort your input, the algorithm will then work adequately. inline bool binary_fuse16_populate(const uint64_t *keys, uint32_t size, binary_fuse16_t *filter) { uint64_t rng_counter = 0x726b2b9d438b9d4d; filter->Seed = binary_fuse_rng_splitmix64(&rng_counter); uint64_t *reverseOrder = (uint64_t *)calloc((size + 1), sizeof(uint64_t)); uint32_t capacity = filter->ArrayLength; uint32_t *alone = (uint32_t *)malloc(capacity * sizeof(uint32_t)); uint8_t *t2count = (uint8_t *)calloc(capacity, sizeof(uint8_t)); uint8_t *reverseH = (uint8_t *)malloc(size * sizeof(uint8_t)); uint64_t *t2hash = (uint64_t *)calloc(capacity, sizeof(uint64_t)); uint32_t blockBits = 1; while (((uint32_t)1 << blockBits) < filter->SegmentCount) { blockBits += 1; } uint32_t block = ((uint32_t)1 << blockBits); uint32_t *startPos = (uint32_t *)malloc((1 << blockBits) * sizeof(uint32_t)); uint32_t h012[5]; if ((alone == NULL) || (t2count == NULL) || (reverseH == NULL) || (t2hash == NULL) || (reverseOrder == NULL) || (startPos == NULL)) { free(alone); free(t2count); free(reverseH); free(t2hash); free(reverseOrder); free(startPos); return false; } reverseOrder[size] = 1; for (int loop = 0; true; ++loop) { if (loop + 1 > XOR_MAX_ITERATIONS) { fprintf(stderr, "Too many iterations. Are all your keys unique?"); free(alone); free(t2count); free(reverseH); free(t2hash); free(reverseOrder); free(startPos); return false; } for (uint32_t i = 0; i < block; i++) { // important : i * size would overflow as a 32-bit number in some // cases. startPos[i] = ((uint64_t)i * size) >> blockBits; } uint64_t maskblock = block - 1; for (uint32_t i = 0; i < size; i++) { uint64_t hash = binary_fuse_murmur64(keys[i] + filter->Seed); uint64_t segment_index = hash >> (64 - blockBits); while (reverseOrder[startPos[segment_index]] != 0) { segment_index++; segment_index &= maskblock; } reverseOrder[startPos[segment_index]] = hash; startPos[segment_index]++; } int error = 0; uint32_t duplicates = 0; for (uint32_t i = 0; i < size; i++) { uint64_t hash = reverseOrder[i]; uint32_t h0 = binary_fuse16_hash(0, hash, filter); t2count[h0] += 4; t2hash[h0] ^= hash; uint32_t h1 = binary_fuse16_hash(1, hash, filter); t2count[h1] += 4; t2count[h1] ^= 1; t2hash[h1] ^= hash; uint32_t h2 = binary_fuse16_hash(2, hash, filter); t2count[h2] += 4; t2hash[h2] ^= hash; t2count[h2] ^= 2; if ((t2hash[h0] & t2hash[h1] & t2hash[h2]) == 0) { if (((t2hash[h0] == 0) && (t2count[h0] == 8)) || ((t2hash[h1] == 0) && (t2count[h1] == 8)) || ((t2hash[h2] == 0) && (t2count[h2] == 8))) { duplicates += 1; t2count[h0] -= 4; t2hash[h0] ^= hash; t2count[h1] -= 4; t2count[h1] ^= 1; t2hash[h1] ^= hash; t2count[h2] -= 4; t2count[h2] ^= 2; t2hash[h2] ^= hash; } } error = (t2count[h0] < 4) ? 1 : error; error = (t2count[h1] < 4) ? 1 : error; error = (t2count[h2] < 4) ? 1 : error; } if (error) { memset(reverseOrder, 0, sizeof(uint64_t)*size); memset(t2count, 0, sizeof(uint8_t)*capacity); memset(t2hash, 0, sizeof(uint64_t)*capacity); filter->Seed = binary_fuse_rng_splitmix64(&rng_counter); continue; } // End of key addition uint32_t Qsize = 0; // Add sets with one key to the queue. for (uint32_t i = 0; i < capacity; i++) { alone[Qsize] = i; Qsize += ((t2count[i] >> 2) == 1) ? 1 : 0; } uint32_t stacksize = 0; while (Qsize > 0) { Qsize--; uint32_t index = alone[Qsize]; if ((t2count[index] >> 2) == 1) { uint64_t hash = t2hash[index]; // h012[0] = binary_fuse16_hash(0, hash, filter); h012[1] = binary_fuse16_hash(1, hash, filter); h012[2] = binary_fuse16_hash(2, hash, filter); h012[3] = binary_fuse16_hash(0, hash, filter); // == h012[0]; h012[4] = h012[1]; uint8_t found = t2count[index] & 3; reverseH[stacksize] = found; reverseOrder[stacksize] = hash; stacksize++; uint32_t other_index1 = h012[found + 1]; alone[Qsize] = other_index1; Qsize += ((t2count[other_index1] >> 2) == 2 ? 1 : 0); t2count[other_index1] -= 4; t2count[other_index1] ^= binary_fuse_mod3(found + 1); t2hash[other_index1] ^= hash; uint32_t other_index2 = h012[found + 2]; alone[Qsize] = other_index2; Qsize += ((t2count[other_index2] >> 2) == 2 ? 1 : 0); t2count[other_index2] -= 4; t2count[other_index2] ^= binary_fuse_mod3(found + 2); t2hash[other_index2] ^= hash; } } if (stacksize + duplicates == size) { // success size = stacksize; break; } memset(reverseOrder, 0, sizeof(uint64_t)*size); memset(t2count, 0, sizeof(uint8_t)*capacity); memset(t2hash, 0, sizeof(uint64_t)*capacity); filter->Seed = binary_fuse_rng_splitmix64(&rng_counter); } for (uint32_t i = size - 1; i < size; i--) { // the hash of the key we insert next uint64_t hash = reverseOrder[i]; uint16_t xor2 = binary_fuse16_fingerprint(hash); uint8_t found = reverseH[i]; h012[0] = binary_fuse16_hash(0, hash, filter); h012[1] = binary_fuse16_hash(1, hash, filter); h012[2] = binary_fuse16_hash(2, hash, filter); h012[3] = h012[0]; h012[4] = h012[1]; filter->Fingerprints[h012[found]] = xor2 ^ filter->Fingerprints[h012[found + 1]] ^ filter->Fingerprints[h012[found + 2]]; } free(alone); free(t2count); free(reverseH); free(t2hash); free(reverseOrder); free(startPos); return true; } #endif ================================================ FILE: include/hnswlib/hnswalg.h ================================================ #pragma once #include "hnswlib.h" #include "visited_list_pool.h" #include #include #include #include #include #include namespace hnswlib { typedef unsigned int tableint; typedef unsigned int linklistsizeint; template class HierarchicalNSW : public AlgorithmInterface { public: static const tableint max_update_element_locks = 65536; HierarchicalNSW(SpaceInterface *s) { } HierarchicalNSW(SpaceInterface *s, const std::string &location, bool nmslib = false, size_t max_elements=0) { loadIndex(location, s, max_elements); } HierarchicalNSW(SpaceInterface *s, size_t max_elements, size_t M = 16, size_t ef_construction = 200, size_t random_seed = 100) : link_list_locks_(max_elements), link_list_update_locks_(max_update_element_locks), element_levels_(max_elements) { max_elements_ = max_elements; has_deletions_ = false; num_deleted_ = 0; data_size_ = s->get_data_size(); fstdistfunc_ = s->get_dist_func(); dist_func_param_ = s->get_dist_func_param(); M_ = M; maxM_ = M_; maxM0_ = M_ * 2; ef_construction_ = std::max(ef_construction,M_); ef_ = 10; level_generator_.seed(random_seed); update_probability_generator_.seed(random_seed + 1); size_links_level0_ = maxM0_ * sizeof(tableint) + sizeof(linklistsizeint); size_data_per_element_ = size_links_level0_ + data_size_ + sizeof(labeltype); offsetData_ = size_links_level0_; label_offset_ = size_links_level0_ + data_size_; offsetLevel0_ = 0; data_level0_memory_ = (char *) malloc(max_elements_ * size_data_per_element_); if (data_level0_memory_ == nullptr) throw std::runtime_error("Not enough memory"); cur_element_count = 0; visited_list_pool_ = new VisitedListPool(1, max_elements); //initializations for special treatment of the first node enterpoint_node_ = -1; maxlevel_ = -1; linkLists_ = (char **) malloc(sizeof(void *) * max_elements_); if (linkLists_ == nullptr) throw std::runtime_error("Not enough memory: HierarchicalNSW failed to allocate linklists"); size_links_per_element_ = maxM_ * sizeof(tableint) + sizeof(linklistsizeint); mult_ = 1 / log(1.0 * M_); revSize_ = 1.0 / mult_; } struct CompareByFirst { constexpr bool operator()(std::pair const &a, std::pair const &b) const noexcept { return a.first < b.first; } }; ~HierarchicalNSW() { free(data_level0_memory_); for (tableint i = 0; i < cur_element_count; i++) { if (element_levels_[i] > 0) free(linkLists_[i]); } free(linkLists_); delete visited_list_pool_; } size_t max_elements_; size_t cur_element_count; size_t size_data_per_element_; size_t size_links_per_element_; size_t num_deleted_; size_t M_; size_t maxM_; size_t maxM0_; size_t ef_construction_; double mult_, revSize_; int maxlevel_; VisitedListPool *visited_list_pool_; std::mutex cur_element_count_guard_; std::vector link_list_locks_; // Locks to prevent race condition during update/insert of an element at same time. // Note: Locks for additions can also be used to prevent this race condition if the querying of KNN is not exposed along with update/inserts i.e multithread insert/update/query in parallel. std::vector link_list_update_locks_; tableint enterpoint_node_; size_t size_links_level0_; size_t offsetData_, offsetLevel0_; char *data_level0_memory_; char **linkLists_; std::vector element_levels_; size_t data_size_; bool has_deletions_; size_t label_offset_; DISTFUNC fstdistfunc_; void *dist_func_param_; std::unordered_map label_lookup_; std::default_random_engine level_generator_; std::default_random_engine update_probability_generator_; inline labeltype getExternalLabel(tableint internal_id) const { labeltype return_label; memcpy(&return_label,(data_level0_memory_ + internal_id * size_data_per_element_ + label_offset_), sizeof(labeltype)); return return_label; } inline void setExternalLabel(tableint internal_id, labeltype label) const { memcpy((data_level0_memory_ + internal_id * size_data_per_element_ + label_offset_), &label, sizeof(labeltype)); } inline labeltype *getExternalLabeLp(tableint internal_id) const { return (labeltype *) (data_level0_memory_ + internal_id * size_data_per_element_ + label_offset_); } inline char *getDataByInternalId(tableint internal_id) const { return (data_level0_memory_ + internal_id * size_data_per_element_ + offsetData_); } int getRandomLevel(double reverse_size) { std::uniform_real_distribution distribution(0.0, 1.0); double r = -log(distribution(level_generator_)) * reverse_size; return (int) r; } // TODO: std::priority_queue, std::vector>, CompareByFirst> searchBaseLayer(tableint ep_id, const void *data_point, int layer, const local_state_t *local_state_ptr) { VisitedList *vl = visited_list_pool_->getFreeVisitedList(); vl_type *visited_array = vl->mass; vl_type visited_array_tag = vl->curV; std::priority_queue, std::vector>, CompareByFirst> top_candidates; std::priority_queue, std::vector>, CompareByFirst> candidateSet; dist_t lowerBound; if (!isMarkedDeleted(ep_id)) { dist_t dist = fstdistfunc_(data_point, getDataByInternalId(ep_id), dist_func_param_, local_state_ptr); top_candidates.emplace(dist, ep_id); lowerBound = dist; candidateSet.emplace(-dist, ep_id); } else { lowerBound = std::numeric_limits::max(); candidateSet.emplace(-lowerBound, ep_id); } visited_array[ep_id] = visited_array_tag; while (!candidateSet.empty()) { std::pair curr_el_pair = candidateSet.top(); if ((-curr_el_pair.first) > lowerBound) { break; } candidateSet.pop(); tableint curNodeNum = curr_el_pair.second; std::unique_lock lock(link_list_locks_[curNodeNum]); int *data;// = (int *)(linkList0_ + curNodeNum * size_links_per_element0_); if (layer == 0) { data = (int*)get_linklist0(curNodeNum); } else { data = (int*)get_linklist(curNodeNum, layer); // data = (int *) (linkLists_[curNodeNum] + (layer - 1) * size_links_per_element_); } size_t size = getListCount((linklistsizeint*)data); tableint *datal = (tableint *) (data + 1); #ifdef USE_SSE _mm_prefetch((char *) (visited_array + *(data + 1)), _MM_HINT_T0); _mm_prefetch((char *) (visited_array + *(data + 1) + 64), _MM_HINT_T0); _mm_prefetch(getDataByInternalId(*datal), _MM_HINT_T0); _mm_prefetch(getDataByInternalId(*(datal + 1)), _MM_HINT_T0); #endif for (size_t j = 0; j < size; j++) { tableint candidate_id = *(datal + j); // if (candidate_id == 0) continue; #ifdef USE_SSE _mm_prefetch((char *) (visited_array + *(datal + j + 1)), _MM_HINT_T0); _mm_prefetch(getDataByInternalId(*(datal + j + 1)), _MM_HINT_T0); #endif if (visited_array[candidate_id] == visited_array_tag) continue; visited_array[candidate_id] = visited_array_tag; char *currObj1 = (getDataByInternalId(candidate_id)); dist_t dist1 = fstdistfunc_(data_point, currObj1, dist_func_param_, local_state_ptr); if (top_candidates.size() < ef_construction_ || lowerBound > dist1) { candidateSet.emplace(-dist1, candidate_id); #ifdef USE_SSE _mm_prefetch(getDataByInternalId(candidateSet.top().second), _MM_HINT_T0); #endif if (!isMarkedDeleted(candidate_id)) top_candidates.emplace(dist1, candidate_id); if (top_candidates.size() > ef_construction_) top_candidates.pop(); if (!top_candidates.empty()) lowerBound = top_candidates.top().first; } } } visited_list_pool_->releaseVisitedList(vl); return top_candidates; } mutable std::atomic metric_distance_computations; mutable std::atomic metric_hops; template std::priority_queue, std::vector>, CompareByFirst> searchBaseLayerST(tableint ep_id, const void *data_point, size_t ef, const local_state_t *local_state_ptr) const { VisitedList *vl = visited_list_pool_->getFreeVisitedList(); vl_type *visited_array = vl->mass; vl_type visited_array_tag = vl->curV; std::priority_queue, std::vector>, CompareByFirst> top_candidates; std::priority_queue, std::vector>, CompareByFirst> candidate_set; dist_t lowerBound; if (!has_deletions || !isMarkedDeleted(ep_id)) { dist_t dist = fstdistfunc_(data_point, getDataByInternalId(ep_id), dist_func_param_, local_state_ptr); lowerBound = dist; top_candidates.emplace(dist, ep_id); candidate_set.emplace(-dist, ep_id); } else { lowerBound = std::numeric_limits::max(); candidate_set.emplace(-lowerBound, ep_id); } visited_array[ep_id] = visited_array_tag; while (!candidate_set.empty()) { std::pair current_node_pair = candidate_set.top(); if ((-current_node_pair.first) > lowerBound && (top_candidates.size() == ef || has_deletions == false)) { break; } candidate_set.pop(); tableint current_node_id = current_node_pair.second; int *data = (int *) get_linklist0(current_node_id); size_t size = getListCount((linklistsizeint*)data); // bool cur_node_deleted = isMarkedDeleted(current_node_id); if(collect_metrics){ metric_hops++; metric_distance_computations+=size; } #ifdef USE_SSE _mm_prefetch((char *) (visited_array + *(data + 1)), _MM_HINT_T0); _mm_prefetch((char *) (visited_array + *(data + 1) + 64), _MM_HINT_T0); _mm_prefetch(data_level0_memory_ + (*(data + 1)) * size_data_per_element_ + offsetData_, _MM_HINT_T0); _mm_prefetch((char *) (data + 2), _MM_HINT_T0); #endif for (size_t j = 1; j <= size; j++) { int candidate_id = *(data + j); // if (candidate_id == 0) continue; #ifdef USE_SSE _mm_prefetch((char *) (visited_array + *(data + j + 1)), _MM_HINT_T0); _mm_prefetch(data_level0_memory_ + (*(data + j + 1)) * size_data_per_element_ + offsetData_, _MM_HINT_T0);//////////// #endif if (!(visited_array[candidate_id] == visited_array_tag)) { visited_array[candidate_id] = visited_array_tag; char *currObj1 = (getDataByInternalId(candidate_id)); dist_t dist = fstdistfunc_(data_point, currObj1, dist_func_param_, local_state_ptr); if (top_candidates.size() < ef || lowerBound > dist) { candidate_set.emplace(-dist, candidate_id); #ifdef USE_SSE _mm_prefetch(data_level0_memory_ + candidate_set.top().second * size_data_per_element_ + offsetLevel0_,/////////// _MM_HINT_T0);//////////////////////// #endif if (!has_deletions || !isMarkedDeleted(candidate_id)) top_candidates.emplace(dist, candidate_id); if (top_candidates.size() > ef) top_candidates.pop(); if (!top_candidates.empty()) lowerBound = top_candidates.top().first; } } } } visited_list_pool_->releaseVisitedList(vl); return top_candidates; } // TODO template std::priority_queue, std::vector>, CompareByFirst> searchBaseLayerSTWithFilter(tableint ep_id, const void *data_point, const binary_fuse16_t *filter, size_t ef, const local_state_t *local_state_ptr) const { VisitedList *vl = visited_list_pool_->getFreeVisitedList(); vl_type *visited_array = vl->mass; vl_type visited_array_tag = vl->curV; std::priority_queue, std::vector>, CompareByFirst> top_candidates; std::priority_queue, std::vector>, CompareByFirst> candidate_set; dist_t lowerBound; uint64_t label = getExternalLabel(ep_id); if (binary_fuse16_contain(label, filter)) { dist_t dist = fstdistfunc_(data_point, getDataByInternalId(ep_id), dist_func_param_, local_state_ptr); lowerBound = dist; top_candidates.emplace(dist, ep_id); candidate_set.emplace(-dist, ep_id); } else { lowerBound = std::numeric_limits::max(); candidate_set.emplace(-lowerBound, ep_id); } visited_array[ep_id] = visited_array_tag; while (!candidate_set.empty()) { std::pair current_node_pair = candidate_set.top(); if ((-current_node_pair.first) > lowerBound) { break; } candidate_set.pop(); tableint current_node_id = current_node_pair.second; int *data = (int *)get_linklist0(current_node_id); size_t size = getListCount((linklistsizeint *)data); // bool cur_node_deleted = // isMarkedDeleted(current_node_id); if (collect_metrics) { metric_hops++; metric_distance_computations += size; } #ifdef USE_SSE _mm_prefetch((char *)(visited_array + *(data + 1)), _MM_HINT_T0); _mm_prefetch((char *)(visited_array + *(data + 1) + 64), _MM_HINT_T0); _mm_prefetch(data_level0_memory_ + (*(data + 1)) * size_data_per_element_ + offsetData_, _MM_HINT_T0); _mm_prefetch((char *)(data + 2), _MM_HINT_T0); #endif for (size_t j = 1; j <= size; j++) { int candidate_id = *(data + j); // if (candidate_id == 0) continue; #ifdef USE_SSE _mm_prefetch((char *)(visited_array + *(data + j + 1)), _MM_HINT_T0); _mm_prefetch(data_level0_memory_ + (*(data + j + 1)) * size_data_per_element_ + offsetData_, _MM_HINT_T0); //////////// #endif if (!(visited_array[candidate_id] == visited_array_tag)) { visited_array[candidate_id] = visited_array_tag; char *currObj1 = (getDataByInternalId(candidate_id)); dist_t dist = fstdistfunc_(data_point, currObj1, dist_func_param_, local_state_ptr); if (top_candidates.size() < ef || lowerBound > dist) { candidate_set.emplace(-dist, candidate_id); #ifdef USE_SSE _mm_prefetch(data_level0_memory_ + candidate_set.top().second * size_data_per_element_ + offsetLevel0_, /////////// _MM_HINT_T0); //////////////////////// #endif uint64_t et_label = getExternalLabel(candidate_id); if (binary_fuse16_contain(et_label, filter)) top_candidates.emplace(dist, candidate_id); if (top_candidates.size() > ef) top_candidates.pop(); if (!top_candidates.empty()) lowerBound = top_candidates.top().first; } } } } visited_list_pool_->releaseVisitedList(vl); return top_candidates; } // TODO void getNeighborsByHeuristic2( std::priority_queue, std::vector>, CompareByFirst> &top_candidates, const size_t M, const local_state_t *local_state_ptr) { if (top_candidates.size() < M) { return; } std::priority_queue> queue_closest; std::vector> return_list; while (top_candidates.size() > 0) { queue_closest.emplace(-top_candidates.top().first, top_candidates.top().second); top_candidates.pop(); } while (queue_closest.size()) { if (return_list.size() >= M) break; std::pair curent_pair = queue_closest.top(); dist_t dist_to_query = -curent_pair.first; queue_closest.pop(); bool good = true; for (std::pair second_pair : return_list) { dist_t curdist = fstdistfunc_(getDataByInternalId(second_pair.second), getDataByInternalId(curent_pair.second), dist_func_param_, local_state_ptr);; if (curdist < dist_to_query) { good = false; break; } } if (good) { return_list.push_back(curent_pair); } } for (std::pair curent_pair : return_list) { top_candidates.emplace(-curent_pair.first, curent_pair.second); } } linklistsizeint *get_linklist0(tableint internal_id) const { return (linklistsizeint *) (data_level0_memory_ + internal_id * size_data_per_element_ + offsetLevel0_); }; linklistsizeint *get_linklist0(tableint internal_id, char *data_level0_memory_) const { return (linklistsizeint *) (data_level0_memory_ + internal_id * size_data_per_element_ + offsetLevel0_); }; linklistsizeint *get_linklist(tableint internal_id, int level) const { return (linklistsizeint *) (linkLists_[internal_id] + (level - 1) * size_links_per_element_); }; linklistsizeint *get_linklist_at_level(tableint internal_id, int level) const { return level == 0 ? get_linklist0(internal_id) : get_linklist(internal_id, level); }; tableint mutuallyConnectNewElement(const void *data_point, tableint cur_c, std::priority_queue, std::vector>, CompareByFirst> &top_candidates, int level, bool isUpdate, const local_state_t *local_state_ptr) { size_t Mcurmax = level ? maxM_ : maxM0_; getNeighborsByHeuristic2(top_candidates, M_, local_state_ptr); if (top_candidates.size() > M_) throw std::runtime_error("Should be not be more than M_ candidates returned by the heuristic"); std::vector selectedNeighbors; selectedNeighbors.reserve(M_); while (top_candidates.size() > 0) { selectedNeighbors.push_back(top_candidates.top().second); top_candidates.pop(); } tableint next_closest_entry_point = selectedNeighbors.back(); { linklistsizeint *ll_cur; if (level == 0) ll_cur = get_linklist0(cur_c); else ll_cur = get_linklist(cur_c, level); if (*ll_cur && !isUpdate) { throw std::runtime_error("The newly inserted element should have blank link list"); } setListCount(ll_cur,selectedNeighbors.size()); tableint *data = (tableint *) (ll_cur + 1); for (size_t idx = 0; idx < selectedNeighbors.size(); idx++) { if (data[idx] && !isUpdate) throw std::runtime_error("Possible memory corruption"); if (level > element_levels_[selectedNeighbors[idx]]) throw std::runtime_error("Trying to make a link on a non-existent level"); data[idx] = selectedNeighbors[idx]; } } for (size_t idx = 0; idx < selectedNeighbors.size(); idx++) { std::unique_lock lock(link_list_locks_[selectedNeighbors[idx]]); linklistsizeint *ll_other; if (level == 0) ll_other = get_linklist0(selectedNeighbors[idx]); else ll_other = get_linklist(selectedNeighbors[idx], level); size_t sz_link_list_other = getListCount(ll_other); if (sz_link_list_other > Mcurmax) throw std::runtime_error("Bad value of sz_link_list_other"); if (selectedNeighbors[idx] == cur_c) throw std::runtime_error("Trying to connect an element to itself"); if (level > element_levels_[selectedNeighbors[idx]]) throw std::runtime_error("Trying to make a link on a non-existent level"); tableint *data = (tableint *) (ll_other + 1); bool is_cur_c_present = false; if (isUpdate) { for (size_t j = 0; j < sz_link_list_other; j++) { if (data[j] == cur_c) { is_cur_c_present = true; break; } } } // If cur_c is already present in the neighboring connections of `selectedNeighbors[idx]` then no need to modify any connections or run the heuristics. if (!is_cur_c_present) { if (sz_link_list_other < Mcurmax) { data[sz_link_list_other] = cur_c; setListCount(ll_other, sz_link_list_other + 1); } else { // finding the "weakest" element to replace it with the new one dist_t d_max = fstdistfunc_(getDataByInternalId(cur_c), getDataByInternalId(selectedNeighbors[idx]), dist_func_param_, local_state_ptr); // Heuristic: std::priority_queue, std::vector>, CompareByFirst> candidates; candidates.emplace(d_max, cur_c); for (size_t j = 0; j < sz_link_list_other; j++) { candidates.emplace( fstdistfunc_(getDataByInternalId(data[j]), getDataByInternalId(selectedNeighbors[idx]), dist_func_param_, local_state_ptr), data[j]); } getNeighborsByHeuristic2(candidates, Mcurmax, local_state_ptr); int indx = 0; while (candidates.size() > 0) { data[indx] = candidates.top().second; candidates.pop(); indx++; } setListCount(ll_other, indx); // Nearest K: /*int indx = -1; for (int j = 0; j < sz_link_list_other; j++) { dist_t d = fstdistfunc_(getDataByInternalId(data[j]), getDataByInternalId(rez[idx]), dist_func_param_, nullptr); if (d > d_max) { indx = j; d_max = d; } } if (indx >= 0) { data[indx] = cur_c; } */ } } } return next_closest_entry_point; } std::mutex global; size_t ef_; void setEf(size_t ef) { ef_ = ef; } std::priority_queue> searchKnnInternal(void *query_data, int k) { std::priority_queue> top_candidates; if (cur_element_count == 0) return top_candidates; // FIXME: Never used, so fix index to 0. local_state_t local_state; local_state.batch_index = 0; tableint currObj = enterpoint_node_; dist_t curdist = fstdistfunc_(query_data, getDataByInternalId(enterpoint_node_), dist_func_param_, &local_state); for (size_t level = maxlevel_; level > 0; level--) { bool changed = true; while (changed) { changed = false; int *data; data = (int *) get_linklist(currObj,level); int size = getListCount(data); tableint *datal = (tableint *) (data + 1); for (int i = 0; i < size; i++) { tableint cand = datal[i]; if (cand < 0 || cand > max_elements_) throw std::runtime_error("cand error"); dist_t d = fstdistfunc_(query_data, getDataByInternalId(cand), dist_func_param_, nullptr); if (d < curdist) { curdist = d; currObj = cand; changed = true; } } } } if (num_deleted_) { std::priority_queue> top_candidates1=searchBaseLayerST(currObj, query_data, ef_, &local_state); top_candidates.swap(top_candidates1); } else{ std::priority_queue> top_candidates1=searchBaseLayerST(currObj, query_data, ef_, &local_state); top_candidates.swap(top_candidates1); } while (top_candidates.size() > k) { top_candidates.pop(); } return top_candidates; }; void resizeIndex(size_t new_max_elements){ if (new_max_elements(new_max_elements).swap(link_list_locks_); // Reallocate base layer char * data_level0_memory_new = (char *) realloc(data_level0_memory_, new_max_elements * size_data_per_element_); if (data_level0_memory_new == nullptr) throw std::runtime_error("Not enough memory: resizeIndex failed to allocate base layer"); data_level0_memory_ = data_level0_memory_new; // Reallocate all other layers char ** linkLists_new = (char **) realloc(linkLists_, sizeof(void *) * new_max_elements); if (linkLists_new == nullptr) throw std::runtime_error("Not enough memory: resizeIndex failed to allocate other layers"); linkLists_ = linkLists_new; max_elements_ = new_max_elements; } void saveIndex(const std::string &location) { std::ofstream output(location, std::ios::binary); std::streampos position; writeBinaryPOD(output, offsetLevel0_); writeBinaryPOD(output, max_elements_); writeBinaryPOD(output, cur_element_count); writeBinaryPOD(output, size_data_per_element_); writeBinaryPOD(output, label_offset_); writeBinaryPOD(output, offsetData_); writeBinaryPOD(output, maxlevel_); writeBinaryPOD(output, enterpoint_node_); writeBinaryPOD(output, maxM_); writeBinaryPOD(output, maxM0_); writeBinaryPOD(output, M_); writeBinaryPOD(output, mult_); writeBinaryPOD(output, ef_construction_); output.write(data_level0_memory_, cur_element_count * size_data_per_element_); for (size_t i = 0; i < cur_element_count; i++) { unsigned int linkListSize = element_levels_[i] > 0 ? size_links_per_element_ * element_levels_[i] : 0; writeBinaryPOD(output, linkListSize); if (linkListSize) output.write(linkLists_[i], linkListSize); } output.close(); } void loadIndex(const std::string &location, SpaceInterface *s, size_t max_elements_i=0) { std::ifstream input(location, std::ios::binary); if (!input.is_open()) throw std::runtime_error("Cannot open file"); // get file size: input.seekg(0,input.end); std::streampos total_filesize=input.tellg(); input.seekg(0,input.beg); readBinaryPOD(input, offsetLevel0_); readBinaryPOD(input, max_elements_); readBinaryPOD(input, cur_element_count); size_t max_elements = max_elements_i; if(max_elements < cur_element_count) max_elements = max_elements_; max_elements_ = max_elements; readBinaryPOD(input, size_data_per_element_); readBinaryPOD(input, label_offset_); readBinaryPOD(input, offsetData_); readBinaryPOD(input, maxlevel_); readBinaryPOD(input, enterpoint_node_); readBinaryPOD(input, maxM_); readBinaryPOD(input, maxM0_); readBinaryPOD(input, M_); readBinaryPOD(input, mult_); readBinaryPOD(input, ef_construction_); data_size_ = s->get_data_size(); fstdistfunc_ = s->get_dist_func(); dist_func_param_ = s->get_dist_func_param(); auto pos=input.tellg(); /// Optional - check if index is ok: input.seekg(cur_element_count * size_data_per_element_,input.cur); for (size_t i = 0; i < cur_element_count; i++) { if(input.tellg() < 0 || input.tellg()>=total_filesize){ throw std::runtime_error("Index seems to be corrupted or unsupported"); } unsigned int linkListSize; readBinaryPOD(input, linkListSize); if (linkListSize != 0) { input.seekg(linkListSize,input.cur); } } // throw exception if it either corrupted or old index if(input.tellg()!=total_filesize) throw std::runtime_error("Index seems to be corrupted or unsupported"); input.clear(); /// Optional check end input.seekg(pos,input.beg); data_level0_memory_ = (char *) malloc(max_elements * size_data_per_element_); if (data_level0_memory_ == nullptr) throw std::runtime_error("Not enough memory: loadIndex failed to allocate level0"); input.read(data_level0_memory_, cur_element_count * size_data_per_element_); size_links_per_element_ = maxM_ * sizeof(tableint) + sizeof(linklistsizeint); size_links_level0_ = maxM0_ * sizeof(tableint) + sizeof(linklistsizeint); std::vector(max_elements).swap(link_list_locks_); std::vector(max_update_element_locks).swap(link_list_update_locks_); visited_list_pool_ = new VisitedListPool(1, max_elements); linkLists_ = (char **) malloc(sizeof(void *) * max_elements); if (linkLists_ == nullptr) throw std::runtime_error("Not enough memory: loadIndex failed to allocate linklists"); element_levels_ = std::vector(max_elements); revSize_ = 1.0 / mult_; ef_ = 10; for (size_t i = 0; i < cur_element_count; i++) { label_lookup_[getExternalLabel(i)]=i; unsigned int linkListSize; readBinaryPOD(input, linkListSize); if (linkListSize == 0) { element_levels_[i] = 0; linkLists_[i] = nullptr; } else { element_levels_[i] = linkListSize / size_links_per_element_; linkLists_[i] = (char *) malloc(linkListSize); if (linkLists_[i] == nullptr) throw std::runtime_error("Not enough memory: loadIndex failed to allocate linklist"); input.read(linkLists_[i], linkListSize); } } for (size_t i = 0; i < cur_element_count; i++) { if(isMarkedDeleted(i)) num_deleted_ += 1; } input.close(); return; } template std::vector getDataByLabel(labeltype label) const { tableint label_c; auto search = label_lookup_.find(label); if (search == label_lookup_.end() || isMarkedDeleted(search->second)) { throw std::runtime_error("Label not found"); } label_c = search->second; char* data_ptrv = getDataByInternalId(label_c); size_t dim = *((size_t *) dist_func_param_); std::vector data; data_t* data_ptr = (data_t*) data_ptrv; for (int i = 0; i < dim; i++) { data.push_back(*data_ptr); data_ptr += 1; } return data; } static const unsigned char DELETE_MARK = 0x01; // static const unsigned char REUSE_MARK = 0x10; /** * Marks an element with the given label deleted, does NOT really change the current graph. * @param label */ void markDelete(labeltype label) { auto search = label_lookup_.find(label); if (search == label_lookup_.end()) { throw std::runtime_error("Label not found"); } tableint internalId = search->second; markDeletedInternal(internalId); } /** * Uses the first 8 bits of the memory for the linked list to store the mark, * whereas maxM0_ has to be limited to the lower 24 bits, however, still large enough in almost all cases. * @param internalId */ void markDeletedInternal(tableint internalId) { assert(internalId < cur_element_count); if (!isMarkedDeleted(internalId)) { unsigned char *ll_cur = ((unsigned char *)get_linklist0(internalId))+2; *ll_cur |= DELETE_MARK; num_deleted_ += 1; } else { throw std::runtime_error("The requested to delete element is already deleted"); } } /** * Remove the deleted mark of the node, does NOT really change the current graph. * @param label */ void unmarkDelete(labeltype label) { auto search = label_lookup_.find(label); if (search == label_lookup_.end()) { throw std::runtime_error("Label not found"); } tableint internalId = search->second; unmarkDeletedInternal(internalId); } /** * Remove the deleted mark of the node. * @param internalId */ void unmarkDeletedInternal(tableint internalId) { assert(internalId < cur_element_count); if (isMarkedDeleted(internalId)) { unsigned char *ll_cur = ((unsigned char *)get_linklist0(internalId))+2; *ll_cur &= ~DELETE_MARK; num_deleted_ -= 1; } else { throw std::runtime_error("The requested to undelete element is not deleted"); } } /** * Checks the first 8 bits of the memory to see if the element is marked deleted. * @param internalId * @return */ bool isMarkedDeleted(tableint internalId) const { unsigned char *ll_cur = ((unsigned char*)get_linklist0(internalId))+2; return *ll_cur & DELETE_MARK; } unsigned short int getListCount(linklistsizeint * ptr) const { return *((unsigned short int *)ptr); } void setListCount(linklistsizeint * ptr, unsigned short int size) const { *((unsigned short int*)(ptr))=*((unsigned short int *)&size); } void addPoint(const void *data_point, labeltype label, size_t batch_index) { addPoint(data_point, label, -1, batch_index); } void updatePoint(const void *dataPoint, tableint internalId, float updateNeighborProbability, const local_state_t *local_state_ptr) { // update the feature vector associated with existing point with new vector memcpy(getDataByInternalId(internalId), dataPoint, data_size_); int maxLevelCopy = maxlevel_; tableint entryPointCopy = enterpoint_node_; // If point to be updated is entry point and graph just contains single element then just return. if (entryPointCopy == internalId && cur_element_count == 1) return; int elemLevel = element_levels_[internalId]; std::uniform_real_distribution distribution(0.0, 1.0); for (int layer = 0; layer <= elemLevel; layer++) { std::unordered_set sCand; std::unordered_set sNeigh; std::vector listOneHop = getConnectionsWithLock(internalId, layer); if (listOneHop.size() == 0) continue; sCand.insert(internalId); for (auto&& elOneHop : listOneHop) { sCand.insert(elOneHop); if (distribution(update_probability_generator_) > updateNeighborProbability) continue; sNeigh.insert(elOneHop); std::vector listTwoHop = getConnectionsWithLock(elOneHop, layer); for (auto&& elTwoHop : listTwoHop) { sCand.insert(elTwoHop); } } for (auto&& neigh : sNeigh) { // if (neigh == internalId) // continue; std::priority_queue, std::vector>, CompareByFirst> candidates; size_t size = sCand.find(neigh) == sCand.end() ? sCand.size() : sCand.size() - 1; // sCand guaranteed to have size >= 1 size_t elementsToKeep = std::min(ef_construction_, size); for (auto&& cand : sCand) { if (cand == neigh) continue; dist_t distance = fstdistfunc_(getDataByInternalId(neigh), getDataByInternalId(cand), dist_func_param_, local_state_ptr); if (candidates.size() < elementsToKeep) { candidates.emplace(distance, cand); } else { if (distance < candidates.top().first) { candidates.pop(); candidates.emplace(distance, cand); } } } // Retrieve neighbours using heuristic and set connections. getNeighborsByHeuristic2(candidates, layer == 0 ? maxM0_ : maxM_, local_state_ptr); { std::unique_lock lock(link_list_locks_[neigh]); linklistsizeint *ll_cur; ll_cur = get_linklist_at_level(neigh, layer); size_t candSize = candidates.size(); setListCount(ll_cur, candSize); tableint *data = (tableint *) (ll_cur + 1); for (size_t idx = 0; idx < candSize; idx++) { data[idx] = candidates.top().second; candidates.pop(); } } } } repairConnectionsForUpdate(dataPoint, entryPointCopy, internalId, elemLevel, maxLevelCopy, local_state_ptr); }; void repairConnectionsForUpdate(const void *dataPoint, tableint entryPointInternalId, tableint dataPointInternalId, int dataPointLevel, int maxLevel, const local_state_t *local_state_ptr) { tableint currObj = entryPointInternalId; if (dataPointLevel < maxLevel) { dist_t curdist = fstdistfunc_(dataPoint, getDataByInternalId(currObj), dist_func_param_, local_state_ptr); for (int level = maxLevel; level > dataPointLevel; level--) { bool changed = true; while (changed) { changed = false; unsigned int *data; std::unique_lock lock(link_list_locks_[currObj]); data = get_linklist_at_level(currObj,level); int size = getListCount(data); tableint *datal = (tableint *) (data + 1); #ifdef USE_SSE _mm_prefetch(getDataByInternalId(*datal), _MM_HINT_T0); #endif for (int i = 0; i < size; i++) { #ifdef USE_SSE _mm_prefetch(getDataByInternalId(*(datal + i + 1)), _MM_HINT_T0); #endif tableint cand = datal[i]; dist_t d = fstdistfunc_(dataPoint, getDataByInternalId(cand), dist_func_param_, local_state_ptr); if (d < curdist) { curdist = d; currObj = cand; changed = true; } } } } } if (dataPointLevel > maxLevel) throw std::runtime_error("Level of item to be updated cannot be bigger than max level"); for (int level = dataPointLevel; level >= 0; level--) { std::priority_queue, std::vector>, CompareByFirst> topCandidates = searchBaseLayer( currObj, dataPoint, level, local_state_ptr); std::priority_queue, std::vector>, CompareByFirst> filteredTopCandidates; while (topCandidates.size() > 0) { if (topCandidates.top().second != dataPointInternalId) filteredTopCandidates.push(topCandidates.top()); topCandidates.pop(); } // Since element_levels_ is being used to get `dataPointLevel`, there could be cases where `topCandidates` could just contains entry point itself. // To prevent self loops, the `topCandidates` is filtered and thus can be empty. if (filteredTopCandidates.size() > 0) { bool epDeleted = isMarkedDeleted(entryPointInternalId); if (epDeleted) { filteredTopCandidates.emplace(fstdistfunc_(dataPoint, getDataByInternalId(entryPointInternalId), dist_func_param_, local_state_ptr), entryPointInternalId); if (filteredTopCandidates.size() > ef_construction_) filteredTopCandidates.pop(); } currObj = mutuallyConnectNewElement(dataPoint, dataPointInternalId, filteredTopCandidates, level, true, local_state_ptr); } } } std::vector getConnectionsWithLock(tableint internalId, int level) { std::unique_lock lock(link_list_locks_[internalId]); unsigned int *data = get_linklist_at_level(internalId, level); int size = getListCount(data); std::vector result(size); tableint *ll = (tableint *) (data + 1); memcpy(result.data(), ll,size * sizeof(tableint)); return result; }; tableint addPoint(const void *data_point, labeltype label, int level, size_t batch_index) { tableint cur_c = 0; local_state_t local_state; local_state.batch_index = batch_index; { // Checking if the element with the same label already exists // if so, updating it *instead* of creating a new element. std::unique_lock templock_curr(cur_element_count_guard_); auto search = label_lookup_.find(label); if (search != label_lookup_.end()) { tableint existingInternalId = search->second; templock_curr.unlock(); std::unique_lock lock_el_update(link_list_update_locks_[(existingInternalId & (max_update_element_locks - 1))]); if (isMarkedDeleted(existingInternalId)) { unmarkDeletedInternal(existingInternalId); } updatePoint(data_point, existingInternalId, 1.0, &local_state); return existingInternalId; } if (cur_element_count >= max_elements_) { throw std::runtime_error("The number of elements exceeds the specified limit"); }; cur_c = cur_element_count; cur_element_count++; label_lookup_[label] = cur_c; } // Take update lock to prevent race conditions on an element with insertion/update at the same time. std::unique_lock lock_el_update(link_list_update_locks_[(cur_c & (max_update_element_locks - 1))]); std::unique_lock lock_el(link_list_locks_[cur_c]); int curlevel = getRandomLevel(mult_); if (level > 0) curlevel = level; element_levels_[cur_c] = curlevel; std::unique_lock templock(global); int maxlevelcopy = maxlevel_; if (curlevel <= maxlevelcopy) templock.unlock(); tableint currObj = enterpoint_node_; tableint enterpoint_copy = enterpoint_node_; memset(data_level0_memory_ + cur_c * size_data_per_element_ + offsetLevel0_, 0, size_data_per_element_); // Initialisation of the data and label memcpy(getExternalLabeLp(cur_c), &label, sizeof(labeltype)); memcpy(getDataByInternalId(cur_c), data_point, data_size_); if (curlevel) { linkLists_[cur_c] = (char *) malloc(size_links_per_element_ * curlevel + 1); if (linkLists_[cur_c] == nullptr) throw std::runtime_error("Not enough memory: addPoint failed to allocate linklist"); memset(linkLists_[cur_c], 0, size_links_per_element_ * curlevel + 1); } if ((signed)currObj != -1) { if (curlevel < maxlevelcopy) { dist_t curdist = fstdistfunc_(data_point, getDataByInternalId(currObj), dist_func_param_, &local_state); for (int level = maxlevelcopy; level > curlevel; level--) { bool changed = true; while (changed) { changed = false; unsigned int *data; std::unique_lock lock(link_list_locks_[currObj]); data = get_linklist(currObj,level); int size = getListCount(data); tableint *datal = (tableint *) (data + 1); for (int i = 0; i < size; i++) { tableint cand = datal[i]; if (cand < 0 || cand > max_elements_) throw std::runtime_error("cand error"); dist_t d = fstdistfunc_(data_point, getDataByInternalId(cand), dist_func_param_, &local_state); if (d < curdist) { curdist = d; currObj = cand; changed = true; } } } } } bool epDeleted = isMarkedDeleted(enterpoint_copy); for (int level = std::min(curlevel, maxlevelcopy); level >= 0; level--) { if (level > maxlevelcopy || level < 0) // possible? throw std::runtime_error("Level error"); std::priority_queue, std::vector>, CompareByFirst> top_candidates = searchBaseLayer( currObj, data_point, level, &local_state); if (epDeleted) { top_candidates.emplace(fstdistfunc_(data_point, getDataByInternalId(enterpoint_copy), dist_func_param_, &local_state), enterpoint_copy); if (top_candidates.size() > ef_construction_) top_candidates.pop(); } currObj = mutuallyConnectNewElement(data_point, cur_c, top_candidates, level, false, &local_state); } } else { // Do nothing for the first element enterpoint_node_ = 0; maxlevel_ = curlevel; } //Releasing lock for the maximum level if (curlevel > maxlevelcopy) { enterpoint_node_ = cur_c; maxlevel_ = curlevel; } return cur_c; }; std::priority_queue> searchKnn(const void *query_data, size_t k, size_t batch_index) const { std::priority_queue> result; if (cur_element_count == 0) return result; local_state_t local_state; local_state.batch_index = batch_index; tableint currObj = enterpoint_node_; dist_t curdist = fstdistfunc_(query_data, getDataByInternalId(enterpoint_node_), dist_func_param_, &local_state); for (int level = maxlevel_; level > 0; level--) { bool changed = true; while (changed) { changed = false; unsigned int *data; data = (unsigned int *) get_linklist(currObj, level); int size = getListCount(data); metric_hops++; metric_distance_computations+=size; tableint *datal = (tableint *)(data + 1); for (int i = 0; i < size; i++) { tableint cand = datal[i]; if (cand < 0 || cand > max_elements_) throw std::runtime_error("cand error"); dist_t d = fstdistfunc_(query_data, getDataByInternalId(cand), dist_func_param_, &local_state); if (d < curdist) { curdist = d; currObj = cand; changed = true; } } } } std::priority_queue, std::vector>, CompareByFirst> top_candidates; if (num_deleted_) { top_candidates=searchBaseLayerST( currObj, query_data, std::max(ef_, k), &local_state); } else{ top_candidates=searchBaseLayerST( currObj, query_data, std::max(ef_, k), &local_state); } while (top_candidates.size() > k) { top_candidates.pop(); } while (top_candidates.size() > 0) { std::pair rez = top_candidates.top(); result.push(std::pair(rez.first, getExternalLabel(rez.second))); top_candidates.pop(); } return result; }; std::priority_queue> searchKnnWithFilter(const void *query_data, const binary_fuse16_t *filter, size_t k, size_t batch_index) const { std::priority_queue> result; if (cur_element_count == 0) return result; local_state_t local_state; local_state.batch_index = batch_index; tableint currObj = enterpoint_node_; dist_t curdist = fstdistfunc_(query_data, getDataByInternalId(enterpoint_node_), dist_func_param_, &local_state); for (int level = maxlevel_; level > 0; level--) { bool changed = true; while (changed) { changed = false; unsigned int *data; data = (unsigned int *)get_linklist(currObj, level); int size = getListCount(data); metric_hops++; metric_distance_computations += size; tableint *datal = (tableint *)(data + 1); for (int i = 0; i < size; i++) { tableint cand = datal[i]; if (cand < 0 || cand > max_elements_) throw std::runtime_error("cand error"); dist_t d = fstdistfunc_(query_data, getDataByInternalId(cand), dist_func_param_, &local_state); if (d < curdist) { curdist = d; currObj = cand; changed = true; } } } } std::priority_queue, std::vector>, CompareByFirst> top_candidates; if (has_deletions_) { top_candidates = searchBaseLayerSTWithFilter( currObj, query_data, filter, std::max(ef_, k), &local_state); } else { top_candidates = searchBaseLayerSTWithFilter( currObj, query_data, filter, std::max(ef_, k), &local_state); } while (top_candidates.size() > k) { top_candidates.pop(); } while (top_candidates.size() > 0) { std::pair rez = top_candidates.top(); result.push(std::pair(rez.first, getExternalLabel(rez.second))); top_candidates.pop(); } return result; }; void checkIntegrity(){ int connections_checked=0; std::vector inbound_connections_num(cur_element_count,0); for(int i = 0;i < cur_element_count; i++){ for(int l = 0;l <= element_levels_[i]; l++){ linklistsizeint *ll_cur = get_linklist_at_level(i,l); int size = getListCount(ll_cur); tableint *data = (tableint *) (ll_cur + 1); std::unordered_set s; for (int j=0; j 0); assert(data[j] < cur_element_count); assert (data[j] != i); inbound_connections_num[data[j]]++; s.insert(data[j]); connections_checked++; } assert(s.size() == size); } } if(cur_element_count > 1){ int min1=inbound_connections_num[0], max1=inbound_connections_num[0]; for(int i=0; i < cur_element_count; i++){ assert(inbound_connections_num[i] > 0); min1=std::min(inbound_connections_num[i],min1); max1=std::max(inbound_connections_num[i],max1); } std::cout << "Min inbound: " << min1 << ", Max inbound:" << max1 << "\n"; } std::cout << "integrity ok, checked " << connections_checked << " connections\n"; } }; } ================================================ FILE: include/hnswlib/hnswlib.h ================================================ #pragma once #ifndef NO_MANUAL_VECTORIZATION #ifdef __SSE__ #define USE_SSE #ifdef __AVX__ #define USE_AVX #ifdef __AVX512F__ #define USE_AVX512 #endif #endif #endif #endif #if defined(USE_AVX) || defined(USE_SSE) #ifdef _MSC_VER #include #include #include "cpu_x86.h" void cpu_x86::cpuid(int32_t out[4], int32_t eax, int32_t ecx) { __cpuidex(out, eax, ecx); } __int64 xgetbv(unsigned int x) { return _xgetbv(x); } #else #include #include #include void cpuid(int32_t cpuInfo[4], int32_t eax, int32_t ecx) { __cpuid_count(eax, ecx, cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]); } uint64_t xgetbv(unsigned int index) { uint32_t eax, edx; __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); return ((uint64_t)edx << 32) | eax; } #endif #if defined(USE_AVX512) #include #endif #if defined(__GNUC__) #define PORTABLE_ALIGN32 __attribute__((aligned(32))) #define PORTABLE_ALIGN64 __attribute__((aligned(64))) #else #define PORTABLE_ALIGN32 __declspec(align(32)) #define PORTABLE_ALIGN64 __declspec(align(64)) #endif // Adapted from https://github.com/Mysticial/FeatureDetector #define _XCR_XFEATURE_ENABLED_MASK 0 bool AVXCapable() { int cpuInfo[4]; // CPU support cpuid(cpuInfo, 0, 0); int nIds = cpuInfo[0]; bool HW_AVX = false; if (nIds >= 0x00000001) { cpuid(cpuInfo, 0x00000001, 0); HW_AVX = (cpuInfo[2] & ((int)1 << 28)) != 0; } // OS support cpuid(cpuInfo, 1, 0); bool osUsesXSAVE_XRSTORE = (cpuInfo[2] & (1 << 27)) != 0; bool cpuAVXSuport = (cpuInfo[2] & (1 << 28)) != 0; bool avxSupported = false; if (osUsesXSAVE_XRSTORE && cpuAVXSuport) { uint64_t xcrFeatureMask = xgetbv(_XCR_XFEATURE_ENABLED_MASK); avxSupported = (xcrFeatureMask & 0x6) == 0x6; } return HW_AVX && avxSupported; } bool AVX512Capable() { if (!AVXCapable()) return false; int cpuInfo[4]; // CPU support cpuid(cpuInfo, 0, 0); int nIds = cpuInfo[0]; bool HW_AVX512F = false; if (nIds >= 0x00000007) { // AVX512 Foundation cpuid(cpuInfo, 0x00000007, 0); HW_AVX512F = (cpuInfo[1] & ((int)1 << 16)) != 0; } // OS support cpuid(cpuInfo, 1, 0); bool osUsesXSAVE_XRSTORE = (cpuInfo[2] & (1 << 27)) != 0; bool cpuAVXSuport = (cpuInfo[2] & (1 << 28)) != 0; bool avx512Supported = false; if (osUsesXSAVE_XRSTORE && cpuAVXSuport) { uint64_t xcrFeatureMask = xgetbv(_XCR_XFEATURE_ENABLED_MASK); avx512Supported = (xcrFeatureMask & 0xe6) == 0xe6; } return HW_AVX512F && avx512Supported; } #endif #include #include #include #include namespace hnswlib { typedef size_t labeltype; typedef struct local_state_s { size_t batch_index; } local_state_t; typedef struct pq_local_data_s { float *data; size_t batch_len; } pq_local_data_t; template class pairGreater { public: bool operator()(const T& p1, const T& p2) { return p1.first > p2.first; } }; template static void writeBinaryPOD(std::ostream &out, const T &podRef) { out.write((char *) &podRef, sizeof(T)); } template static void readBinaryPOD(std::istream &in, T &podRef) { in.read((char *) &podRef, sizeof(T)); } template using DISTFUNC = MTYPE(*)(const void *, const void *, const void *, const local_state_t *); template class SpaceInterface { public: //virtual void search(void *); virtual size_t get_data_size() = 0; virtual DISTFUNC get_dist_func() = 0; virtual void attach_local_data(const void *) = 0; virtual void detach_local_data() = 0; virtual void *get_dist_func_param() = 0; virtual ~SpaceInterface() {} }; template class AlgorithmInterface { public: virtual void addPoint(const void *datapoint, labeltype label, size_t batch_index) = 0; virtual std::priority_queue> searchKnn(const void *, size_t, size_t) const = 0; // Return k nearest neighbor in the order of closer fist virtual std::vector> searchKnnCloserFirst(const void *query_data, size_t k, size_t batch_index) const; virtual void saveIndex(const std::string &location)=0; virtual ~AlgorithmInterface(){ } }; template std::vector> AlgorithmInterface::searchKnnCloserFirst(const void *query_data, size_t k, size_t batch_index) const { std::vector> result; // here searchKnn returns the result in the order of further first auto ret = searchKnn(query_data, k, batch_index); { size_t sz = ret.size(); result.resize(sz); while (!ret.empty()) { result[--sz] = ret.top(); ret.pop(); } } return result; } } #include "bruteforce.h" #include "fusefilter.h" #include "hnswalg.h" #include "space_ip.h" #include "space_l2.h" #include "space_pq.h" ================================================ FILE: include/hnswlib/space_ip.h ================================================ #pragma once #include "hnswlib.h" namespace hnswlib { static float InnerProduct(const void *pVect1, const void *pVect2, const void *qty_ptr, const local_state_t *local_state) { size_t qty = *((size_t *) qty_ptr); float res = 0; for (unsigned i = 0; i < qty; i++) { res += ((float *) pVect1)[i] * ((float *) pVect2)[i]; } return res; } static float InnerProductDistance(const void *pVect1, const void *pVect2, const void *qty_ptr, const local_state_t *local_state) { return 1.0f - InnerProduct(pVect1, pVect2, qty_ptr, local_state); } #if defined(USE_AVX) // Favor using AVX if available. static float InnerProductSIMD4ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { float PORTABLE_ALIGN32 TmpRes[8]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); size_t qty16 = qty / 16; size_t qty4 = qty / 4; const float *pEnd1 = pVect1 + 16 * qty16; const float *pEnd2 = pVect1 + 4 * qty4; __m256 sum256 = _mm256_set1_ps(0); while (pVect1 < pEnd1) { //_mm_prefetch((char*)(pVect2 + 16), _MM_HINT_T0); __m256 v1 = _mm256_loadu_ps(pVect1); pVect1 += 8; __m256 v2 = _mm256_loadu_ps(pVect2); pVect2 += 8; sum256 = _mm256_add_ps(sum256, _mm256_mul_ps(v1, v2)); v1 = _mm256_loadu_ps(pVect1); pVect1 += 8; v2 = _mm256_loadu_ps(pVect2); pVect2 += 8; sum256 = _mm256_add_ps(sum256, _mm256_mul_ps(v1, v2)); } __m128 v1, v2; __m128 sum_prod = _mm_add_ps(_mm256_extractf128_ps(sum256, 0), _mm256_extractf128_ps(sum256, 1)); while (pVect1 < pEnd2) { v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2)); } _mm_store_ps(TmpRes, sum_prod); float sum = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3];; return sum; } static float InnerProductDistanceSIMD4ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { return 1.0f - InnerProductSIMD4ExtAVX(pVect1v, pVect2v, qty_ptr, local_state); } #endif #if defined(USE_SSE) static float InnerProductSIMD4ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { float PORTABLE_ALIGN32 TmpRes[8]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); size_t qty16 = qty / 16; size_t qty4 = qty / 4; const float *pEnd1 = pVect1 + 16 * qty16; const float *pEnd2 = pVect1 + 4 * qty4; __m128 v1, v2; __m128 sum_prod = _mm_set1_ps(0); while (pVect1 < pEnd1) { v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2)); v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2)); v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2)); v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2)); } while (pVect1 < pEnd2) { v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2)); } _mm_store_ps(TmpRes, sum_prod); float sum = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3]; return sum; } static float InnerProductDistanceSIMD4ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { return 1.0f - InnerProductSIMD4ExtSSE(pVect1v, pVect2v, qty_ptr, local_state); } #endif #if defined(USE_AVX512) static float InnerProductSIMD16ExtAVX512(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { float PORTABLE_ALIGN64 TmpRes[16]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); size_t qty16 = qty / 16; const float *pEnd1 = pVect1 + 16 * qty16; __m512 sum512 = _mm512_set1_ps(0); while (pVect1 < pEnd1) { //_mm_prefetch((char*)(pVect2 + 16), _MM_HINT_T0); __m512 v1 = _mm512_loadu_ps(pVect1); pVect1 += 16; __m512 v2 = _mm512_loadu_ps(pVect2); pVect2 += 16; sum512 = _mm512_add_ps(sum512, _mm512_mul_ps(v1, v2)); } _mm512_store_ps(TmpRes, sum512); float sum = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3] + TmpRes[4] + TmpRes[5] + TmpRes[6] + TmpRes[7] + TmpRes[8] + TmpRes[9] + TmpRes[10] + TmpRes[11] + TmpRes[12] + TmpRes[13] + TmpRes[14] + TmpRes[15]; return sum; } static float InnerProductDistanceSIMD16ExtAVX512(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { return 1.0f - InnerProductSIMD16ExtAVX512(pVect1v, pVect2v, qty_ptr, local_state); } #endif #if defined(USE_AVX) static float InnerProductSIMD16ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { float PORTABLE_ALIGN32 TmpRes[8]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); size_t qty16 = qty / 16; const float *pEnd1 = pVect1 + 16 * qty16; __m256 sum256 = _mm256_set1_ps(0); while (pVect1 < pEnd1) { //_mm_prefetch((char*)(pVect2 + 16), _MM_HINT_T0); __m256 v1 = _mm256_loadu_ps(pVect1); pVect1 += 8; __m256 v2 = _mm256_loadu_ps(pVect2); pVect2 += 8; sum256 = _mm256_add_ps(sum256, _mm256_mul_ps(v1, v2)); v1 = _mm256_loadu_ps(pVect1); pVect1 += 8; v2 = _mm256_loadu_ps(pVect2); pVect2 += 8; sum256 = _mm256_add_ps(sum256, _mm256_mul_ps(v1, v2)); } _mm256_store_ps(TmpRes, sum256); float sum = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3] + TmpRes[4] + TmpRes[5] + TmpRes[6] + TmpRes[7]; return sum; } static float InnerProductDistanceSIMD16ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { return 1.0f - InnerProductSIMD16ExtAVX(pVect1v, pVect2v, qty_ptr, local_state); } #endif #if defined(USE_SSE) static float InnerProductSIMD16ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { float PORTABLE_ALIGN32 TmpRes[8]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); size_t qty16 = qty / 16; const float *pEnd1 = pVect1 + 16 * qty16; __m128 v1, v2; __m128 sum_prod = _mm_set1_ps(0); while (pVect1 < pEnd1) { v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2)); v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2)); v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2)); v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; sum_prod = _mm_add_ps(sum_prod, _mm_mul_ps(v1, v2)); } _mm_store_ps(TmpRes, sum_prod); float sum = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3]; return sum; } static float InnerProductDistanceSIMD16ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { return 1.0f - InnerProductSIMD16ExtSSE(pVect1v, pVect2v, qty_ptr, local_state); } #endif #if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512) DISTFUNC InnerProductSIMD16Ext = InnerProductSIMD16ExtSSE; DISTFUNC InnerProductSIMD4Ext = InnerProductSIMD4ExtSSE; DISTFUNC InnerProductDistanceSIMD16Ext = InnerProductDistanceSIMD16ExtSSE; DISTFUNC InnerProductDistanceSIMD4Ext = InnerProductDistanceSIMD4ExtSSE; static float InnerProductDistanceSIMD16ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { size_t qty = *((size_t *) qty_ptr); size_t qty16 = qty >> 4 << 4; float res = InnerProductSIMD16Ext(pVect1v, pVect2v, &qty16, local_state); float *pVect1 = (float *) pVect1v + qty16; float *pVect2 = (float *) pVect2v + qty16; size_t qty_left = qty - qty16; float res_tail = InnerProduct(pVect1, pVect2, &qty_left, local_state); return 1.0f - (res + res_tail); } static float InnerProductDistanceSIMD4ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { size_t qty = *((size_t *) qty_ptr); size_t qty4 = qty >> 2 << 2; float res = InnerProductSIMD4Ext(pVect1v, pVect2v, &qty4, local_state); size_t qty_left = qty - qty4; float *pVect1 = (float *) pVect1v + qty4; float *pVect2 = (float *) pVect2v + qty4; float res_tail = InnerProduct(pVect1, pVect2, &qty_left, local_state); return 1.0f - (res + res_tail); } #endif class InnerProductSpace : public SpaceInterface { DISTFUNC fstdistfunc_; size_t data_size_; size_t dim_; public: InnerProductSpace(size_t dim) { fstdistfunc_ = InnerProductDistance; #if defined(USE_AVX) || defined(USE_SSE) || defined(USE_AVX512) #if defined(USE_AVX512) if (AVX512Capable()) { InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX512; InnerProductDistanceSIMD16Ext = InnerProductDistanceSIMD16ExtAVX512; } else if (AVXCapable()) { InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX; InnerProductDistanceSIMD16Ext = InnerProductDistanceSIMD16ExtAVX; } #elif defined(USE_AVX) if (AVXCapable()) { InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX; InnerProductDistanceSIMD16Ext = InnerProductDistanceSIMD16ExtAVX; } #endif #if defined(USE_AVX) if (AVXCapable()) { InnerProductSIMD4Ext = InnerProductSIMD4ExtAVX; InnerProductDistanceSIMD4Ext = InnerProductDistanceSIMD4ExtAVX; } #endif if (dim % 16 == 0) fstdistfunc_ = InnerProductDistanceSIMD16Ext; else if (dim % 4 == 0) fstdistfunc_ = InnerProductDistanceSIMD4Ext; else if (dim > 16) fstdistfunc_ = InnerProductDistanceSIMD16ExtResiduals; else if (dim > 4) fstdistfunc_ = InnerProductDistanceSIMD4ExtResiduals; #endif dim_ = dim; data_size_ = dim * sizeof(float); } size_t get_data_size() { return data_size_; } DISTFUNC get_dist_func() { return fstdistfunc_; } void *get_dist_func_param() { return &dim_; } // Not local state void attach_local_data(const void *) {} void detach_local_data() {} ~InnerProductSpace() {} }; } ================================================ FILE: include/hnswlib/space_l2.h ================================================ #pragma once #include "hnswlib.h" namespace hnswlib { static float L2Sqr(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); float res = 0; for (size_t i = 0; i < qty; i++) { float t = *pVect1 - *pVect2; pVect1++; pVect2++; res += t * t; } return (res); } #if defined(USE_AVX512) // Favor using AVX512 if available. static float L2SqrSIMD16ExtAVX512(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); float PORTABLE_ALIGN64 TmpRes[16]; size_t qty16 = qty >> 4; const float *pEnd1 = pVect1 + (qty16 << 4); __m512 diff, v1, v2; __m512 sum = _mm512_set1_ps(0); while (pVect1 < pEnd1) { v1 = _mm512_loadu_ps(pVect1); pVect1 += 16; v2 = _mm512_loadu_ps(pVect2); pVect2 += 16; diff = _mm512_sub_ps(v1, v2); // sum = _mm512_fmadd_ps(diff, diff, sum); sum = _mm512_add_ps(sum, _mm512_mul_ps(diff, diff)); } _mm512_store_ps(TmpRes, sum); float res = TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3] + TmpRes[4] + TmpRes[5] + TmpRes[6] + TmpRes[7] + TmpRes[8] + TmpRes[9] + TmpRes[10] + TmpRes[11] + TmpRes[12] + TmpRes[13] + TmpRes[14] + TmpRes[15]; return (res); } #endif #if defined(USE_AVX) // Favor using AVX if available. static float L2SqrSIMD16ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); float PORTABLE_ALIGN32 TmpRes[8]; size_t qty16 = qty >> 4; const float *pEnd1 = pVect1 + (qty16 << 4); __m256 diff, v1, v2; __m256 sum = _mm256_set1_ps(0); while (pVect1 < pEnd1) { v1 = _mm256_loadu_ps(pVect1); pVect1 += 8; v2 = _mm256_loadu_ps(pVect2); pVect2 += 8; diff = _mm256_sub_ps(v1, v2); sum = _mm256_add_ps(sum, _mm256_mul_ps(diff, diff)); v1 = _mm256_loadu_ps(pVect1); pVect1 += 8; v2 = _mm256_loadu_ps(pVect2); pVect2 += 8; diff = _mm256_sub_ps(v1, v2); sum = _mm256_add_ps(sum, _mm256_mul_ps(diff, diff)); } _mm256_store_ps(TmpRes, sum); return TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3] + TmpRes[4] + TmpRes[5] + TmpRes[6] + TmpRes[7]; } #endif #if defined(USE_SSE) static float L2SqrSIMD16ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); float PORTABLE_ALIGN32 TmpRes[8]; size_t qty16 = qty >> 4; const float *pEnd1 = pVect1 + (qty16 << 4); __m128 diff, v1, v2; __m128 sum = _mm_set1_ps(0); while (pVect1 < pEnd1) { //_mm_prefetch((char*)(pVect2 + 16), _MM_HINT_T0); v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; diff = _mm_sub_ps(v1, v2); sum = _mm_add_ps(sum, _mm_mul_ps(diff, diff)); v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; diff = _mm_sub_ps(v1, v2); sum = _mm_add_ps(sum, _mm_mul_ps(diff, diff)); v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; diff = _mm_sub_ps(v1, v2); sum = _mm_add_ps(sum, _mm_mul_ps(diff, diff)); v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; diff = _mm_sub_ps(v1, v2); sum = _mm_add_ps(sum, _mm_mul_ps(diff, diff)); } _mm_store_ps(TmpRes, sum); return TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3]; } #endif #if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512) DISTFUNC L2SqrSIMD16Ext = L2SqrSIMD16ExtSSE; static float L2SqrSIMD16ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { size_t qty = *((size_t *) qty_ptr); size_t qty16 = qty >> 4 << 4; float res = L2SqrSIMD16Ext(pVect1v, pVect2v, &qty16, local_state); float *pVect1 = (float *) pVect1v + qty16; float *pVect2 = (float *) pVect2v + qty16; size_t qty_left = qty - qty16; float res_tail = L2Sqr(pVect1, pVect2, &qty_left, local_state); return (res + res_tail); } #endif #if defined(USE_SSE) static float L2SqrSIMD4Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { float PORTABLE_ALIGN32 TmpRes[8]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); size_t qty4 = qty >> 2; const float *pEnd1 = pVect1 + (qty4 << 2); __m128 diff, v1, v2; __m128 sum = _mm_set1_ps(0); while (pVect1 < pEnd1) { v1 = _mm_loadu_ps(pVect1); pVect1 += 4; v2 = _mm_loadu_ps(pVect2); pVect2 += 4; diff = _mm_sub_ps(v1, v2); sum = _mm_add_ps(sum, _mm_mul_ps(diff, diff)); } _mm_store_ps(TmpRes, sum); return TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3]; } static float L2SqrSIMD4ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { size_t qty = *((size_t *) qty_ptr); size_t qty4 = qty >> 2 << 2; float res = L2SqrSIMD4Ext(pVect1v, pVect2v, &qty4, local_state); size_t qty_left = qty - qty4; float *pVect1 = (float *) pVect1v + qty4; float *pVect2 = (float *) pVect2v + qty4; float res_tail = L2Sqr(pVect1, pVect2, &qty_left, local_state); return (res + res_tail); } #endif class L2Space : public SpaceInterface { DISTFUNC fstdistfunc_; size_t data_size_; size_t dim_; public: L2Space(size_t dim) { fstdistfunc_ = L2Sqr; #if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512) #if defined(USE_AVX512) if (AVX512Capable()) L2SqrSIMD16Ext = L2SqrSIMD16ExtAVX512; else if (AVXCapable()) L2SqrSIMD16Ext = L2SqrSIMD16ExtAVX; #elif defined(USE_AVX) if (AVXCapable()) L2SqrSIMD16Ext = L2SqrSIMD16ExtAVX; #endif if (dim % 16 == 0) fstdistfunc_ = L2SqrSIMD16Ext; else if (dim % 4 == 0) fstdistfunc_ = L2SqrSIMD4Ext; else if (dim > 16) fstdistfunc_ = L2SqrSIMD16ExtResiduals; else if (dim > 4) fstdistfunc_ = L2SqrSIMD4ExtResiduals; #endif dim_ = dim; data_size_ = dim * sizeof(float); } size_t get_data_size() { return data_size_; } DISTFUNC get_dist_func() { return fstdistfunc_; } void *get_dist_func_param() { return &dim_; } // Not local state void attach_local_data(const void *) {} void detach_local_data() {} ~L2Space() {} }; static int L2SqrI4x(const void *__restrict pVect1, const void *__restrict pVect2, const void *__restrict qty_ptr, const local_state_t *__restrict local_state) { size_t qty = *((size_t *) qty_ptr); int res = 0; unsigned char *a = (unsigned char *) pVect1; unsigned char *b = (unsigned char *) pVect2; qty = qty >> 2; for (size_t i = 0; i < qty; i++) { res += ((*a) - (*b)) * ((*a) - (*b)); a++; b++; res += ((*a) - (*b)) * ((*a) - (*b)); a++; b++; res += ((*a) - (*b)) * ((*a) - (*b)); a++; b++; res += ((*a) - (*b)) * ((*a) - (*b)); a++; b++; } return (res); } static int L2SqrI(const void* __restrict pVect1, const void* __restrict pVect2, const void* __restrict qty_ptr, const local_state_t *__restrict local_state) { size_t qty = *((size_t*)qty_ptr); int res = 0; unsigned char* a = (unsigned char*)pVect1; unsigned char* b = (unsigned char*)pVect2; for(size_t i = 0; i < qty; i++) { res += ((*a) - (*b)) * ((*a) - (*b)); a++; b++; } return (res); } class L2SpaceI : public SpaceInterface { DISTFUNC fstdistfunc_; size_t data_size_; size_t dim_; public: L2SpaceI(size_t dim) { if(dim % 4 == 0) { fstdistfunc_ = L2SqrI4x; } else { fstdistfunc_ = L2SqrI; } dim_ = dim; data_size_ = dim * sizeof(unsigned char); } size_t get_data_size() { return data_size_; } DISTFUNC get_dist_func() { return fstdistfunc_; } void *get_dist_func_param() { return &dim_; } // Not local state void attach_local_data(const void *) {} void detach_local_data() {} ~L2SpaceI() {} }; } ================================================ FILE: include/hnswlib/space_pq.h ================================================ #pragma once #include "hnswlib.h" #include #include #include namespace hnswlib { typedef struct pq_dist_param_s { size_t n_subvectors; size_t n_clusters; size_t batch_len; float *batch_dtable; } pq_dist_param_t; template static float PQLookup(const void *pVect1v, const void *pVect2v, const void *qty_ptr, const local_state_t *local_state) { CODETYPE *pVect2 = (CODETYPE *)pVect2v; pq_dist_param_t *qty = (pq_dist_param_t *)qty_ptr; if (qty->batch_len <= local_state->batch_index || qty->batch_dtable == nullptr) { // Since all the batch_index actually manage by us throw std::runtime_error("Row index exceeds or batch distance table " "uninitialized, most likely an internal bug!"); } size_t n_clusters = qty->n_clusters; size_t row_step = (qty->n_clusters * qty->n_subvectors) * local_state->batch_index; const float *dtable = qty->batch_dtable; float res = 0; for (size_t i = 0; i < qty->n_subvectors; i++) { res += dtable[row_step + i * n_clusters + (*pVect2)]; pVect2++; } return res; } template class PQ_Space : public SpaceInterface { DISTFUNC fstdistfunc_; size_t data_size_, d_subvectors; pq_dist_param_t param; bool ip_enable; public: PQ_Space(const std::string &space_name, size_t n_subvectors, size_t n_clusters, size_t d_subvectors, float *codebook) : d_subvectors(d_subvectors) { param.n_subvectors = n_subvectors; param.n_clusters = n_clusters; data_size_ = n_subvectors * sizeof(CODETYPE); fstdistfunc_ = PQLookup; } void attach_local_data(const void *local_data) { hnswlib::pq_local_data_t *pq_param = (hnswlib::pq_local_data_t *)local_data; param.batch_dtable = pq_param->data; param.batch_len = pq_param->batch_len; } void detach_local_data() { param.batch_dtable = nullptr; param.batch_len = 0; }; size_t get_data_size() { return data_size_; } DISTFUNC get_dist_func() { return fstdistfunc_; } void *get_dist_func_param() { return ¶m; } ~PQ_Space() {} }; } // namespace hnswlib ================================================ FILE: include/hnswlib/visited_list_pool.h ================================================ #pragma once #include #include #include namespace hnswlib { typedef unsigned short int vl_type; class VisitedList { public: vl_type curV; vl_type *mass; unsigned int numelements; VisitedList(int numelements1) { curV = -1; numelements = numelements1; mass = new vl_type[numelements]; } void reset() { curV++; if (curV == 0) { memset(mass, 0, sizeof(vl_type) * numelements); curV++; } }; ~VisitedList() { delete[] mass; } }; /////////////////////////////////////////////////////////// // // Class for multi-threaded pool-management of VisitedLists // ///////////////////////////////////////////////////////// class VisitedListPool { std::deque pool; std::mutex poolguard; int numelements; public: VisitedListPool(int initmaxpools, int numelements1) { numelements = numelements1; for (int i = 0; i < initmaxpools; i++) pool.push_front(new VisitedList(numelements)); } VisitedList *getFreeVisitedList() { VisitedList *rez; { std::unique_lock lock(poolguard); if (pool.size() > 0) { rez = pool.front(); pool.pop_front(); } else { rez = new VisitedList(numelements); } } rez->reset(); return rez; }; void releaseVisitedList(VisitedList *vl) { std::unique_lock lock(poolguard); pool.push_front(vl); }; ~VisitedListPool() { while (pool.size()) { VisitedList *rez = pool.front(); pool.pop_front(); delete rez; } }; }; } ================================================ FILE: notebooks/fashion_product_search.ipynb ================================================ { "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "name": "fashion_product_search.ipynb", "provenance": [], "collapsed_sections": [] }, "kernelspec": { "name": "python3", "display_name": "Python 3" }, "language_info": { "name": "python" } }, "cells": [ { "cell_type": "markdown", "source": [ "

\"Colaboratory

\n", "\n", "

AnnLite Powered E-commerce Product Search

\n", "\n", "
\n", " \n", "\"Open\n", "
" ], "metadata": { "id": "sXBlcqX4sxY9" } }, { "cell_type": "markdown", "source": [ "## What is AnnLite?\n", "\n", "`AnnLite` is an **Approximate Nearest Neighbor Search** (ANNS) library integrated with the Jina ecosystem.\n", "\n", "\n", "This indexer is recommended to be used when an application requires **search with filters** applied on `Document` tags.\n", "The `filtering query language` is based on [MongoDB's query and projection operators](https://docs.mongodb.com/manual/reference/operator/query/). We currently support a subset of those selectors.\n", "The tags filters can be combined with `$and` and `$or`:\n", "\n", "- `$eq` - Equal to (number, string)\n", "- `$ne` - Not equal to (number, string)\n", "- `$gt` - Greater than (number)\n", "- `$gte` - Greater than or equal to (number)\n", "- `$lt` - Less than (number)\n", "- `$lte` - Less than or equal to (number)\n", "\n", "For example, we want to search for a product with a price no more than `50$`.\n", "```python\n", "index.search(query, filter={\"price\": {\"$lte\": 50}})\n", "```\n" ], "metadata": { "id": "nIbrboKnckJG" } }, { "cell_type": "markdown", "metadata": { "id": "_ym-WauGOsto" }, "source": [ "**Building a Neural Search System for Ecommerce Product Search**\n", "\n", "In this tutorial, we will use **annlite** as indexer to build a neural search engine for images of [Fasion Product Image (Small)](https://www.kaggle.com/paramaggarwal/fashion-product-images-small). This examples allows for **appling filters on various product attributes** when performing similarity search.\n", "\n", "![image](https://storage.googleapis.com/kaggle-datasets-images/175990/396802/720cd7ceb25eb130d0b873464f734370/data-original.png)\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "KpUu97Fq7v4T" }, "source": [ "---\n", "\n", "## Preliminaries\n", "\n" ] }, { "cell_type": "markdown", "source": [ "**Download dataset**\n", "\n", "*Skip this if you've already downloaded them.*\n" ], "metadata": { "id": "uX3E944Mt_vq" } }, { "cell_type": "code", "metadata": { "id": "uou66Zcw7X8g", "colab": { "base_uri": "https://localhost:8080/" }, "outputId": "5ee59044-c0d8-42b0-e7bb-47aa978ffd8e" }, "source": [ "!rm -rf data\n", "!pip install gdown\n", "\n", "import gdown\n", "\n", "# a file\n", "url = \"https://drive.google.com/file/d/1p26I3AaFO8bLU1PzDlGKUdM7eBVDrLjm/view?usp=sharing\"\n", "output = \"fashion.zip\"\n", "gdown.download(url, output, quiet=False, fuzzy=True)\n", "\n", "!unzip fashion.zip -d data > /dev/null" ], "execution_count": 1, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Requirement already satisfied: gdown in /usr/local/lib/python3.7/dist-packages (4.2.2)\n", "Requirement already satisfied: tqdm in /usr/local/lib/python3.7/dist-packages (from gdown) (4.63.0)\n", "Requirement already satisfied: beautifulsoup4 in /usr/local/lib/python3.7/dist-packages (from gdown) (4.6.3)\n", "Requirement already satisfied: requests[socks] in /usr/local/lib/python3.7/dist-packages (from gdown) (2.23.0)\n", "Requirement already satisfied: filelock in /usr/local/lib/python3.7/dist-packages (from gdown) (3.6.0)\n", "Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from gdown) (1.15.0)\n", "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests[socks]->gdown) (1.24.3)\n", "Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests[socks]->gdown) (3.0.4)\n", "Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests[socks]->gdown) (2.10)\n", "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests[socks]->gdown) (2021.10.8)\n", "Requirement already satisfied: PySocks!=1.5.7,>=1.5.6 in /usr/local/lib/python3.7/dist-packages (from requests[socks]->gdown) (1.7.1)\n" ] }, { "output_type": "stream", "name": "stderr", "text": [ "Downloading...\n", "From: https://drive.google.com/uc?id=1p26I3AaFO8bLU1PzDlGKUdM7eBVDrLjm\n", "To: /content/fashion.zip\n", "100%|██████████| 6.55M/6.55M [00:01<00:00, 5.63MB/s]\n" ] } ] }, { "cell_type": "markdown", "source": [ "**Install dependencies**" ], "metadata": { "id": "Q32auOxQtKX2" } }, { "cell_type": "code", "source": [ "!pip install pandas\n", "!pip install Pillow\n", "!pip install matplotlib\n", "!pip install torchvision\n", "\n", "!pip install jina\n", "!pip install annlite" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Z4jpZDCCIubb", "outputId": "238a4f82-26a9-4783-a273-a19a2dce9e58" }, "execution_count": 2, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (1.3.5)\n", "Requirement already satisfied: pytz>=2017.3 in /usr/local/lib/python3.7/dist-packages (from pandas) (2018.9)\n", "Requirement already satisfied: numpy>=1.17.3 in /usr/local/lib/python3.7/dist-packages (from pandas) (1.21.5)\n", "Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.7/dist-packages (from pandas) (2.8.2)\n", "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.7.3->pandas) (1.15.0)\n", "Requirement already satisfied: Pillow in /usr/local/lib/python3.7/dist-packages (7.1.2)\n", "Requirement already satisfied: matplotlib in /usr/local/lib/python3.7/dist-packages (3.2.2)\n", "Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib) (3.0.7)\n", "Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib) (0.11.0)\n", "Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib) (1.3.2)\n", "Requirement already satisfied: numpy>=1.11 in /usr/local/lib/python3.7/dist-packages (from matplotlib) (1.21.5)\n", "Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib) (2.8.2)\n", "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.1->matplotlib) (1.15.0)\n", "Requirement already satisfied: torchvision in /usr/local/lib/python3.7/dist-packages (0.11.1+cu111)\n", "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from torchvision) (1.21.5)\n", "Requirement already satisfied: pillow!=8.3.0,>=5.3.0 in /usr/local/lib/python3.7/dist-packages (from torchvision) (7.1.2)\n", "Requirement already satisfied: torch==1.10.0 in /usr/local/lib/python3.7/dist-packages (from torchvision) (1.10.0+cu111)\n", "Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from torch==1.10.0->torchvision) (3.10.0.2)\n", "Requirement already satisfied: jina in /usr/local/lib/python3.7/dist-packages (3.0.4)\n", "Requirement already satisfied: filelock in /usr/local/lib/python3.7/dist-packages (from jina) (3.6.0)\n", "Requirement already satisfied: aiofiles in /usr/local/lib/python3.7/dist-packages (from jina) (0.8.0)\n", "Requirement already satisfied: pydantic in /usr/local/lib/python3.7/dist-packages (from jina) (1.9.0)\n", "Requirement already satisfied: uvicorn[standard] in /usr/local/lib/python3.7/dist-packages (from jina) (0.17.5)\n", "Requirement already satisfied: aiostream in /usr/local/lib/python3.7/dist-packages (from jina) (0.4.4)\n", "Requirement already satisfied: protobuf>=3.19.1 in /usr/local/lib/python3.7/dist-packages (from jina) (3.19.4)\n", "Requirement already satisfied: fastapi in /usr/local/lib/python3.7/dist-packages (from jina) (0.74.1)\n", "Requirement already satisfied: rich in /usr/local/lib/python3.7/dist-packages (from jina) (11.2.0)\n", "Requirement already satisfied: websockets in /usr/local/lib/python3.7/dist-packages (from jina) (10.2)\n", "Requirement already satisfied: lz4<3.1.2 in /usr/local/lib/python3.7/dist-packages (from jina) (3.1.1)\n", "Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from jina) (2.23.0)\n", "Requirement already satisfied: cryptography in /usr/local/lib/python3.7/dist-packages (from jina) (36.0.1)\n", "Requirement already satisfied: aiohttp in /usr/local/lib/python3.7/dist-packages (from jina) (3.8.1)\n", "Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.7/dist-packages (from jina) (21.3)\n", "Requirement already satisfied: pathspec in /usr/local/lib/python3.7/dist-packages (from jina) (0.9.0)\n", "Requirement already satisfied: grpcio>=1.33.1 in /usr/local/lib/python3.7/dist-packages (from jina) (1.44.0)\n", "Requirement already satisfied: python-multipart in /usr/local/lib/python3.7/dist-packages (from jina) (0.0.5)\n", "Requirement already satisfied: kubernetes>=18.20.0 in /usr/local/lib/python3.7/dist-packages (from jina) (23.3.0)\n", "Requirement already satisfied: docarray>=0.7.0 in /usr/local/lib/python3.7/dist-packages (from jina) (0.8.11)\n", "Requirement already satisfied: pyyaml>=5.3.1 in /usr/local/lib/python3.7/dist-packages (from jina) (6.0)\n", "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from jina) (1.21.5)\n", "Requirement already satisfied: uvloop in /usr/local/lib/python3.7/dist-packages (from jina) (0.16.0)\n", "Requirement already satisfied: docker in /usr/local/lib/python3.7/dist-packages (from jina) (5.0.3)\n", "Requirement already satisfied: six>=1.5.2 in /usr/local/lib/python3.7/dist-packages (from grpcio>=1.33.1->jina) (1.15.0)\n", "Requirement already satisfied: urllib3>=1.24.2 in /usr/local/lib/python3.7/dist-packages (from kubernetes>=18.20.0->jina) (1.24.3)\n", "Requirement already satisfied: setuptools>=21.0.0 in /usr/local/lib/python3.7/dist-packages (from kubernetes>=18.20.0->jina) (57.4.0)\n", "Requirement already satisfied: google-auth>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from kubernetes>=18.20.0->jina) (1.35.0)\n", "Requirement already satisfied: websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 in /usr/local/lib/python3.7/dist-packages (from kubernetes>=18.20.0->jina) (1.3.1)\n", "Requirement already satisfied: python-dateutil>=2.5.3 in /usr/local/lib/python3.7/dist-packages (from kubernetes>=18.20.0->jina) (2.8.2)\n", "Requirement already satisfied: requests-oauthlib in /usr/local/lib/python3.7/dist-packages (from kubernetes>=18.20.0->jina) (1.3.1)\n", "Requirement already satisfied: certifi>=14.05.14 in /usr/local/lib/python3.7/dist-packages (from kubernetes>=18.20.0->jina) (2021.10.8)\n", "Requirement already satisfied: rsa<5,>=3.1.4 in /usr/local/lib/python3.7/dist-packages (from google-auth>=1.0.1->kubernetes>=18.20.0->jina) (4.8)\n", "Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/lib/python3.7/dist-packages (from google-auth>=1.0.1->kubernetes>=18.20.0->jina) (0.2.8)\n", "Requirement already satisfied: cachetools<5.0,>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from google-auth>=1.0.1->kubernetes>=18.20.0->jina) (4.2.4)\n", "Requirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from packaging>=20.0->jina) (3.0.7)\n", "Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /usr/local/lib/python3.7/dist-packages (from pyasn1-modules>=0.2.1->google-auth>=1.0.1->kubernetes>=18.20.0->jina) (0.4.8)\n", "Requirement already satisfied: charset-normalizer<3.0,>=2.0 in /usr/local/lib/python3.7/dist-packages (from aiohttp->jina) (2.0.12)\n", "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.7/dist-packages (from aiohttp->jina) (6.0.2)\n", "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.7/dist-packages (from aiohttp->jina) (1.3.0)\n", "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.7/dist-packages (from aiohttp->jina) (1.7.2)\n", "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.7/dist-packages (from aiohttp->jina) (1.2.0)\n", "Requirement already satisfied: typing-extensions>=3.7.4 in /usr/local/lib/python3.7/dist-packages (from aiohttp->jina) (3.10.0.2)\n", "Requirement already satisfied: asynctest==0.13.0 in /usr/local/lib/python3.7/dist-packages (from aiohttp->jina) (0.13.0)\n", "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.7/dist-packages (from aiohttp->jina) (21.4.0)\n", "Requirement already satisfied: async-timeout<5.0,>=4.0.0a3 in /usr/local/lib/python3.7/dist-packages (from aiohttp->jina) (4.0.2)\n", "Requirement already satisfied: idna>=2.0 in /usr/local/lib/python3.7/dist-packages (from yarl<2.0,>=1.0->aiohttp->jina) (2.10)\n", "Requirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.7/dist-packages (from cryptography->jina) (1.15.0)\n", "Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.12->cryptography->jina) (2.21)\n", "Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->jina) (3.0.4)\n", "Requirement already satisfied: starlette==0.17.1 in /usr/local/lib/python3.7/dist-packages (from fastapi->jina) (0.17.1)\n", "Requirement already satisfied: anyio<4,>=3.0.0 in /usr/local/lib/python3.7/dist-packages (from starlette==0.17.1->fastapi->jina) (3.5.0)\n", "Requirement already satisfied: sniffio>=1.1 in /usr/local/lib/python3.7/dist-packages (from anyio<4,>=3.0.0->starlette==0.17.1->fastapi->jina) (1.2.0)\n", "Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/lib/python3.7/dist-packages (from requests-oauthlib->kubernetes>=18.20.0->jina) (3.2.0)\n", "Requirement already satisfied: pygments<3.0.0,>=2.6.0 in /usr/local/lib/python3.7/dist-packages (from rich->jina) (2.6.1)\n", "Requirement already satisfied: colorama<0.5.0,>=0.4.0 in /usr/local/lib/python3.7/dist-packages (from rich->jina) (0.4.4)\n", "Requirement already satisfied: commonmark<0.10.0,>=0.9.0 in /usr/local/lib/python3.7/dist-packages (from rich->jina) (0.9.1)\n", "Requirement already satisfied: asgiref>=3.4.0 in /usr/local/lib/python3.7/dist-packages (from uvicorn[standard]->jina) (3.5.0)\n", "Requirement already satisfied: h11>=0.8 in /usr/local/lib/python3.7/dist-packages (from uvicorn[standard]->jina) (0.13.0)\n", "Requirement already satisfied: click>=7.0 in /usr/local/lib/python3.7/dist-packages (from uvicorn[standard]->jina) (7.1.2)\n", "Requirement already satisfied: watchgod>=0.6 in /usr/local/lib/python3.7/dist-packages (from uvicorn[standard]->jina) (0.7)\n", "Requirement already satisfied: python-dotenv>=0.13 in /usr/local/lib/python3.7/dist-packages (from uvicorn[standard]->jina) (0.19.2)\n", "Requirement already satisfied: httptools<0.4.0,>=0.2.0 in /usr/local/lib/python3.7/dist-packages (from uvicorn[standard]->jina) (0.3.0)\n", "Requirement already satisfied: annlite in /usr/local/lib/python3.7/dist-packages (0.3.0)\n", "Requirement already satisfied: protobuf>=3.13.0 in /usr/local/lib/python3.7/dist-packages (from annlite) (3.19.4)\n", "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from annlite) (1.21.5)\n", "Requirement already satisfied: cython in /usr/local/lib/python3.7/dist-packages (from annlite) (0.29.28)\n", "Requirement already satisfied: click in /usr/local/lib/python3.7/dist-packages (from annlite) (7.1.2)\n", "Requirement already satisfied: docarray>=0.4.4 in /usr/local/lib/python3.7/dist-packages (from annlite) (0.8.11)\n", "Requirement already satisfied: loguru in /usr/local/lib/python3.7/dist-packages (from annlite) (0.6.0)\n", "Requirement already satisfied: lmdb in /usr/local/lib/python3.7/dist-packages (from annlite) (0.99)\n", "Requirement already satisfied: scikit-learn in /usr/local/lib/python3.7/dist-packages (from annlite) (1.0.2)\n", "Requirement already satisfied: rich in /usr/local/lib/python3.7/dist-packages (from docarray>=0.4.4->annlite) (11.2.0)\n", "Requirement already satisfied: colorama<0.5.0,>=0.4.0 in /usr/local/lib/python3.7/dist-packages (from rich->docarray>=0.4.4->annlite) (0.4.4)\n", "Requirement already satisfied: typing-extensions<5.0,>=3.7.4 in /usr/local/lib/python3.7/dist-packages (from rich->docarray>=0.4.4->annlite) (3.10.0.2)\n", "Requirement already satisfied: commonmark<0.10.0,>=0.9.0 in /usr/local/lib/python3.7/dist-packages (from rich->docarray>=0.4.4->annlite) (0.9.1)\n", "Requirement already satisfied: pygments<3.0.0,>=2.6.0 in /usr/local/lib/python3.7/dist-packages (from rich->docarray>=0.4.4->annlite) (2.6.1)\n", "Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->annlite) (3.1.0)\n", "Requirement already satisfied: scipy>=1.1.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->annlite) (1.4.1)\n", "Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->annlite) (1.1.0)\n" ] } ] }, { "cell_type": "markdown", "source": [ "**Prepare workspace**" ], "metadata": { "id": "9u4eqV4WtQAG" } }, { "cell_type": "code", "metadata": { "id": "lJ9ul55u4HOC" }, "source": [ "!rm -rf workspace\n", "!mkdir workspace" ], "execution_count": 3, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "oZXKzbcd_GD7" }, "source": [ "---\n", "\n", "# Using **AnnLite** in E-Commerce Product Image Search\n" ] }, { "cell_type": "code", "metadata": { "id": "r-dNxUGqEVzS", "outputId": "8ab65e78-fb52-447a-8898-0d84fe747ae2", "colab": { "base_uri": "https://localhost:8080/" } }, "source": [ "import os\n", "import glob\n", "\n", "import pandas as pd\n", "\n", "from PIL import Image\n", "from jina import Document, DocumentArray\n", "\n", "from matplotlib import pyplot as plt\n", "from matplotlib.pyplot import imshow\n", "\n", "from annlite import AnnLite\n", "\n", "os.environ['JINA_LOG_LEVEL'] = 'DEBUG'" ], "execution_count": 4, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[1;33mDeprecationWarning: The module numpy.dual is deprecated. Instead of using dual, use the functions directly from numpy or scipy.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/fft/__init__.py:97)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.typeDict` is a deprecated alias for `np.sctypeDict`.\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/sparse/sputils.py:17)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.int` is a deprecated alias for the builtin `int`. To silence this warning, use `int` by itself. Doing this will not modify any behavior and is safe. When replacing `np.int`, you may wish to use e.g. `np.int64` or `np.int32` to specify the precision. If you wish to review your current use, check the release note link for additional information.\n", "Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/special/orthogonal.py:81)\u001b[0m\n", "\u001b[1;33mDeprecationWarning: `np.int` is a deprecated alias for the builtin `int`. To silence this warning, use `int` by itself. Doing this will not modify any behavior and is safe. When replacing `np.int`, you may wish to use e.g. `np.int64` or `np.int32` to specify the precision. If you wish to review your current use, check the release note link for additional information.\n", "Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/scipy/special/orthogonal.py:81)\u001b[0m\n" ] } ] }, { "cell_type": "markdown", "source": [ "**Prepare Data**" ], "metadata": { "id": "o8lxgKt2u4ux" } }, { "cell_type": "code", "source": [ "MAX_NUM_DOCS = 200\n", "\n", "df = pd.read_csv('/content/data/data/styles.csv', warn_bad_lines=True, error_bad_lines=False)\n", "df = df.dropna()\n", "df['year'] = df['year'].astype(int)" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "hpaJlIQ-u93z", "outputId": "dd1ff1a2-6ebd-40a1-a81b-f1f739ad9d61" }, "execution_count": 5, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[1;33mFutureWarning: The error_bad_lines argument has been deprecated and will be removed in a future version.\n", "\n", "\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/IPython/core/interactiveshell.py:2882)\u001b[0m\n", "\u001b[1;33mFutureWarning: The warn_bad_lines argument has been deprecated and will be removed in a future version.\n", "\n", "\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/IPython/core/interactiveshell.py:2882)\u001b[0m\n", "b'Skipping line 6044: expected 10 fields, saw 11\\nSkipping line 6569: expected 10 fields, saw 11\\nSkipping line 7399: expected 10 fields, saw 11\\nSkipping line 7939: expected 10 fields, saw 11\\nSkipping line 9026: expected 10 fields, saw 11\\nSkipping line 10264: expected 10 fields, saw 11\\nSkipping line 10427: expected 10 fields, saw 11\\nSkipping line 10905: expected 10 fields, saw 11\\nSkipping line 11373: expected 10 fields, saw 11\\nSkipping line 11945: expected 10 fields, saw 11\\nSkipping line 14112: expected 10 fields, saw 11\\nSkipping line 14532: expected 10 fields, saw 11\\nSkipping line 15076: expected 10 fields, saw 12\\nSkipping line 29906: expected 10 fields, saw 11\\nSkipping line 31625: expected 10 fields, saw 11\\nSkipping line 33020: expected 10 fields, saw 11\\nSkipping line 35748: expected 10 fields, saw 11\\nSkipping line 35962: expected 10 fields, saw 11\\nSkipping line 37770: expected 10 fields, saw 11\\nSkipping line 38105: expected 10 fields, saw 11\\nSkipping line 38275: expected 10 fields, saw 11\\nSkipping line 38404: expected 10 fields, saw 12\\n'\n", "\u001b[1;33mSettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame.\n", "Try using .loc[row_indexer,col_indexer] = value instead\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\u001b[0m \u001b[1;30m(raised from /usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py:5)\u001b[0m\n" ] } ] }, { "cell_type": "code", "source": [ "df" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 423 }, "id": "gkdL6ZXpvP7K", "outputId": "5c4c160d-374d-4de9-ebde-0dc041fd298c" }, "execution_count": 6, "outputs": [ { "output_type": "execute_result", "data": { "text/html": [ "\n", "
\n", "
\n", "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idgendermasterCategorysubCategoryarticleTypebaseColourseasonyearusageproductDisplayName
015970MenApparelTopwearShirtsNavy BlueFall2011CasualTurtle Check Men Navy Blue Shirt
139386MenApparelBottomwearJeansBlueSummer2012CasualPeter England Men Party Blue Jeans
259263WomenAccessoriesWatchesWatchesSilverWinter2016CasualTitan Women Silver Watch
321379MenApparelBottomwearTrack PantsBlackFall2011CasualManchester United Men Solid Black Track Pants
453759MenApparelTopwearTshirtsGreySummer2012CasualPuma Men Grey T-shirt
.................................
4441917036MenFootwearShoesCasual ShoesWhiteSummer2013CasualGas Men Caddy Casual Shoe
444206461MenFootwearFlip FlopsFlip FlopsRedSummer2011CasualLotto Men's Soccer Track Flip Flop
4442118842MenApparelTopwearTshirtsBlueFall2011CasualPuma Men Graphic Stellar Blue Tshirt
4442246694WomenPersonal CareFragrancePerfume and Body MistBlueSpring2017CasualRasasi Women Blue Lady Perfume
4442351623WomenAccessoriesWatchesWatchesPinkWinter2016CasualFossil Women Pink Dial Chronograph Watch ES3050
\n", "

44077 rows × 10 columns

\n", "
\n", " \n", " \n", " \n", "\n", " \n", "
\n", "
\n", " " ], "text/plain": [ " id gender masterCategory subCategory articleType \\\n", "0 15970 Men Apparel Topwear Shirts \n", "1 39386 Men Apparel Bottomwear Jeans \n", "2 59263 Women Accessories Watches Watches \n", "3 21379 Men Apparel Bottomwear Track Pants \n", "4 53759 Men Apparel Topwear Tshirts \n", "... ... ... ... ... ... \n", "44419 17036 Men Footwear Shoes Casual Shoes \n", "44420 6461 Men Footwear Flip Flops Flip Flops \n", "44421 18842 Men Apparel Topwear Tshirts \n", "44422 46694 Women Personal Care Fragrance Perfume and Body Mist \n", "44423 51623 Women Accessories Watches Watches \n", "\n", " baseColour season year usage \\\n", "0 Navy Blue Fall 2011 Casual \n", "1 Blue Summer 2012 Casual \n", "2 Silver Winter 2016 Casual \n", "3 Black Fall 2011 Casual \n", "4 Grey Summer 2012 Casual \n", "... ... ... ... ... \n", "44419 White Summer 2013 Casual \n", "44420 Red Summer 2011 Casual \n", "44421 Blue Fall 2011 Casual \n", "44422 Blue Spring 2017 Casual \n", "44423 Pink Winter 2016 Casual \n", "\n", " productDisplayName \n", "0 Turtle Check Men Navy Blue Shirt \n", "1 Peter England Men Party Blue Jeans \n", "2 Titan Women Silver Watch \n", "3 Manchester United Men Solid Black Track Pants \n", "4 Puma Men Grey T-shirt \n", "... ... \n", "44419 Gas Men Caddy Casual Shoe \n", "44420 Lotto Men's Soccer Track Flip Flop \n", "44421 Puma Men Graphic Stellar Blue Tshirt \n", "44422 Rasasi Women Blue Lady Perfume \n", "44423 Fossil Women Pink Dial Chronograph Watch ES3050 \n", "\n", "[44077 rows x 10 columns]" ] }, "metadata": {}, "execution_count": 6 } ] }, { "cell_type": "code", "source": [ "import os\n", "\n", "def get_product_docs(max_num: int = MAX_NUM_DOCS):\n", " da = DocumentArray()\n", " for index, row in df.iterrows():\n", " doc_id = row.pop('id')\n", " doc_uri = f'/content/data/data/{doc_id}.jpg'\n", " if not os.path.exists(doc_uri):\n", " continue\n", "\n", " doc = Document(id=str(doc_id), uri=doc_uri, tags=dict(row))\n", " da.append(doc)\n", " if len(da) == max_num:\n", " break\n", " \n", " return da" ], "metadata": { "id": "ATITPHWAvHzV" }, "execution_count": 34, "outputs": [] }, { "cell_type": "code", "source": [ "docs = get_product_docs(500)\n", "print(len(docs)) # should be 500\n", "docs.plot_image_sprites()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 337 }, "id": "Ilbu6gwMvUEO", "outputId": "252e86dc-be9d-4078-d3a7-5b9d6872abf8" }, "execution_count": 35, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "500\n" ] }, { "output_type": "display_data", "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": {} } ] }, { "cell_type": "markdown", "source": [ "**Preprocess documents**\n", "\n" ], "metadata": { "id": "hfw6f5aBvhzR" } }, { "cell_type": "code", "source": [ "def preproc(d: Document):\n", " return (d.load_uri_to_image_tensor() # load\n", " .set_image_tensor_normalization() # normalize color \n", " .set_image_tensor_channel_axis(-1, 0)) # switch color axis\n", "\n", "docs.apply(preproc)" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 281 }, "id": "YKG4TgKDvn-l", "outputId": "da73230d-67b2-44c6-8377-28db2d292e36" }, "execution_count": 36, "outputs": [ { "output_type": "display_data", "data": { "text/html": [ "
                           Documents Summary                           \n",
              "                                                                       \n",
              "  Length                 500                                           \n",
              "  Homogenous Documents   True                                          \n",
              "  Common Attributes      ('id', 'tensor', 'mime_type', 'uri', 'tags')  \n",
              "                                                                       \n",
              "                      Attributes Summary                       \n",
              "                                                               \n",
              "  Attribute   Data type      #Unique values   Has empty value  \n",
              " ───────────────────────────────────────────────────────────── \n",
              "  id          ('str',)       500              False            \n",
              "  mime_type   ('str',)       1                False            \n",
              "  tags        ('dict',)      500              False            \n",
              "  tensor      ('ndarray',)   500              False            \n",
              "  uri         ('str',)       500              False            \n",
              "                                                               \n",
              "
\n" ], "text/plain": [ "\u001b[3m Documents Summary \u001b[0m\n", " \n", " Length 500 \n", " Homogenous Documents True \n", " Common Attributes ('id', 'tensor', 'mime_type', 'uri', 'tags') \n", " \n", "\u001b[3m Attributes Summary \u001b[0m\n", " \n", " \u001b[1m \u001b[0m\u001b[1mAttribute\u001b[0m\u001b[1m \u001b[0m \u001b[1m \u001b[0m\u001b[1mData type \u001b[0m\u001b[1m \u001b[0m \u001b[1m \u001b[0m\u001b[1m#Unique values\u001b[0m\u001b[1m \u001b[0m \u001b[1m \u001b[0m\u001b[1mHas empty value\u001b[0m\u001b[1m \u001b[0m \n", " ───────────────────────────────────────────────────────────── \n", " id ('str',) 500 False \n", " mime_type ('str',) 1 False \n", " tags ('dict',) 500 False \n", " tensor ('ndarray',) 500 False \n", " uri ('str',) 500 False \n", " \n" ] }, "metadata": {} } ] }, { "cell_type": "markdown", "source": [ "**Encode image documents**" ], "metadata": { "id": "dxVn7Vy7x3O6" } }, { "cell_type": "code", "source": [ "import torchvision\n", "model = torchvision.models.resnet50(pretrained=True) # load ResNet50\n", "docs.embed(model, batch_size=8, device='cpu', to_numpy=True)" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 297 }, "id": "H-vsMHXDx8ep", "outputId": "76c9487e-4a17-433e-a94f-5e35015fdffa" }, "execution_count": 37, "outputs": [ { "output_type": "display_data", "data": { "text/html": [ "
                                 Documents Summary                                  \n",
              "                                                                                    \n",
              "  Length                 500                                                        \n",
              "  Homogenous Documents   True                                                       \n",
              "  Common Attributes      ('id', 'tensor', 'mime_type', 'uri', 'tags', 'embedding')  \n",
              "                                                                                    \n",
              "                      Attributes Summary                       \n",
              "                                                               \n",
              "  Attribute   Data type      #Unique values   Has empty value  \n",
              " ───────────────────────────────────────────────────────────── \n",
              "  embedding   ('ndarray',)   500              False            \n",
              "  id          ('str',)       500              False            \n",
              "  mime_type   ('str',)       1                False            \n",
              "  tags        ('dict',)      500              False            \n",
              "  tensor      ('ndarray',)   500              False            \n",
              "  uri         ('str',)       500              False            \n",
              "                                                               \n",
              "
\n" ], "text/plain": [ "\u001b[3m Documents Summary \u001b[0m\n", " \n", " Length 500 \n", " Homogenous Documents True \n", " Common Attributes ('id', 'tensor', 'mime_type', 'uri', 'tags', 'embedding') \n", " \n", "\u001b[3m Attributes Summary \u001b[0m\n", " \n", " \u001b[1m \u001b[0m\u001b[1mAttribute\u001b[0m\u001b[1m \u001b[0m \u001b[1m \u001b[0m\u001b[1mData type \u001b[0m\u001b[1m \u001b[0m \u001b[1m \u001b[0m\u001b[1m#Unique values\u001b[0m\u001b[1m \u001b[0m \u001b[1m \u001b[0m\u001b[1mHas empty value\u001b[0m\u001b[1m \u001b[0m \n", " ───────────────────────────────────────────────────────────── \n", " embedding ('ndarray',) 500 False \n", " id ('str',) 500 False \n", " mime_type ('str',) 1 False \n", " tags ('dict',) 500 False \n", " tensor ('ndarray',) 500 False \n", " uri ('str',) 500 False \n", " \n" ] }, "metadata": {} } ] }, { "cell_type": "markdown", "source": [ "**Index documents into PQLite**" ], "metadata": { "id": "mD9K3ih_zn5I" } }, { "cell_type": "code", "source": [ "# clear the workspace folder\n", "!rm -rf workspace/*\n", "\n", "index = AnnLite(dim=1000, \n", " metric='cosine', \n", " columns=[\n", " ('year', int), \n", " ('baseColour', str), \n", " ('masterCategory', str)\n", " ], \n", " data_path='./workspace')\n", "\n", "index.index(docs)" ], "metadata": { "id": "N3mxWt_kyVOQ" }, "execution_count": 38, "outputs": [] }, { "cell_type": "code", "source": [ "before_year = \"2017\" #@param [2017, 2018, 2019]\n", "category = \"Apparel\" #@param [\"Apparel\", \"Footwear\"]\n", "color = \"Brown\" #@param [\"White\", \"Black\", \"Brown\"]\n", "\n" ], "metadata": { "id": "kKHnOpzc17c7" }, "execution_count": 39, "outputs": [] }, { "cell_type": "code", "source": [ "query = docs[0:1]\n", "index.search(query, \n", " filter={\n", " 'year': {'$lte': before_year}, \n", " 'masterCategory': {'$eq': category},\n", " 'baseColour': {'$eq': color}\n", " }, \n", " limit=5, \n", " include_metadata=True)\n", "\n", "\n", "img = Image.open(query[0].uri)\n", "imshow(img)\n", "plt.title(f'Query: {query[0].tags[\"productDisplayName\"]}')\n", "plt.show()\n", "for doc in query:\n", " for k, match in enumerate(doc.matches):\n", " print(f'[{k}] ({match.scores[\"cosine\"].value}) id: {match.id} tags: {dict(match.tags)}')\n", " img = Image.open(match.uri)\n", " imshow(img)\n", " plt.title(f'{match.tags[\"productDisplayName\"]}')\n", " plt.show()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 1000 }, "id": "pWlDhtu10GlS", "outputId": "a29ad777-322e-4b3c-db7f-8361e8342bc4" }, "execution_count": 42, "outputs": [ { "output_type": "display_data", "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" } }, { "output_type": "stream", "name": "stdout", "text": [ "[0] (0.2998480200767517) id: 20166 tags: {'gender': 'Men', 'masterCategory': 'Apparel', 'subCategory': 'Topwear', 'articleType': 'Shirts', 'baseColour': 'Brown', 'season': 'Winter', 'year': 2011, 'usage': 'Casual', 'productDisplayName': 'Wrangler Men Biker Brown Shirt'}\n" ] }, { "output_type": "display_data", "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" } }, { "output_type": "stream", "name": "stdout", "text": [ "[1] (0.3230225443840027) id: 20105 tags: {'gender': 'Women', 'masterCategory': 'Apparel', 'subCategory': 'Topwear', 'articleType': 'Kurtas', 'baseColour': 'Brown', 'season': 'Fall', 'year': 2011, 'usage': 'Ethnic', 'productDisplayName': 'Diva Women Printed Brown Kurta'}\n" ] }, { "output_type": "display_data", "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" } }, { "output_type": "stream", "name": "stdout", "text": [ "[2] (0.3932587504386902) id: 20110 tags: {'gender': 'Women', 'masterCategory': 'Apparel', 'subCategory': 'Topwear', 'articleType': 'Kurtas', 'baseColour': 'Brown', 'season': 'Fall', 'year': 2011, 'usage': 'Ethnic', 'productDisplayName': 'Diva Women Printed Brown Kurta'}\n" ] }, { "output_type": "display_data", "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAANcAAAEICAYAAADMYmH7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nOy9ebBlWVbe91t7n+FOb8ixKiu7q6snhm5hgdwgQEjqQMg4bGMpbEumkXEjY8tyWLZlCRsCJIYQttqyQkRYIqyQDYEQQxsbHIIwstw4gAYjJBoENHQ3dA1dVVmVc77hDmfaey//sfe5776X72W9ygGqs+7KuHnfPeO+557vrLXX+tZaoqqsZS1refhifr8HsJa1PK6yBtda1vKIZA2utazlEckaXGtZyyOSNbjWspZHJGtwrWUtj0geGrhE5O+LyF9/WMd7M4uI/LaIvP/36FzfKSI/9HtxrjebnApcIvIZEalEZCoiuyLySyLyF0Vkub+q/kVV/RsPc3Ai8gER+eSRZR85Ydm3PMxzP0wRkZ8TkVpEZiJyS0R+QkQunbS9qr5XVX/ulMf+jIh81UMb7OFjv19EQhr3TEReEZHvehTnehARkR8Qke9e+fxeEbkqIt90n8d7KNf09Wiur1HVDeBtwIeAbwa+70EH8BryUeDzROQCgIhkwB8EhkeWfVna9o0sf0lVJ8DnANvA9xzdIH2XN5q8qqqTNPavAL5RRP70cRu+EcYvIl8E/Czw3ar6t1/nvg91/K/bLFTVPVX9SeDfBz4oIn8gDWz59BCRT4rIv9XvIyKZiNwUkT+UPv/vInJNRPZE5KMi8t4TzvUK8Dzwx9KiPwT8NvDzR5YZ4FdEZEtEfjCd60UR+Wu9dhWRbxCR/09Evidp3+dF5MvT8pdF5IaIfHBlzKWI/G0ReUlEriezd5jWvV9ErojIX037XRWRP3/K63cH+HGgv26fEZFvFpHfBObpWi2fnMls+7H0vabJZHxfWvePgKeBn0qa5b9Ny780WRe7IvIbqyamiLxdRH4+HesjwPnTjDuN/QXgl4D3rBxPReQ/F5FPA59Oy/4TEXlWRO6IyE+KyFNp+XeJyN9Nf+ciMheR/zF9HibtflZEnknH/WC6/rdE5Ntea3wi8iXAR4BvVdXvTcuOarX3i8iVlc9Hr/+PnnBNT3XPrsp9z7lU9V8AV4A/eszqHwU+sPL5q4Fbqvpr6fM/Ad4NXAR+Dfjhe5zqoxwA6Y8BvwD84pFlv6yqHfB3gS3gHcAfB/5DYPWm/8PAbwLngB8BPgx8MfAu4D8A/p6ITNK2HyJqmS9M6y8D375yrCfTuS4D3wh8r4icucf3AEBEzgP/LvAvVxZ/APg3gW1Vdcfs9m+nsW4DPwn8PQBV/XrgJaJVMVHVvyUil4H/C/hu4CzwTcCP95o+fe9fJYLqbwAf5JQiIu8G/gjwy0dW/WnitX2PiHwl8DeBPwtcAl5MY4f4UHx/+vuLgWsc/I5fBvxOevj08hXA5wJ/Avh2Efn8ewzvS4D/G/ivVfV/Pe13SrJ6/T/AkWuatnk992wUVX3NF/AZ4KuOWf7LwLelv3+AqIoh3oxTYJQ+/zDw7SccextQYOuE9d8A/Mv09z8G/iTweUeWfQdggRZ4z8q+/ynwcyvH+fTKui9I531iZdltIpgEmAPvXFn3ZcAL6e/3AxWQray/AXzpCd/h54AFsAu8kq7HhZVr+x+ddL2B7wR+ZmXde4DqpN+GaK7/oyPH+6dEED0NOGC8su5HgB86YdzvB0Ia9366Xj8BFCvbKPCVK5+/D/hbK58nQAc8AwyBmvhw+xbgW4kP6AnwXcD/lPZ5Jh33LSvH+RfA154wzh9I43sBOH/Muu8+8p2uHLl+J17/+7ln+9eDegsvA3eOLlTVZ4FPAl8jIiPik/dHAETEisiHROQ5EdlPXwRONk8+CvwrSSt8KfDPVPVTwKW07CvSNueBnPik7OXFNMZerq/8XaWxHl02AS4AI+BXk2m1S3wqXljZ9vYRLbNI+54k/6WqbqvqZVX9c6p6c2Xdy/fYD+ITfvU8g3vMD94G/Jl+3GnsX0HUIk8BO6o6X9n+xeMOsiKvpnFvEm+qCviHR7ZZHf9Tq8dU1RnxoXVZVSvgY0Sr4o8RNdkvEbXhH0+fV+Xo977X9f3edOyPnMaCuMf475L7uGeBBzALReSLiTfuL56wSW8a/ingEwlwAF+Xln0V0ax6pj/kcQdR1eeBV4G/ALyUfiyAf5aWTYga9BbxCfm2ld2fJmqK1yu3iDfRe9ONta2qWxon9Y9CHiQ14ei+LxM11/bKa6yqHwKuAmdEZLyy/dOnPpHqHvEh+TX3GMOrrPwG6VznOPgdfh74SuCLgF9Jn7+aaNY9iFPKE++tl4B/KiKbafmc+KDs5clj9j16DY9+fl33bC+vG1wisinRWfFhojnx8RM2/TDwrwH/GUlrJdkAGuLTbAT896c47S8AfyW99/KLadnHVLVSVQ/8GPDficiGiLwtrX/dMRxVDcD/AnyPiFwEEJHLIvLVr/dYvwdynTjH7OWHiBbDV6cn7iBN4t+iqi8Sn+7fJSKFiHwFdwPlREnz0a8lOpVOkh8F/ryIfKGIlMTf95+r6mfS+p8nzoU/oaot0WT+j4km981jjndq0Tjv/jPEh+NPJ2D/OvBvJEfJk8BfPsWhjl7T+7lnXxe4fkpEpsQn47cBf4fDzoJDoqpXidrly4H/bWXVDxLNhleAT3D35Pg4+XniRHJVS/5CWrb6tPsviE+q59O2PwJ8/ymOf5x8M/As8MvJFPgZ4uT6jSZ/E/hryQT8JlV9mfiU/VbgJvH3+m84+K2/juh8uEOcq/7gaxz/qeQ1mxF/t7PAnztpY1X9GeCvEz2iV4F3EgHZyy8R51797/YJ4jzsoYRSEmD/nXTMnyI+bH6DaMr9Pxy+F0+SQ9eU+7tnEV0nS65lLY9E1tzCtazlEckaXGtZyyOSNbjWspZHJA8ELhH510XkdxLV5Q1LnF3LWn4/5L4dGiJigd8lMiauEGMWH1DVT5y0z/nz5/WZZ565r/O9USQERVFAECMIK0ERPRwgMSkKcncw5Mg1V0U1xMUiiOmfeXLSHsvPIf1hACNHj0tiFGg8rsiR0fRsAhDhmPWPr3zmM5/h1q1bj/TLPggL+EuAZ1OQFxH5MClgfNIOzzzzDB/72Mce4JS/N3LwwOnf428QVKmajs55rDXkZYExhqDgFVQhhPhuBAoL1oBhlQoT4qu/6QENntDUqPdIlmPLARhLggwALp2DdP8rggvQuDjOkQ0MjbIKwxACrm3RELB5TpYXEUUr37NrW0IIGGvJ8xwRQdN3gQg4icg7/fXj7ofBUbn78fF7K+973/se+TkeBFyXOUwbuUKMnxwSEfkLRCYFTz99ajLA77+oEtQDIGLiC8FaExVM0i79TWgkKR7DUhP060P8C0FBA6KBqE0ASW/WRu1i7aHza/AEhS4oLoAYweYZktSitaS7OeC9j8daObmxFozBGLMclCb0KGCMQUSW72t5ePLI829U9R8A/wDgfe9732dNUC1owLtIHbQ2Q6wBgcxmiNEIFY1mmQgYw1GLCzRqO9EIKlBEPUYDiEaASESXyfMDVApAIPhA10Vw1V2g8wGbZZTGYCWNx6RzuYB3HcIBYBDBZlk83EFeKxoCIQREBGvt69ZMazmdPAi4XgHeuvL5Ldwfj++NKUvTSFeMQ4lAEiGoRhMQXd6XR5/8K5Zf3I6VvzVt0J+jf+/ndKp4F/A+EFTRZEnqcp8VLEs/YFA5MGtF4pwQc7zfSuNGiDGsyQQPXx4EXL8CvFtE3k4E1dcSqTWPkaSbfuXGO1AsivOBEJQsS2bXcZK2N2IQFCHajaKKBgc+ELynaVu897RtR1XXUbOYHGMzjM0oRxMmoyFiDCYziBye2xhjsXkEZQjxmIZobkoavx55X37LlCLhvY+pEsZijE3fd63R7lfuG1yq6kTkLxFzhSzw/ap6L0LnZ5n0GuvuJ3qaueCdx4eoubJ0wx/dLvkVMabfT0BNNBNdQF2Lc456NqNtWxaLip3dPbz3FMMx5XBEXpSMxhMmo0HvDiHN2tJ8LpqC1mQRJE1DCCGahcnU7LXeSk7S4W8bAs45NASyPJrCa3kweaArqKo/Dfz0QxrLG0uWrmtW5iPJ3Er/i5GoHXrzKzkJVs02We4Q9YzvOrquIXhPPdunWczpuo7p/n4EV1WxtzfFB08xGFEMxuRFQesC+7M5iEGyDIwhLwrKwTABi+T716WH77DWWdW+gqreNUVcaugVT+Hde6/ltLJ+PJ0gRoTMZvQxol4T9EAzRijyjKCKMbLEn/eK94oRyK3BGDCqSAiggf2dHW7fvE5VLfjM889y7ZVXaJqa27dvU1cVXeeom4YQFFuU2KLE2IzBeItiMKQYDNk6c5ZiMOTy5bfw7s/9XIbDIZujgnJYICJkWYYm72AP+oOYVwSNsWuz71HLGlzHiAAqgrEmBXhXnuzpZhQBa4XooD+QECK4VCCzyjLMrIqGQF0t2Llzm9l0ygvPPsfzzz1LtVhw4+Z1qsUC7wPO+WhOZjmSFYix2GKIyQuGowkXL11mNJ7gvXLpqcsYDC636OAgGCwrrneOmIF3a7W1PApZg+sEWRqCIglgcfakyfd+1IOYtk5vehC/gmju7e/StQ1XXn6Z5557lsVsxs2bN5jPprRtAyHEWFlyw6sSI9IhxtqCdyCGtqnZ392hripeuTLhd3/nDBsbE97+1qcY2Cew1pJn2aG4VW+qLsd5DLB6bRYdGmvK6cOQNbhOlFXH+cFfful+l6U5uMrnEFWEEEGX4sRRSz3LbLrPJ37rN/n1X/sYTbWgnk9p6ooY/1IKa2iDj4AKYekNFONjXC0EurZmb28PVbh54zovv/Qik8mEP/rlf5hJ+a8yGAzY2NxkMBwe/jbCgY5dBdeKqZjnefrma632MGQNruPkeD/Act6iK37AXkPpMdv223Rty3S6z97uDrdv3+bGjeu0dYX6DrzDiJBnNmob4LAvMGox0UAIHucDVd3SpfiXV2UynrC7s0Nd15GOFQKrwznsmDiG6ai6Ekg+TI86uCZrwL1eWYPrFHLgNQTpaYHH3KRCnIcJlhA88/mc4B3Xrl7ldz71Ke7cvsX1a6+izsUgcdfhuwaARiM9Kqw4HnJryMoCELoQ8L4jOI9rG5z3dG2Oa2taa7h+9VU+9alPsbm5ybvf/W7KoogxsZ5OxcqYj3HH63KtHgSrWc/PHkTW4HpNkaWToDf7UF11HKatomTWgIW2ccym+ywWC1566SV+8zd+nRvXr+HbmuA7RD2ubWnqKsaYugYNniwvKMoBxlqKzDIaFASFed3iuo7QObqmonOOLrO4ZkhD4MrLL2EInDt3jrNnznD2zJlIxl3lKyYJybmyHHvvVVxyDwPOHaFHreV1yxpcr1NWibHJb3G3aGSk13XNfDZjPp8xn8+Zz+dI6BDv0aAYa8jzHA0eIwENhizLKPIsAiOzZNZGd78IIppYHmk2poHgHd5Z6rpmOp1RliV1XdN1HRmQvRat6VjgyNoKfAiyBtdrymEzyayYSUFXuLZ9nMt5gvfM9qc89+yzXL36Kp957tNcu/oqO3duk4uSGyWzlrPbW2xujDECWTqGqhKWSVoWjE00qxgnM0Bb2JjKop5mMcM1NbduWlzXUlULXn31Vba2thhPJpy/eBGbHf6Zl1pqRWOtYilqrGz591ruT9bgOoX0CYUQUz6MJGCFg7lKnxjpvcd3jsViwStXrvD888/y6ssvcef2bfb3dhnkhmFuGZQFW5sT3vLUJTJrGBQ5mTV0XUdd15Fn6DxN5+mco2laus6BBoo85pkogbauEBH2RGgTP/HWrVvcvn0b7z3bZ8+ufpPkku9jdcex4Q/M4LU8mKzBdQrRZOYhQj97iRP9w6ZhT37tupamrpnu77Gzc4fFYo4IFHnG9tYG589sMhyUPHXpEk8+cRFrDEVusUbS/o6ggaZ1tG1H03YoMY+sbixV26EhegoJLOlVvYZtmobFYsFgMMB1Hc65NF5zGFgr3yWkNJRV4u6a/vRgsgbXKSSEeMP3njO7pA5FjbUEV1DqqmI+nXL71k2ef+5ZPvHxjxNcS2EN5eYGf+C9n88Xf9EfZDIeceniOc5vb/Y7A4rNMrKiBBHapqGtG2aLBR//7U/x4stXmM7m+BCYmQXORe0GQpZibs45dnZ2uHr1Kt57Ll6M4M3ynDyPHsQlg6MftyreOaqqIoRAWQ4oB8O19npAWYPrBFn1A2hK45BeVcWl0aI6klvlnaNpaqpqwc7OHW7dvEGRGcZFdFRcPH+ed73rnWxOxlw4s8n2xnh5c6sG8rJkMJlgjKGta9oqOiqu37jJ7t4eAIMip6kNokoIkSplEkNYVanrmtlsxmQyoW2jl1HEYDONPMdjQBNU6boO7z02yykfzWV9U8kaXMdIn8MV1C/nKFmWLecofSC55xwuKXwhsD/d59q1a9y8eZNqPse5jslwwsWL5xkPh5w/f47NyZjxaBi9gonhITZyn6wo4jowgtFAllkGg4Inn7hICIGbt25z584O3nVUdUPXdQQNaHp1Xcfu7i55njMYDFBVsizD2nuk8SfAFUVBCPGca5314LIG1wkS0/w9EOcgWVmmtP5EzE25xUpM9DUihOC5eeMGv/u7v8uN69fY3d2hrWsG58/yzne8nbNntnn7257mwrmzDMqC0sbiNWISsx6LiEJXA2CNxRaWzI743He9g7devszLV65w88YN1LXsCdSLOV0IEGJZgKZpuHr1Knt7exRFgaoyGAygNwePODH6ehrWmLgdMfFy7Yt/cFmD656SmAzL/CgIfoXEKwe8BkjOhLZlsViwWCyiVgkeY4ThcMhkPGYwGFDkWaQ7SUixq+iBjGZmDPD2KfgQOYyDQUmW5fEYZUFZ5EsNI6megBLN16ZJrI8+afLQKI9+xQO2xkHtjTVx92HIGlwnyGrOU88SP8wf1Ji6n0DX5001VcXe3i7T/T2qxZymjiXTBkXOaFhS5haTHCHWGoxNgWmNWlJ9ILguknR9oAsB1UgFVhUMgfNntwmuA5Tbt27GjGjvIoMjKG3bArBYLNjZ2WFzc5PhaMR4Y/NQYudaHq2swXWCiBgOWEP9pCrpB+1Z8SyB1d+wdb1gf2+X6f4+1WJBU1cE7yK4BiVlnmElFvA01mJz0/v6oxZxAd+1MVO5rllUNYpg8xKxGZbAhbPb5NZSVwtetEJLoPMOR4f3AZtSR3pwjcdjzp47x3iycdi9fkLK/1oejrym/heR75fYsf63VpadFZGPiMin0/vrbZP5hpZ+WvJaFWjlSHJG71X03uO9X8aNdEXlheBxXUvXtriuxbUtvmtR7xPAeka8Eryja1u6tqGpq5ip3LZYayiL/OCV57GeYgJLH7PqA9JVVdG23YqpybGgurt61ck1N9by2nIazfUDxO7xq03SvgX4f1X1Q6lG/LcQm8U9trIEyWoWRnotb8LgcV1H1zQJPIGuA+cCXedwXctsOuXWzevkmaXIDLkVsswwHhTkKfM5EyUYpWsqpru3cC5QdZ7OBxShtIZia8L03DaXn7zAbF7x6r5jNnWgPs31AtPplCtXrtB1HSbLePKpp8jy/BBgjgaTSV8xNtdcM+IfRF4TXKr6URF55sjiP0Xsig6x+fTP8RiDS1fmWcrxT/GeExi8x7suvnwgBOK793jnqKsF0z1LZg1lJuRWKPOMMozJyjzSq2yciLmuYTGb0naO/XlF1XSUgwFnzp6jKAdsTsac296kyHN2mhm636IqsYpTMgvv3LmDiHDhiScI99BAh7yIyiFtuwbY/cn9zrmeSG1ZIXZcf+KkDT8by1kf3Fi6oqminlpWTjqSUNlvZo0hzzOyLEvuddDgaZqKqlpQF8JiYMmNIWTgraBFTjfIyW2s22FEliRdaw3WC1YEg2IF8sScHw4KJuNR1GZli7U2VQCWJRVrPp9TFJEpH0JYfVIsZQ2eRyMP7NBQVRU52l7j0PrPynLWaIx1AQecPAHDQX1C7Z/wfY4XUBQF49GYxXBOmVtyC75r2btzmxwP7RjpKjIrDCwUVhgNB4wLQyaBLMvIUmqJNVDmGaJKbiGTQGGFybBgNB7Sbm+xePIi03nFtVkgv7XAh4NLXNc1169fZz6f87ZnnknexCN5XMeQd1eJKGvg3b/cL7iui8glVb0qIpeAGw9zUG8E0QPbaOnTSLrrSBkAPaTcjI05WX3avpGoudq2oa4rmsJQDzIyY5AM1MZ67945gveoibUyRKK3yRqTXjEWlplYEiDPMsqyYDQcEhSKPMcau2weAZGhX1WRNd809dLBcggwx7Dij128ltct9wuunwQ+CHwovf/jhzaiN4hEEKUbbXX5ygdN3CfRnq8BVoTMmmjOmZ7YG5bzMGsNk9GQPLOM8jjvGg0HDIYlRZkjRE2nKHlm2NqY4EJgMB7ROk9ZDtja2qIoS+bzObmNwMsyS5bHYqE9iEIIzOdznHPMZjMWiwV5nlMUxbIYzWq5uLU8XHlNcInIjxKdF+dF5ArwHURQ/ZiIfCPwIvBnH+Ugf19EiHXdOXzzyYriOuQ8TK5Ea03SLJbMSoqVheR+b8isYWtzg7LImZSWYWEpi4LxeMRgWOLblraKqf9lljE4ux0Z7MUAsjzG34wFhPlsSp48jkWWURQF4g7CAN57ptMp8/mc3d1d9vf3sdYymUwOwLUcO2uQPWQ5jbfwAyes+hMPeSxvGJGVP46rlrRcKSDoEevxsLNjlb2BRvAVKT5VFBlFYcmLHJvZg0KeGjtNisnI8gwxFlOWSFGmgDMp70owIsvs6KMvVcU5hzGGrnN0Xbd00/eDu9slvzotXoPtQWTN0DhRVvJJ4O65Coc1WP8eguKcJwSPEMtaWyMUeU5ZFmxtbPDExYuRH5gLeRYb6mVlDjZWa8oygwbF5hmmyGOnktyCFdRHBoc6j3pHkRmKPEtVpw47IEIISyrUdLrPrVu3YoOHsmRjY2O5TV8ItKd7rZY14Mgx13J6WYPrnhIB1t9nxwHsYCvitiHgQ6yj0es9aw15binynI3JhPPnzjEcFKSmj2nvtH1myKxBRTG5RYo81tLITAKX4rsW38aah5k1FJnFHrRRWUoPLlVlNptx584dAM6eO3eIqbEk965W6U3hiDWw7l/W4DpOjsSweoCd1DUkVXmJ/5J3z9h4w1vblwNIbPejNI9U/FO9R9WD99Hc7M1D75fMdTWe4BzBueXyg4I5KYi9PMfKGTRq06auY32O1DGzX6d6Umh8LQ8ia3C9DjmOMgSpQI2JAeCyKBiNRoyGQ8pBSVkWWGtxnaNpW9quxXUOl8WmdMaCeke3mEVvoomsDTKLekdYdKhC5z3Ox2i1hsQh9HH7zBo0BNq2oXMH4FrlN84Xc27cvEnnHE8//fRSCx/Ksl7LQ5U1uO5DjosVSWJv2MyS53l8ZYmpIYIPkf7kvV+ajZrIieodvm3wbYPkGWQDxAjaOTQ5IFzT0jmfCndk0RkRwtKhEUsFeLwPh8bZv3dty2w2I8/z5TwMWGusRyhrcB0jujQF+xu1D6wen66xTDsRQ5E013A4pCgLslQzsKlrFtawmM+Zz2bgHTLMyAqL77rIeG8qyrLEZtG07NoOl+ZMMeM5Nr4z5QAxlqpzdK6LzPmuw/kIrlVvYT9un7KUm6bB+4NA84m+ULnX2rWcRtbgOkGWcyRiP+O+R3AvfRxJU4q8TX2Rx6Mx586ewbc1k/GE4XAIAvv7+3RNxe3bt7h96yb1aIjZnlBMhrR1zXR/l2o+YzwakRnIrI1gqGsQExvfFQWmKMm2tjFFwbSOrI/FfE5dV7Rtg/OKtZEdsqphneuO1Vx3d6GUQ8Bam4v3L2tw3UOWZtVRxtPKetUDpnxsw5MxKEvKokyFYSxCZKq3Ak3dUFUV1ghdN0CDElLDu845nI8mY0Aik957xByk4RtrsHmG5BlihOAdznfRzAyaCtWYuzRrSD2PnXN30aDudtTI3ZpLD70dkrvhd0pj8zEH7hpc9xA1sQBN0OilE2IrVyMmOtklZRFjQAUjls2Nc1x60qG+YDy+SJ7fwBjwQWidcO1my2996hqbkzHm8zcYb2wQspLhFuSjGitK5cF4RWxJuVHEFrHDkqzIUQm0i1voQqmnV2maG7TtAsOCYaF0TgmhQV0iE/dEXt+iXUXohmhw9PySA60lyzLaYqKJe79yJOVyyV6hJzmv1ul4jAG2BtcJoiIEMShK03W0rsOIYShCYU3sOCmpWhOSwJVxZvsJBnYL9SUbG09SlleREHAh4IPyyvWW+fwKm5sbnL3wdi69ZRNrlfHZTQRPU82Y7d9CQ8dkUjLeKDBWsKXB5oJrKuq9W3RNRbV/lbq6StvUGGrGhdJKWJZcA0E0cSR9jW8rQpv6gmlq0CcHmsv7pCGRZYmD++VrLHtv6kF3zL6UAWLAZiD25AM8BrIG1z3l+OTIVT7h4bWCMZY8L8iLMlauLYfRpd60sehM55kvGqzNmM8rZvOKPBNGg+hSR2KMLIhibIbJbOpgGU2+oH0yZosGF52HiSlfFDmIie5+txogjmWyo7fSoeEgdnaSBbfK5+0D5OjdrJR7AU446fhvDh/lGlwnSCDggkeJ1KBBPoiudtNThDTFkRSsxWBBIxujKDMm4yFvectlFrMps/19bl29Givx1nPm05rZtOTjH/841WKPjcmIZ55+gq3NMUUubJzZxlooCsgKUPW4doZ3DV2zYD6f0zYVirC1dYZy4HiyDuwET1W33Lx1i/3pjKZumc3mdJ1jPN5nf38Pm1maqkJd1F69xSbGYlLh0KCxNAEczlCOHMn+Ch0gTfp1K0zMQyl+kgjQ1q6YhY+vOdjLGlwniKriNYIrMzmZZD0HI1qBqvgQokMCQeWgDJvkMBgOuHDhPIvZZW7dyNm/c5Oug7qpme/vMM8sz79QUC/2OHd2m/FQMHKOra0x442zFEUG0oF0qO9oF46uWtA0FXVVxyblKkzGGxSlcqZSLjqYL2oWVRW7orSOtm6om5b5PIKyHJS0bV4rb7kAACAASURBVIN617th6AlcUUMaggvLeJmISRQtWdE3Sz7YkvoV6y8eyCF/yOqHJbDW4FoL9A609OcKK10gCIjpGfKAifOcLLdsbEw4c2abpprHAp6iiJG4Pcp8vuDWnR2UwO07d8gzQDo2t0qCz0E6RBwhdLRth+tcimPF0IC1hkIKcg/jcWA0D/gQEyzRGGAuyhwklnKr6wWLeclsus/e3i5FUVIMR2RZsaRQ9ax+Y47e/MfxDGVFYx0mOnPo05uzmd4aXCdKcgawOveIRTv7uYoxgk2vnjeriWA72hjx9nc8zdntCYNCeOHZT7BYQF4IZZnhvefKtWu88PIVzp/dwNqGGxfP8NSli1hpGI0GGPEYE3tyha5CfYuqx5iMsjAMhiXjjTEBYd92zEzHYG+fKy+/RHAtuYVzZzYJCj4Ebt+8TrWY85nnn+V3PvXbbGxu8da3v5PtM8OU6hLrzRsx5Fn05h1t8HfURb9iMKY5aK/NdEVhHZfO8vjLGlz3FDnyfhDbinxCVjxuactkQuVFztbWBoUVblzbIMttdJIZwdro9p7N5+ztz/C+4ebNLTLjGAwss+l5UIcRjxWPEEA7UJ9qa8SAdVEOGE8mKJbJpGE8bmjaFptKBRgTAQjCbL6gWizQoOzt7XLnzm2CKpecS5qUZTm1qOnSspWaHMdrtOWF6SNkHAeiQ1xiOfT22MoaXCeIINjEcjByUPzT9G1J9PBEXkNMGSFovCHVUY5KjIUz589w+fKTFLlw89qrVPMdVB3GBPIMcts7AKJH0PkY7M0tSCaI2KQhNdXRiKZbnheoxrlQWZZsbxYEH9jcmDCZjIhlsKP2mc+FpvEEbbl16zavvPwSTdPwzDvflTSW4n10cBibcYSQcoTFcdJFk4QiORE4/YMJOWRtP5ayBtcJYkTIxC6fxj2Qlk3jlm5sjR1Geve28wTnEYHx5hjMhEuLp/i893wOFy+e4ZM0XHv1ebw2ZMYzzKHIwRgFCanRXkvXWkxpySWWaBsMS4o8uuVzG4vVGCMxjARMhiOeuDgiz3POnz/Lzs4dvPc0XSQL7+wY5nOHVIGXX7zCaFhyeW+X937BF4DGFBbXuZSkWQB9/Mssv/+p4LB8EK2agnqQNqOKajSjH2tkcbpy1m8VkZ8VkU+IyG+LyH+Vlj/eJa05DKpVMfRP8hWP8vLGOShJbTNDlluKMmc4GjAaDSmKPJpXomSZoSwzyiIjy2ziBMryyS6J8R5pTxk2i68sy8nyImUOxwC2tTEZsywKhsN4ruFwEMsJ5LEjijERKG3bspjPqBbzyEfs2lhINHj6Pl8Rsn2TpIMcND0EkpXXa1zPw1lsb46512k0lwP+qqr+mohsAL8qIh8BvoHHuKS1Kqmp3AFoDLHK0vLpLLokHEQWOiCCJm8goYm9i7sFrlvgfIXJAuNJSTkwbFy+wGgwYGMy4h3PPMX21gbnzmyxdWaLQVkyKC2DIjauK0tLnsc6GzbPMNagrkO7FqOQm5xBlrG9OeE9n/c5XDx/jtk85nBVVc1wOGE0HOFDYHt7QNcumE13efGFZzEGJpMJ589fpCjLpBUhuiU8y9wYYPVR0yvuvqVSn3qjEvXdoe1FgLC0BI7yNR9HOU2BmqvA1fT3VEQ+CVzmMS9pHZMRA0HjfCr4WOjFGiJ1Z0nW7YOpPeAk1lMLAXUNeEfoFnSuonMV1iqjSYFQ8MxbL/PkxQuMhgMuPXGeyXjIZDRka3uLIs8oiwguI0Kex3JtWIuUJViDNg3qY0O+3OQMTKzE+/mf+266d72DO3d2eO7Z59ifTtna3OTM9oSu65hXNVVdMdvf4aUXnqWpK5689BRbW1sMhiXW6JIs3Ds0VIXI9Dpop9TX2ug5ibFpnomAk9V513LGCoQj6x5feV1zrlQz/ouAf84pS1p/NpazXpWDIKkceWb3rHNNZmDqThIcJDB2TUXoOuo6Bn+bpo5aYjzGGmFza4Ot7U0GZcFgUJIn823peZQY1JVoh9Jrh9jK0iDGoMZGcm4anzFCURbkCs24YWNzI3kDJdbU6DrY2cO52JSvbRsWixnVIpqJWWYpBkpps0PB3r7isBzkACxd7v1cbHk9krPiEIJ6CiZH52SPr5waXCIyAX4c+Muqun8kcfDEktafteWsOfj5s/5mPuSiDriuSY4MH50CGiLjPDjapub2jatUsymvXHmZK1deYH9/j2GZ8Z73fg5lUfD0Wy/xxIXziIAlYFTJswyvHrxSqKVvOalqCCqIGgQLkoEFU8SKUxIsBI8xlsFohC0KymHJsCxo25b5omI2W1A3DZ9+7nlefPkKxmbMp7u0bY3raopBzmSyyYWLT/DEpctkWY7JCkzS1NH3aJbzUU1Pnh5UQaNTRpL2NivM+iUb5E0UTT4VuEQkJwLrh1X1J9Lix7qk9XLG0NcF5MCJATEm5F1H8A5VB6FL4OrQ4KirObs7t9jf3eHWrevc2bnJbDpl46lLXL58idFwwFsuP8mFC2cJzlHPZ7i2xYpEgm5INyuSTLE0p6P34BnUZJCltJJOEyvDUA4KiuGIQVkyHpQE72na2KC8qmqmsxm7u7u4EKjrOYtqHjXq5gaz8ZSiyDl37jyCItZiZOU2EU1zsCOhiPS/qiCYvpzqUo79+zEH2mkq7grwfcAnVfXvrKx6vEtay0FMa5nGD8uYkHMtdV3hXUvX1rTNPPbncg3etTR1xd7eDvPZPs63jMdDMgubWxM2NscMypK8yJapTcYYbGYxIilILMk7GNsKiUjUGRKL0ajzqPPQpcRHIqteROj6uvAhEFwMEXRtQ7VYUNc1qCfPM4yPNTl8UDQ4pnt7tG0TC+yMxrFN0fZZRhubxE6beSy/hjkEDCMp7y1duFVGy6oj5C6z5TEPdJ1Gc/0R4OuBj4vIr6dl38pjXtLaiCDGcmDQ9A3uHCF4mmrB3s4tmrpif3+HnZ2bONdRV1PaeoH3Hc1ihusaUM+Tl84hApcuXuCpSxcp8pzhoMDYePyssBiTWhBlGVaEYjggHw5i4qKmOY4qoXGodtHh0sVcKVMUDIscHwKLvV3arsPajLwoEDHM9ve5fedObELuWiajMtbVcB4fAr5teOXlF1CE3Z073Lx5g+FoxNvf+W6eeuot2DxnOJxg8xxjMoyNj5tI9k3xQD0AmC5Bc4CgHoDCY48r4HTewl/k5OvwWJa07o0e6Sfmy7yk1BLVO7zraJuapqlZLOZMp3t0bctivkddzaKH0dUE7yiKjPGoIM8yxuMho/GQ3NrE3+uJsgasYk2Md8WahxYxNmUFx3gWwUeA+xCbk/t4u1pk2brVdx1tVZHl+bKSrkvatGkiPzFqOfCJl9S5lsW8wodAnhdkecGoHvPEk0/StjW5BnxRxhAAAubAiSJyEA07kZnBmyW6dSBrhsaJchA8Dd4t+xPv3LnJfL6Pcx1tPcc7hzUwGY9wZUbwFb4zeO/pmpjUaE2AEGtpZJlhMMhjVagQvYzGGoaj4XJuZ1MPMDHxOELAmGyZjk+/Ps+QIgaSJYvUI2NgOBqSFXkqUBp5TL5rme3vUTexW2VdzXHes6gaWucwNmNjMsJYy8bGmNGwpCws89mU69depSgHbHUd5WBIUQ4ZjeNDIGYTJxeHkNz1qxB73PXTybIG1z0l0nZ81+GSy/rlF1/gxo1r5HnGZFwmZoWwtTXB+w7XzWgbAy2E0NK2C6wpUc1BDVluGI4HZNbSNTWuC1ibMRmPKbIizZNiOnzwsZeyIOR5PA/JW6hiMHmJGQzBCMHVqGswIow3xoi1BB/wbYv3Htc27O3cjg6NxSI2Ie8ce9MpddOyuX2GCxcvMBiOGIzGDMcDjLFM93eYzacMBiOatmU0njDZ2KIoB4gpI4sleQUFSd5UIRzLbXlzyWc9uE7Tlf40+9xjYzT4BJyOtm2jU4CCEHJUDdbGDGQRS5ZMurBkmh/kSPXxZWPSXCXVZu+TFGM8yhxk8fY5VkuKVR9ZOmA9iE2METFLs8sYE5er4lKJuBBiJamY5h+Wh7cm1qbP8yx2XykLijwjS4XsYwHTgDGx1JvNMsrBiJCoUnKYwbycT93lwHgT4uyzGlyvCySv+1gBId5AsUn4DlW1iOn1Gk21LBOKxJzIMiX42Mium4xpGkM1z3GdiY3psqi1bBbLo5nMUlJQZDHD2XtHnQq4SIpHG5ORDZJ2UJMY8LKMOImYnnvVf4nkVfAQBNc2zKb7dE1LXS1AfTQb+0Z7xvDU5UsYmzEYjtg8czZxFjPE5qhC1Tpa5+jaiju3b7C/l+OcY3NrC1DyXMmNgdixefWKpvfDqHoz6bPPanA9DFkF1aG2OTFZA1VPUy+Y7e9R1RVd8v5BILNxDpVlkKUWrMNBgR8PsUbJiwxbm7RNfFlrlkmWpigiFkKgbVqC7xAkzrkQTG7JiwEgaBvAh5RGEqtSKfGmPpjjhKjdUsUl17WxYGhVxeKiKXt6UOaYLKMoS85fuMhoPE7FcAoQg1fFe8UHpXOOVj2u89R1Q1DI8pymvpyIxrEgT7xm5kCrLt0bbwa/4PHy2IBrtcjlw+stFQOz3rvIHm+bVHnJ4bqOuq7R4MlzQfM++Lsy20ik374gZ6QbRbPSZzlFlswvYg4VEjAqGO2r4PbN8BLlSaPJKBpjV33JagnE9XkRTUHvwTmaumGxmFMvKrx3FGVssmeyWFUqzwusSYwLSaatMfF4krKuCbFfs4IPMTcspMKlfRWpYy9bTw9bOmFWtnvMg8e9PBbgWm048PDKL8eojOKp6zn7u3ei2322T1PN8O0C10yx1jAoM0ajmEoStU/cP6RyZnWtTCWQ5xk3b90kzzPKsuTs9jYbkw2MsRSj6KnDR7aFKCnOFidrUuTRMxcg86n+nxKbNQBSltjxAN+1zG/dopnO2N3b5eUXX2Q+n8eCOefOpXrzFmNt1DICbVNTyoBBHlNanIIL0DmH7jrqxQwFnEbN1LYVXdvg2gKfl+m6p7ll/ObRMu1TBtJ5ItvlIf08nwXyWIBrVY4C7KS2P8fJodaly3yK2M+4qRc0TR1vqq7FO8V10VHRDQs0lLHJuAXb19NImsw7pWnBe8divmB/OmXQtoyHI8ajgBiLLQpslse7Gp+isQfsBmyGmCy6um3Mdlbn0baLm9gcGUTzrO0c1WLBfDpld3eH2WzG+ewC50cxmVKsARsbky/qBtd1oAWZjfNDSVO3vl6+69pYXhuDiomazHfROZJy11gahAn0y7Jt2vvnl3HDNwu+HhtwHTULT6vBVruBrCxN85YOdS1dU7FYzJaJharRLGp9mxjxAzIbkx9Hw5IsK8jznMFgyGjUYK1QpM4lqlAtKnzn2Ml2cJ0jywpGk4YsL7FiyLHEGokZ1mTJFExkopholnCvOB+TG5udim43djyZ3tmhnk5p245BOUQQxqMxw+EQYy3zxZxFXeO8Y76oaLuO0WKB84G8KFAxqFicj/O21TIHAUU0JNOw9zz21+/Ie0+NXPEkvlmABY8JuI4zCx8YYMFDVxOaisV0lzu3I73JB4eqp21r5rN9vOvo2iGESWrCkFOWJcbA1tYWWWYiXzDdZKqBvd19RITZ/pw8zynygo2NbYqiZDgYsrmxSZZlDIohg9IkoCXTawVc3ntaF0MEt29d5fbta3F+2DTL7pEbG5tsbm6xfWaL7a1tggZu3rzJlVdfoW1b9mdTmqZhOBqzffYOeVGQFQVZMUCBtouOm5VskjgPdW3KCjioO7+qwZbRBBKPvmdz8OYB2GMBrlU5DcBey2yMpdNifEt9LB/ddS3eO3o/ec+Kd67FuWzpsADFWkMIlqIocK6MN1gi/7qupUs3fh0aurajyx3WFPjOIyoMygGqsRipz0KMg/mAEU+sIhAB5r1fdi5pqopqOsMHj/Oxk0mWZZTlAGuT8yKz0UXvHIvFgrbrqBYVTdugCPlgQN61FG5AEeJ8KWAwYpacQFmlgfUlAdL1OkpyOgDSspzqoXWPuzx24HpYEoLDNQvatsK7BnAYCeRFFt3qRnHtANfFhnd98NiaWKuiyAvObJ9hczIB+pCqLj1tIcRmdF3bYoyl7Rzea8zZEovNMma2psjmIIIxGX1XldDFSr91E72BwXvwDaPxJCVENjjnKMuSzc1JnGcZYTqd0XUd+/v7zGczVDXW2xiPItXKdTE/TSIjX6wlywfYrMAHpXXRPS+quLahqzN826LeRbCIPRRM7nmHpjcNV9a9GWQNrhMkut8XtM0c7xtQFwOwZUZZFmQGXDeg6yxlkUdwLQFmsFnGZDRcarPlK71577h9+w77+/upn7FDQ0fXBZyLjIiYGNnncsV6HRqULlVpqqqK2WwGKE+eP8sT584ASlUt6FzLcDhke/ssZVmwP91nd3eXqq7Y39tjtj8ly3POXzjPaDymaVv2FzO89xhjyIucjJx8GPPDfFCoPc7HKFvXNlhj4xzUO6LTJUAqay0SywIYiQ6eXtYOjbXQm36qARGNvD6IzeesSZ61DNUQ2etiDrKUk6lpjSXLbAKGj879pLVC0GWISDXOaUKItQO71iEmBoRFE29P4txLg6ZcLV12t4zHgN4dpxx06+lrXISgkcoUYjnsLMvI85iSUhQFPvhlbA5SrcYl6331mmikUvWN9LyPtRpNYJUbvwRY2vNQpGt1evsYI+2xANfxHr/7k9W6GILH4CkLy2Q8AGBY5hRFhpES1THOD1L5s1jeLHilqRq0yBkNRhRFGR0PbYsPntm0Yrq3h/M+mYV9hanIh29bR113KQAdnZHWWsaTCYPBAJsZysEwJi9mGS6kB4CxdF3kEC4WsZ1r5zxiDHme0zQ1znlA2N7ejg+IPGNza5NyMGB/apktZoTgybOMwaDE2pjMGVsPBdq2oXOBarFgur9H07RsbGzjXQwHGBOWPe3MqgNjVV0dz4p6LOWxANfDkgOvI0Svl0ckkGeGQVkASpFqDMan+XDZF7l3noQQ6Npu2Uc5ywpUu5TcCNWi5s6dvaXGOXCkxLvSuUBT1SvaTaNjYpBaGGWWQTmMQNZA2dQE71N6Smz/WjctVd3ggyZnRo4PbnnO8XgSwZNZhqMheZHjXEdmLB2QWUOR5zHQLHH+GY/d4TpP29Qskuu+aWq8c1Gzqi41XZyDrVTpXXn2vVnyuh4bcL2WV3BVjtNyR5dpoj31zeIk1dwL3uNhaWKRahqGEE0pDQdmmHM+9jruHE3TLruUGGPvYg2prhhQiUJvTDQXTWJqBI1mY9u1GGdiQ/KmifOkEPB1Q9BA07T4EJYAjec0ZMUARAkhI4RyqT26riOokuUZpaYqVDbWSGxdwAdH5wKui+ByXYdrW4xY1Ic03CPewBU7cKm0klNRVm3Fx1geG3DB/fMI7yLvquKdo60XdM08eQv90vEQtyPWsiCyJbxXggguczibIeKoqthDq6pqdnd36brYBijPYtA5aEhNwnWpAcFgbB4Dx+nJb6wlAF3naXHMq9hQYTZPhWac42bToY3DWsN4MqIsS0Q8bedQhfF4yObmJPEHo6OhaRtu3r7FfDHHOcdkNCYMh4zG0QQNqiwWU2bzqKUWVQSZBoPNBgloLQZiHC9eGUg5XssM6uXF5SDP//Hu2AqcrkDNAPgoUKbt/w9V/Q4ReTvwYeAc8KvA16tq+ygHe48x3vNzL/fSWIcARqy2GzVXck+nMtW9VlqV0DdfEFmuDz7gk9Zq2466bunaLuZ02diFUkIgEFJr1dSaqE8+NLGjpUicy4EstVHXtYTgqeuauqlxXUczq2nnFXmeY/OMLM9TSYJAMFFzlWVM7oxOmZhPpqq0bYuqkuc5QDyGtXF83tO1Lc7HlrPOa3pIdFiTRZO0N/8i52sFTiuxw/6/FWbZWnNBA3ylqs5SibVfFJF/AvwV4HtU9cMi8veBbwT+50c41geSUwMraS7XtcwXM9pqSlUvaNtocvWVV3pzq/fGeRdv4OFwFGtQZNnS89d1LpqIPlAkRgYobdsRfIcqOOfxrp+/AQjWZqm2vEmmYUpPadtYZiBlGccukLpsEtE2LQtjaJuGtm2w1lBVCxbVAmsNeR7zy6q65vq16+zv75EXsZ69tTamv+gMHwJVVSXTU3FdwAVdZgb4zCVy7grH6fAVZhVBZpWi8ZgDC05XoEaBWfqYp5cCXwl8XVr+D4Hv5PcYXA/iJVwF1lFwqSpNU7O3t0O92Gc226Ou5/FWkRh78j7QNF1yhwe8iy757e0zDAaxYpNzmki/HW0TAVbmA4aDUZq/zXFdzPRtG0fbdRgjab4jFEXJaDSKbPvgl+ZjVVW0bZxvdV2XTErSXC6aoW3boURtqyhlmTMYFBgrDIro8azrmmvXX431FDc3ufTUk5SDks4t8PM5PgSmszmLqiYEaL0SAmRZQZeqS0XXvhyw3+NFPPJD9b9Xinm9CYAFpy8Kaomm37uA7wWeA3ZV1aVNrhDrxx+37yMtZ30/86wTNdaKhJAm8C7evD5ET5s1EnOdtE+dj8CKbm4OOSA0HJiRB4fv2Xa6nJfEh4SsmE2p6LPEDifxe8ZYGbDMlTpE3err2JPM1FQBOGjqG5bSZ4wRgnc4Z2mamrqqqaqKclDilwAO+PTdgo/lAYIuLeODmoh9uODQxe3NvlUy4oqz403izIBTgkvjr/qFIrIN/J/A5532BJ9t5az7m9Y5R10vqKo5XdvETo0Sa03kWR7d3YsK10Wzr206sjxHgLKIOU61a/AugEKe5RixhBCYzxfLp/1gOCLLXcz67XK8d3RdnONkTUudZZExkVmKskCMsBE26AYlbdtSlAUaFOMF4w3Odezt7VJVC7IsYzQaYPt+zOnVdbEqVV3HsnCLxYJyUFLXdXL3Z+R5jrEB5inwraA+PgQ0xAZ9mUtmrXeRAiUONPZj9iFmTRuTYbL8BLPx8ZbX5S1U1V0R+Vngy4BtEcmS9noL8MqjGOCjllUNsNpvyrmOuq6oqwVd18XUCivLAKv3fa5WX7SmIU9xpLKIHR5rbWNdQSUW+jRRK8zbChFhPB4zHA4i0yEEOpdTVRVVHeNcbdZhm4Yss+TFiLxIPbmEJQh7cOWak5NR1TX70ylV3TAaGcrBgLIsorbyDZry01zX0TQVi0XFYrFgMBzQ1A3WWgbWUhR5LOuWgsiRRRLLCvRJoM65uM451DpILWY1QHAuVo7LwJg8NTVLF/1NgrPTeAsvAF0C1hD4k8D/APws8O8RPYYf5NTlrMPRMwAnBRZfx6/QA+TInncdIXmzlk3e9ICaE4KP6RoprT8EUA6CwcZGM65v4N22HXXT4pxPWb2ynKyrED2B6vHpSR5CwAWP8x6RWFnJh4BXH72TwUeTMDXAEyPLl0l9kBHINccGi6LYriMYJbeWgc1Qk1EMDHlpsAVgAioBj8OFeJ4AqDEEMQQRghhcgNZ5bOfIS0WMTf3IcoqiiN/Zx7ibtQYjikgA9YQQ6zpau5rbla5+fz3Sj9M3b1jN8Xpc5TSa6/9n701ibFmz/a7f10XEbjJPc7uq98rW8wAxQQLhJyYgZGSJAVgwQRYSExqJmWWJATYMEUhmxhsiWUIegMBCsoQYWCAjTy0eMmJgYGL5Ua+qbnvOydxNdF/DYK0vYmeePPee21TdU3kqrvY9mTt3GxEr1vr+67/+/58Cf0vXXRb426WU/9kY84+A/94Y858D/xDRk/+GrQDzxe/3oSNz7/71UL2pnrx8ds4Iaxuw7nKkfCHxsSxscoQ4y/F3cuIO5wO//PnPOR2PnG5+xZws2I5mu6Xbqbt9zowRjv3MZ1/JlG/XdWy3O1zbgLMkU4gkpjwxpEFKzGlcRkTmWcdTQiLbWYYWxxPzPFFyxrUWhyVsPGEjAqK+C/guYIxh63YYA8fjkahrv6dPW549bRh66EuH2baAJdmenoEhz5zHkVKgaTpCuyUby9xsmPzMKVu+OIy0U8G0e56GrdgcPYWm7RTAEXTS+YbgC8EmSD3j+RUlb8XB0nVghRNpdS1YvfNSER0OA3jDox/5fxu08P9CPLnu3/+PgX/h271d7SJebrVeqPljDbBvCqz7f8tl7UGtnlb6akX0kkQhSYMr1+CSad84D7x68QWvXrwgzrekbCgmEIKQW3PODIP0lYYpcns8cXt74ImxbPdXWO8p1pBNIZtCLJFYIlOaGOOwBNc0T7KOmj0uyHzVFAfmOC8UJ2MMLjh844VtHxzWO53RarHWMqeEOx4x2bC59jz9KND3katXnj4F4QEOEzEVhjxxjiOlGEzX4ZtWenGuIVnPWAzHYWZM8CwWjG9wzrHZivZHjBHvznphsFhX8CZDmkhTT3SGUJLIXOfaq9NGsqnHRwLsPekhv4sMje/eXZRmZmVlXzIDNOBE3EGCzJilL1SU/DpPou/Xn88YZmW7q457XY/l2vRF1kLBLzfvPW3T0LZyYnZdR9XRkIw163CjcA/naWLyooxbuYkyrXxxWdEy1jlH8B5jjcDyyogXlr3Inh0OM9M0UhD5s2IyLlowmRAKTSMEYe/84o8cY2ScZkJoCE2gazu8D288BpcSiXV9Ku6bd1sad2nwPHz9fOTbjxhcP/weXgKBi6R1hxqwmoEbazDOC/I1imRafzzy8quv+PKLL9jvHM+fNniHcvQEfhaIOmIotE1L2kS2mw2brmO76djvdzy5vlaQItG1Am6MgwAH9bVqIEXNVta6xUihZFnDrcq4hiZ4uk0n2h3TvKwP64T0zauRaZp0Qhg2uz1+SuQyE2MGGoxpAYPzneq8wzAMHA5H8fLa7dnvdnSdaiXeLxnuTGvXNkNexmi+qef4HvWPgR89cz20q7/rrpfnyfGvaywod9ZbcucqwWwpRueiYpSG7zgyDAObtsXZBue0UV1YmaeaZepMl3dSrjn913uvARHEqMHZpSSsOobWWpUGsCquqeP0mgkuZ6jqRcNZe0eaOuc6YFFusgAAIABJREFUG5aY5gn6Xp8hnytng3NZG7+IvmIxC60KULa78A+D9zJV7f2dQLo8KquEgrzTJcL6LQ7Te7G9Y2Xh99vz5n4pAlLOVV3zhaWzPjCnzPF44HR7y8sXLzjc3nI8Htl2AFs54XWWqQ4pWmPp2pYPPviQ63mibTvapiV4z9D3fPXlV0BhHCWTOOu42u/x7u7ultF3MburQSpUJtb+a1FDhmlmHCQ73d7eME0T53PP0PfknHAuQSPIYmMbMI5SJkqpDPnlsiKKUXEi58Juv+f5B4lnzz/g6bPnXF9d8eTJFdfX10DhhKCBpcj4SpVMK1pe5yIIqNN9fPd6WaThfoEYlt+VhT/m9t0Kh/XKqj9UaF6FXEpO6tZo7zwvpcTNq1d89flnfPnll7x48YKbm1dc7RyGZ1grfZ1xGJers7OW7Wa7CMnUkshay/l05vPPPpMRjxDU6MDx5PoJ281WS0C7gCPjKL2lxgsqGE28WCPqCEuSEtAaQ9/3vPjqBX3fM88zwzhiyDStPMMaR9O2eN+Ss6GUk/TkFp15dN0n5e719ROaZsPHH33ERx9/wtV+z/XVjmdP9uq4MjJPA1BwXuD/rI6VpbYaciTlqEM5ElByuzTBuwiu+ssjD7J3LLi+w95eK8A3PLvwWjq7g3UUPUmHhQibtUGcL+g/l8ON9eZUijrFyKyN4HEcOZ9OOO/pSqF4f3HVL0t5l0thniadg1pRghp89bHVk7l+1subQc3QjZGe0/Ll1tt6Qq9WPyhTw1pHt9ngXEO32eCVDVJtgN64yzVz1RK2KkG9iVP42IPoTds7Flzfbiu1AfxAgK0DvgabDeVyLOKibMk5cTjc8uUXn3M4HMhpxjnUz+olIQgHbxpHwKhVkNXRfoGZx2HkeDySc+bVixe63mp49vw5280G6yytD3RNg3OyJsspkSYR8TRAmiNFx/Ib77X07NhsNjgrk8HeWtoQuN7v6ZpGG8sOYzKx3JC4EZWmOZHStMxyiSRAwIeWgqFpRJbROc9ueyUsfidrv2meOPdgdH6tZsiUBOWMMRJTYpoixlqGYaAfBor1xBRZBrYeCixF5e39+x/p9lsdXHABBl5sr8l4aVA9dEFOOXE6H3n56iVD31NywllDihPHwy1ewYikc0tFVZ7KYmBgmMeR0+FAjHEZ0ei6jhQjV1dXbHc7ts83hBCWW0qJ0/GIM5KlcoxkZJYqNArlNw0b7Wk557DG0vjAbrOla1qapmGz2VDIvDombo63wgGcE8mKZIBMOItZuPetgDhZ7Ig23Ybf/70/w/X1E86nIy+++oJ5nhiGInzBIpk4xqizbWkRp5nnCWMs0yQgkPXNIhBqaml47zhpT3mVAXjk2291cN3FL15nuJsL2Vdj1yMq08Mix5zmSfpN4yj9J2ukPDKWnAqZjEE4hfqi0hfLmUnRv0lH+GNUeWetxerjSsp6f9HhRelPCWhhZB2T0iIVkJO8Z9bnFaAYs1xIgpcx/BDEZ7kgpOKCWco1k2v5qD3znIk6ki++ywHnw2LAByzjMzOiH0LJxKpWVQS8yFX/PiUwZZFCWEz1ylqGm/tXvXrQ3pPtNxxcP1yX4/JV1nVFWVHB5UHrEOHa6yrMQ8/Ynznd3nD78iWvXr7AGGjahrZraWwkx0jKsNE+Vs6FYRyY1WHyeDwS58g4ythGZYd4IwbgJWbyHMlzJE2RiCGZSBytNIJjwhur3sTyuqltxTzce4J1zNrwzb6akHuudrtFJ9H7QC6Jm5OXrLWQYApRPbZSRlR4S4+1jqurjs1uT9u0WOtE7yNlpnFmHEcmMufqT5ZElzArJ7JKaA+TlMn9MNAPPcYH4jwJ8wXAZE1R7/wgxK9t+xEy1w976Xo9e5UV7i0XpWCRDCGXculrzePINAyMg0Da3nuafRAGeymUPAlR1VhpGCtqRy6kOdKfzkvZNE2icFB7VgazZKqiwEhJjgKkEhe2h1WtwTRH4jTjjCU2Ue6rrHMtQbEF7zyt0rEqqJKyoIQUoRgZXYyq6rXcctbMXABLCK0wMYxZQAnpxSUokbKUeBFhulfBGyEhxxgpxjCnmTleZO2cNbDkM1zq7tR/3pfk9Y6Uhd+/v1WBjTuo4T0YWOsjShZ5sNPpyPl8Uk2/WR0fRecvAC0FZ6S5KieFqMjWFGEQfT7vHKYR+57aDPbOLY81Bh3vD6KroeuZxq9Q/TSKm0jQmTCKiOSI3LU0j73zRGvJMZFdouTClEdinnUqWYRHpax1GKySmTPzlBmnjHMCmtRp6dPpzOl0ZhpHjBMYv2RHyTLQaY3DGtln1kFOsjZ0SgIWUKcswVTZuHrNeO3Qvk957B2gP313vNbcy04lG9Fav3MIy3orGdJMSYn+fODFiy843N5yOh8Ypx7njDqVtHTWs3MNzoDzHm8sK+k3Y3LBIuWf9wHbNFBB9RpwFHKKWAzbtqPrOjFAGAYANl1HEwLjNEHKDCEsF4GSEvMo91srevQpBNFp38441cg49z0xzpwOR8ZhxliD9x3OB6ytQ5jiw3U8jngf+JmxXF9fM40zv/rVp+IX1gT22w1t21HyTMmCYnqX8VbHcaZGzCisYxgn0UWsGo4VBnxAGL7cOxLwfgTZj5q5yg9RICxN47u/1vVWWf5YA6w6lAjiJdw85cYhgIJzRqWoq/VP1Wqvz5dXFeK9ZLuFUlTUR0sv3aWOwFzQnOrmrCV40aGoCOKlRDVarsE6Wn95W2hbixRBxmKh1N6ZWXtS2ky3Vn2Mm1ZEZ1JkGEackT6YCIFmCjK97L1VfqUhZ1HgdaogVcgXPQ8u2mtm+XUJJt0vUlnce84j3d6RsvDrt7vrqjcfj7sXzNr8EvNtSpZ1hJJ3U4wL8dUATfAE73Cq6ed16thSOJ/P9P1ZZ5pkjUUpdF27yJHVtV6Ms6o4ZSZVo629LKt8xCbIbg9eQIkmBPa7La1O/85zvGDJizho0zQL+16kogveWTZdi48WjoZxnES+uk1YL1qKtbfbhJbrq5a26/jok5/yez/7s5xOJ17dHJjnhLd2Ye2bEqHMOGvomoa2DeScMBRScozTtMi9WVMvYXevbusxK/qfHJG87K3Hv/1WBBe85QXutdWylIIlxSXA0AnkGOfFhtWYotoYDqdDls5aGi96EOPQ89WXX9454Y0RHy1jWPpgWS1OU5zJyTKWgnVO3oOCU2WnpuoDeo93FmsCdr9fRlNWdruAHsaswSXrKfl23jncZkOIQhQehgnrHN2c8EHQwpwLFEPbtGy3GzbbHZ/89Pf4/T/7BxwOB7747AvGXqYCpuG8qAtbkhj32Q1t2yqTv5CTZ1DpAdAWhzQTljrh/lbDS6xf35/tnQ6u71IxmPvBhfa9NLBWJ46KHNZSUEs7UCGXOselZVUSsEBmvIyWea87m3gt8wwrcljpTLXEcxelYeUl1tdcxvnlj6T6TcqK1tXMabTUFNRQRGWw9qL1wPK+4tWsNkdOjMW9D7TKAhn6Qh8TMc44W/BWZNTq5ytldVZZStOylqdl6QOstzvH4n1IVfe233oo/s1vU2H5vCgUSQO1KDu+YE0RHb82sNt2NE3AWqBknBMfLlMKztqFoRFaYUZYZbIbY5ZsU0ph03V3v6kxbLpOBGGGQYVDN9p8HhlVcemS1+c0yIsOQwKLDasxhuPhgLWW7XbL0ydPcNZyfXXNT8rvCRRvvarpGsZxpu9HrG3VNqhR6TdL07R88slP2LQdn/7yF/zi53/C8XBgt2nY7zpKcQzDgPdQcmKeRF+k73sFUhK9ko99I9kPNc+rgaUw0PJz3S+PeKm1bG8dXKqh8cfAL0opf+ldkrO+s13wBmXLVI1Bq6P/dfREAgxCcHRtc1FyFZwxBB8wJavBQl5QwEblnmtw2QpklKLlm5R9Vf+9znXN00TbtjQhUEph6HvGYVhM88y94IqgGaIsClF39AqB66srnPNsNlueuuekLGKls+ooVjntblOwzms2lfWS90FETJuWw80N53PPzc0rTNnTtSJ9ME0T4yCZswbXOI6q+isXlVnVpHJOGlwW3JqqLvHgO5qgjzzCvk3m+qvA/w1c6+//Jd9BzvphhNC84ee32C5OtLtgr/x+iRbmlBnVsC2qGIw1he1mAyUtyB+qxDTPk9jiGKPGBisTvspKg2STEMJS2r35oxYFLOY7ZWQtBevzvTLpY4zL82p5V8vCUgqn04mbmxucdwwMFKMdY+47EMv7bDYbNpst3rmlT+dDoO06uu2G/X7PPI20XXtnoLLcG+MXPcQtOWdabUEsx06vXLUULwsFTVW3HntEXWxvq7j7M+BfB/4L4D8ystd/IDnryyLhWxYMd4aD7nVRKg9I11kGGMeB0+FGdOAPB1KacBY+/vhDgv+YYRh5+eIrQRGnkcMhSVnoHM+fP7+TNeZ5VspT5urqimfPZParIm73tRBrYFTbnxqom81mCap6n1ce42Vw1aCMMXI6nZZ/X758ifOO5umG5slG9kAGi8WUFYrfdBs++uhj9vsrtl2HSRFjDNv9jrLd8sFHH/P7v/8zdtsdlBnKJEhghhR1faXfabPd8lOVAmh0Rk0axhasF2ZiTjLfpaYS9bgavvVR/q3d3jZz/VfAfwxc6e8f8IPIWb8psN5u19+Hey/vf30FLaBERQglc8kA5XbTsdl0eHficPOSCTmZp5KXXlbXdeowMi/IYD3Z9/v94gyyAA5wJ8Dqv5caGpeBVLNEzVz15+XTX1CU5nle3nscR6yzXHUGf9Wu1Ihydy96HzRzydxWBXKsDxRjaLuO3X6v3ltnGSnRfZjvQXxVysBaCzbcLfOMgSJuZuUS4LhA6n9XFupmjPlLwOellP/DGPMXvu0b3JWz/vO/BsxIy48ia6jaPK6boH4G4ywxzRwOt4xDT9+fhMldjJB9EQqPUKkK0zQy9L2wMeosGBIExor8WdM1+OTxwS+LCat2qMYYuk23sCsaVYSqwSUvJoTa6v64TCiPg2Q8U+hUkXcYB6ZZrF9dEKOGGrSZwjhNmNNJEEQbRMjUBa52VwTf8uzpU549faaukhuMqzoZRgNRGufBeVzTEpzsz8aD95LChPSe9DkVhdSLhIqIVsTQGUvBiehoLmAyBR3srEfoDlft8W1vk7n+ReDfMMb8a0CHrLn+iHdAzrpSheRkrWsvwx0mtjFYZ8FY5jjx4uWX9KcTOc/kNOGdQ76CdGGsoojH/szLzz4lxUjbtrRtu5RxTdPgjWdjN1Cg6Ro530yRE9+JfNl2t12CpoIfS3AVmSWb4wwWQhtom5bz+czt8ZYUE847dvsd0zzx8uYlwyjIYmiCSLOplHYpavXDhPOe7faatnEEH3j+7DkxZT756BN+8vEnbLZbtrsrcNr81jERiyU4aVK7xmFNp4BPxJqksLusR6My42v2a4KQna1BL0YWZxxYTyxGgB0KRRt0j10MtG5vXn3rVkr5T0opPyul/AHwbwP/Wynl32GVs4ZvJWf9Q1Td94GLu3+r4ikrc0CbmFksR+d5Iqe4VDGX/9bXzXktvxZ61GUTWQ3Gq4DnfdcRo3NhFT287GE5J/Qh/bSAEn6djOpmlXADyYS1PEwqR11fv651pGRMCzs9K0pp0M8QGkLT0DTiDSayautzBaxQKofu1uWzWrv2x6xb2gVQy10BT1bv48uadD3Gy0TC1xy5x7Z9nz7XX+Nby1nf377fJcwgykJGGbwli1iKBEsWNneKxGkgp5mxl3Jw6M9suoauE/g9eI/Xk0Zge6Uf6QnWtC273U50MTRz1fVPDSgRimEpeyqrYrvb3WlCe31MZV7M8yyybHUd45x4H1cdQD1pU5Lgcc7htKxMSS4WuRQykZTQ79NR1cfbpsEYx6ZtaRtpA9RgzSkznc7EaeJ0c8vh9pbD4UDjDW1jsdaw2wYxWy+ZlKy2BgbKsKphadrW+bUZ4wwmsKy/lqWXZY2s9yB7fVuXk78P/H39+TvIWV9u33fvrsigscgiuojrCKXgbaZYGZ+fx4EUZ8ahZ+x7hqFn03qRQwtOHUisGnyX1RBBUbwaJM452lb4hBW4qEE2jqIOVa/4Xsf1t9utgCNKaaqZ61KttgaWU5BgUZRCLxzGrDNUpUiJiYySRCXsxiLOlcK6GHHW43xD225wXpR0m9CsTeQiPbTx3DOez5wOB9HIPx5JjYPs8d7hdiI1UHImRqMZPaoqVWVmyLqtpEyOEVscxqGa1eYuofB3wfWb2r5/gJnlp4tX1KqzlnlFVWGh4JxdfIGtrTzB+lrSVJYruwRZzqLFJyWggBfee3IpwgwvhRRV8JNqcidlUXUuiSoImkvGFVc/lUi51MykgZKLBqCXwcqYRBBGwBInzAsKSQOztpbkRSsjJSliaUleHVVUYMZFjw8JxwrxRy0n51kEc5zxeFeoHtD3WocspXctKe+w9RGLLlh6bUYzGEak1t6DuALemTH/77i7F9hJjpi1FuO1QWoApGyb47yQZ3e7LcFbgd6D8AMhq7hKVraG50Dh1J9JMfGB+ZBOwYlu060ghROQop6g4iF8ZhhGjHcM84Q7n5eeWMqJ4MPScAbEFYXCeegZ5ok5Rppth0sN8zRxezoyzxGspdsKbWpWs7lcMla4SdiSMSVTUmLsz+QobiQpgfOBw+0tr168pNuM7I1n227IKTH0PefjkePtgZtXNxxubxgax9h6msZzvd9Q9lspPXWiumaskmWgU3yXA3GO5JjAZGltAdZYnPa+ULUs854E2DtA3L2/m7/tbr/o+xs1V6gNZFbxlxjFuqhtA9YUmuCVrAt1vXaZ2YopjPMk4yNAaAWYaLQsvHQbWYJLM88cRXYspsQ4T0zTRD+qy0mQ5uolnzBTmOKMSVJmenV1nOaJUWlGWENoG8mAOr8FiHQcDpNMnecnzjMlF1wqWNvgUhF71vOZUqC7mpcsF+eZaZwYB2k99H1PTo6SHTk1RJVnI68mFEW1A2ppmFQdSvQeRRynHhuDWcRzLqPqHt7xKLd3hrj7EC3mbfb9agskvy2k0YX4XuQkHQdSijjrKN7JoOTUY61hbgIhOJGsVpMFa430q6wlNAEfPNZZpjgzpxlnHXOcxRBP0bKscs7GraZ1WIHO926/UJpGnSErFOEhFkhFFiZWUUjjxNcq5ijIofbQjDZoU04r455CiXKiSxtiFuWqYnB+IheYxoG+P1MwXMW47L9aFhsFhgws/EZrDSlGxmGgOlLmnDQ7SdBQIKdCSUXpY26ZJlioadodqUsusx68Rx1g70DmWrfL0v6b93mFvvPSq8GUtYlSrIxs5MzpdOR4e0NOkwwb2sKrl7e8evElxsBm09I2wlooWShP3jm67YacM91uQ7eTkuz2cMvQD0vvylpD123YbpUGZMF68dKywWG9pWk79rs91lo++/wzXv7yV5SS2bGjaOYUqB/pqW1kTowDkj1zAmfwbSCRySTmHPFWQBBDoUyShSiIJLaxuDmSi8P5yOEgWvib7cj1hx/L7sJoMCvkriRkb1Ve2zrGceT25lb7XLM6vWQsBqMcxTRFUkjSK/NBWRsCZBgDzphFGi6/jtI/2u1HHvP/btsKYmjvpNRRPCrhYLlq5lK9sSYoQneqvr7D0CM5M1Ny0Cv2apnqnF3YGM67ZShynEZxKUnqUuK9rH/U6G3JWrrEdM7Rdq3OUjmROdOTNOt3qND7YtuqzxetwHLndauGYKmtCGq/Sk0SKkvFRFJMQFQ5uBHn/VpSctF1XMo1nX42wnXMSWhedZK7lBXFNPUVllkukQqoEgd3M5MEl3nk2epye2cy1/dpLC4NXIs4SJZEfzox9SdOxwOvXr7gcPNS57YanPWAaMRDITWOnKycD3rgU4qkUido601QvaQwOQqbY62YgVsrz9FSa1SAIpVM0JGWDAsw0XYdTdvod5C9EEKD0QazcRYXAqVKEkRReRonWYvNKTLpd6hezmCwzihLwiy3FCPn84mC0QwnAZGTtBLEQinK37qgmcyS5pnhrDr54qaMdXJBMcZSsBRjRW3KefABweGVCpVZbSSzZi65vj36IHsH1J++b2CxmgCUgimiuHS6veH21QtOxwNfffkFh9tXPH2y5/rqI7wTqH2eRyiF2DiyntBRL7k1KGopoxKZxJKZc8JRFszZOEtoWzGvs0K3iinR9z1xjsw54ZWpkSlsdjuApSF9yYo3RsqtUmT+yjeBQmE+R846mNiPgyhGlbJkaZNnTI6acT1OZeKclVIvzaIQlZLMaEmfS6D4NK+BNc+zyrg5nDXEaSaOOmiqa8kmNLRB5sISjoxqNTovtCrjlt4XdoXyS4GFmnjZQnik2288uO4G0oOCx2/1zIuXeP2RFTgYx0WEJs4VYVv1BsWvN6uSrXhPrcwIUTmSTLCe+Jef5lKZKZWM0bRnrMXo6yQ1qosxLs9fpALcxRxXfY/aoCtSBspJe4+uVP+tGbsUHAKGVPkBUeRVwwilZ8V0QY/Si8gdsc/FBrZerMzy+nVHr7RNKSjrZzaVBAxr+r9/gN4X3pNuP0Jw/YDMDH0p6xyVeAtS6tze3PD5p79i6M8cbm/pzye2WzHZLs7RtA3XV1dYA8+eXrPbbZimkdubG6ZppG0aPvjwQ4y1XF1f03Yt8xwFNfSOsrDXwQWPCx6vDiZOKUwxRWaVfj6dz/jg1cR7gzFWIf2qySFMd4HIIZtMaAKb7QY3efq+Z9JpXx+ClKe1xWAQAZpW+H9tu8WHFozDGMkkOWfJXDEzDwPEmRxn5nEUWtj5zOl45Hg4smkD09UW7x3egrcC3DTeKQDiFyjfGI+zHms8JhWYk/SzvAPjwdgl2OSi9rs+1zu6lXv/AujiH+1vKa3ndDzy4quvVFn3xDQOzNMkJSRiZrDbbkV/4vqKq6sd59OJ4+GWkrNY9my2OO/Z7XeEpqHAQlGKOTOqn5fzTvUpAtvtVp1HysK6sOr/5aJjt9vRdt0dIm+d7aoE4JQSJhuZEm67O+2AmLwALMkpBC8natME9tsO5zzdZk8ILbkYYjLkAsMQGfqRksUeiZwoalUro/wD/VmCbBo30rim4BtP9Wv2XspNYYkg5ShGtRCtyGjHJB6xxoJqddRDVoMLHn9JCO9Qn+uh7e2h+VIXX0Le1YHCtSycRUptmjifz6QYGQYxPsjOLuWdUJkEWcMXOTmWDBGXIcc6hr9oaFRfHNZhx8qgBykbx2lapLLneb4jElpHUSqPUNA7s5SLLguncdbSsldRm+qpDCyDkIJMdoTQEBMwZZI2dRcJOEVSX9upS6kp+3SZ8fIB58xSatYWVikFU7UyCgs4YkzCauDdOZC1//geBBa8w5nrbfidRY+yWNdkcpzJcWIeBg63t3z11QviPNKfD8R5xFlD4yEET386cD7d0jSBp09WzcDz+czheMSGBtcJqbYfBs460i9l3ZYwz0J9ynlRgzLW0iqDfpwmDscjRoP73PdQipR2pSxTyFWTozJMGiX7OiX/Nk2D855n1rK/umIYBkIIDOO4OFoa4Nmu4+mu08FLkbMex8jtYWCeImfER6tgdO1pMFilJ9llXVZHT0wBi6FrW/a7Dc4aQhCQY5pnhmESqyJbMB4omXmaGM5nXFNoGpFzu1x/SYvjhz9X3tXtNxpcl6RoeDs448ELXbn3y6Khp55bCl0PQ69lj8h+9UPP4XDAe8c0ntUtMhNTXJjolbxqMZQgI/CV1AoCVlR1p5ASNqU1QIxa/zSNEHs1K6Us/sc5Z0LTLD7INVgvt5qJ6rhLJetWkRznRO7MOa8iOjPWGHa7jt2u06BssdZjzEzfR3KScrm6mFSrIyorwwjLZCEel7X3VdWurDUEr6yNlC/6agqMFFT4ZwbXKBByeajKeiAV+7gvovPYtncsc5XXfl4xqDcFogHjMSUz9CeRCDudGPtEyUEYBbNlnmByhfNpxrskfaxYsCZzPg/c3h45nc7MevI5YwmuwfmAU8eQusYQmNksZnHWCUM96fyVcQ7rHC4EfNOQSsE4FRx1Vtgb1ukXk++1CN9MI8fDrfDxcsaV9QKTS6F1jqtuQ+e8MN11bbTxAV+EHmUSS5lsSs1QTm+WOM30pzPzKNQo6zzZWOZUGGNiypmpCIo+5kyfIg4pB50xJAe2dVCsTC03FtcaXAO+BRcyxs4UO5FTQWmdOB2rqY2uQnnUAfaOBFe5d6tZrdwLrLuXPrnaikdxMZnD7cAv/uRTzuczx5sJUkuJMA2OaTSQEiYNuiiPWCOu9K9eHkmzMDbGcSKlTGssbbMhNA3eNZiiDePikCu1ISUFH6LDeiWsWgkqVwpN19GmRAbs0JNNVFqUX9R9i/IiK+Q99BOn21sohe1my26zwWCWXpt3ju7qSntUUftSBVsytiTIuo9ywURRgXJGUETvxFpoPI/cfvWKlDIpgwkt2Xr6mDnPiXPKDPqZQoqUecIXS24C3lhKAOMCDnCNwzcGvzGEHYRtwfqE8SPYQpwi52GiFEO32dGFHYvx+TcPwv9Wb+9AcL2erVgC6wJ2f+15lWJj9OQ0xDnR9yNDPxLnTMmGko2ccMWKxZCWSA4oVqZkxR1yYp6lhKrhbWsPZ+FUoWu8KpemWuz1j7WWUnb+3ZvBFruUeIsjSgUX9OeUErOq93ahEdsrIxcbU0SzwjgHVkRDyUVcSaKY7Ml+EaLvaiGr+0nzRMmZFLWfhVk+89IwX5rmQiiORTU/SpH8ZRBysjEYZ9Q6SG6m/mwSkCgkSknCkKKa4r0fDa93ILi+31ZyIk2JHCOn45GXL1+JyMvhltPpRMmJ0DSE4Nh0gaurFu8MOQ3kNIApzNPMMU3EKAyFfIE2ir/VqqNxPveMk1KRhoGUk8iSbbf6PrJLU0oMg7QBSslst1uqIi+6vjMKHBQlDNfByyoaWsdYKpqIBnRJCfRzDYM4qZxubzgfbqV/1m7woWFOhX7MxCREefExAAAgAElEQVR2s9LIlkAPwZNy1jYGy8R12wo4I0gkzLNnni3gyMlTnDSpQyNI6ZykrM7FMk9RymSTVXBXVH27jcxzhdBQrY3eh+1tRUH/CXAAEhBLKX9ojHkO/A/AHwD/BPjLpZSXv56P+fBWlGEQp0noPccTL16+oj+fubk9cDwe8c6y3zUE37HfdTx7usN7yzQcGEdDTpFhOHKeB/X9nXS8QnyrfBJl3QrHn/uzwPkpMcVxgdy3u52oQvmwgCPDMHA+nwkhsNsJTO6dl3koo1B2QaeHq/uKNJ5LLovgTHWWBCgpk2axSB3Hkb7vmeeJL774gi8/+xRrHburJ7TdhowlZqd9rpE4z0tPKjQBm5RszGVwdUtwGVM0uKSATTqv5b3ozFtrmY5nzr0E1zTNpJjBqkcZQnre+ICks9r3ej8C7NsUvf9KKeWfK6X8of7+14G/V0r5p4C/p79/7Wbu3X6YbbX0cd5J9mga2qYRXXY94b33ovWHOnZcQFmrZsUlRH6X8lTVn5YK0AhTwXu/mH875wR4qEOFNUMZliZz1em4o/t+r6FnLr7T/VsNyipiIyP666h+jPP6s/b3YhR2fNb3q03rSz5j1QfpunaRkqt6Id4J+6TqzDvncc4rM8YQo0oUZEENS5b5rhVNvICltKzm/vd+hNv3KQv/TeAv6M9/CxGu+Wvf8/O8YXs4FA0i9hm6FhsCz55/wJ/52c8Yh4H9dsPh5qlC873Yp5rC+XTCmEJJIyVH1ZYQYKBpPPura0LwGBcwvsFejPILdG4Xsq33ApNfXz9h03b4IM3jaRyZxmlxKPHes9PMVid+KUUGCy+az2Ao3pPVqKHRPpcxZjVk0MCKScbrT+ez9JfGgTmKbasdzsreN0zJkIphnBLznIRh4h3tdoubo4jQGMNuu+WnP/0p+/2eZ8+ueP78Cd5bQjB4Lw3kzbYheEfbNmx3GzCGVzdnzueenA3DMDPPiVQSxY7YOeP8htCKEnCKWZj7xqr316/nbHlXtrcNrgL8L8aYAvzXqqL7SSnlV/r3T4FPfh0f8Js2Yy0mOIwr7K72fPDhh0zjgDOwaRuFtl8yjT0pjarSlETskgvCapbJvs1mQ9e1av/mtAek2ajkxRPLe0+36UT8s9vQhgbrrJz4sxJktZ9kraPrOtq2Jc4yWSx2rmYFOKxZGBG191Vlo2WuKi3qUVmpVbOSk2clJ6ccIRsFRAwxwRghZcOcCimibi9i24qp/l7Qtg3Pnj2jbVuePr3igw+eqPOKmOE5Z2laj3OGpmtou009AozjjDETc0zEVLAkzDRjE0DAN4KZSKM+YUzBuctZlMe5vW1w/UullF8YYz4G/ldjzP9z+cdSStHAe237eq34b9q+uXi8aBURQsNuv6dppYnZdC1xGuk6xzwOjOOJ81FmtcjCs0upLI4lXgcic84Y5wguLGhfpUg1oRGRmUZ4hCL8GRbBzqSKSXEWQm1lP4gn8VomGapPWGWWr992lV3TqV+zSleDMPqt1V6ac+QQ2Gy35FQb3QFjPAXREZSWQSFGCfwUxa8szRN9f+Z0OtKfewVuolLAItlZrJVeoEwKFOoEv9gQwTRHef0siK0AJg7rG5wLWBcUQlTPZcvStH7s21sFVynlF/rv58aYv4PoFX5mjPlpKeVXxpifAp+/4bkXWvF/+LVV9tvt7stHad2u9bsEVtXYm8kpEqeR08sviGPPzauv+OzT/49xHJiGI+NwYFQU7Xg8UkpmGvYE52g3DdvdFmOV4a6CnFf7PW3X0rYdT54+IYTAuT9xOB5F00+l0FJKWMQu1TlHmhMzgjoa0IFKe4f+ZHQpkhTcmKO8njWGRQZfWSBgaJqWdrPBh8C2C3z4wVNSyvT9zDhHzueRdOoZp5lpygyT0J6m/kzse4bzmS8//5zPP/+cg2oWzvPMuQm0p0bJukb4txRKLyM7TRMYBrFPOhwHxjljQ6HgMLbB+Y5ms8eHVoz4VKDUOkfQ72Ds485a8HZGDDvAllIO+vO/CvxnwP+EyFj/Db6VnPU3bw8FWbn4y2tXPQ1ZHzz+wgAcIE0Drc3E4Qwlcrjdyah/mUjRE+PqTLIoOGmJKAq4XviASQRhQhCBzc1mw267IzSBOYpwS0X6Yn0NVO4N1YivIpqsLbHl5+W7qPbgxZwVxojJgX4tayzFloX8CxCsI9hOyLnlTMZg3SzZL0VikpIsxZkco65FZ4ah53w+MwzDug/031JkjsxRQZhILumClGyY50jKkIsaNdXM5USUtDbASu0Y2noEf5e5QNZSf0dRMw/8d6WUv2uM+d+Bv22M+Q+APwH+8q/vY37dZhaZ5KKaehg9qdXdJGw2WG+5yjOfzD9hGntuXnlembzMJ1VRy7SchILCWeXkpRhXh0lqwixLb2oaBZmbU1zEROMcyTkxjSOn4xFrnZzUmr2SSUvmlcDLDH3P4VayYEkiC2eNxTsZu9dvLEFmrZTASVaIooBYZA4r+MWg3PvIPBflXiZ57SIG7JZV7SmodVEpMI6TOLYkg3Mi8x3TrNJxsrYSoAea0BFCSymWeU4YJ+Kgr6GBBWnovw/zJrxFcKls9T/7wP1fAX/x277hD7tb69Sd/JvnLLNKgG9EK8M6R7vfQ9nQdoGrrSfNI5/+ymNKFETMex2XWDPYrLC2LbJ+isqCX8Y1LjLMPEfGQdjvKYtYZ82GKUtfahrGpV1QByQlqJKKwEzkJHNoL1+8IMXEcO45n084a3Wtp8OWnbDfjbVsOtHjKGkkp4liRMotA80caIInRs84ygRySlEyUIqidGWM8ijFsEHk0gp9X72aRZOjlMykGdr7QGilIV0KtN2Wtt1SsIxTwtgk4/zp8hghlI+yZuDHnrx+4wyNB2lMrw38fIsXK9x5mrh7wFpIrsN9znsIDZayTAFf9qicvWtVmlKmkLQ8u5BtLtWILmNsWks49Qmr8ZdVRrtczmtRdBrXyHBhqcKaqxxARRun2ROmadEBLDkvxhH1M9Zz906rTMf9q3NKHcSkzr2VsgAq3jllayTaRhxQqizPejEQQCdTyFmUsJwL6rTiMMbTth3eBQErFMBYSGvLhzP3/n3c2ztCf1pwsnu/X973NU+tY+Ra3gBy4qaIiJfLv3kSddk0z5AF+eu6jmdPn9F//AltG9jv9rRNgGI4nc6y+NamboyJ0+lMVhpTTBHvPefTGUBgdJVsnudIms+M07iOdRiYjIpwonqBzgqSqMOY57Oo3sqaJ2tZaDhbmaUK3jPsdmqOrsKcFCgz5Jmi5WLbNsSY2G23WOsYp4hR7p+zBm8tm7blk48/ZLPpmOdIP4wK8c9MswieXj+5ZrfbUhAZ7aplb7VF4LwQm31o+OCjn3D15CnON7jQgnXC70x6oTPiHPPaIX6k2zsSXLAu6+9nsW/KZiscIFCvnGwSXLKuIGtwzTPzNAsUXSB4cTq5vn7CNA14Z9luGry3jDFzOvUUIATRd8eoolOKhBAWBDEmUbC9HNsvGWJMjFoOXk4cV7DDa3BdMjaGoV/4gkV7cDI0IhknhMA0z1IiahYzxmCJGKRUa0KLc54YM5utyAScTr0aT5QluFxj+fCD51xdXZFSWcZtzuPAuT9jveMnn/yE5x88JxeIRaeYjaxljTGE0BFCJ8yNRoY0waq5nhyLrIwMe2FmKUfucUfYbz647sTJxc41b18a1rK9kl7R0fQCLI2YFbteQIM64LdQj2AZ22+aBlVXu6AvZZVfVx/jZBat9OVmL5zuCxRTRFATlsxSy7Tl82vpuio4sbLX9TNVprp0G6poaPX0kmDO+jrWGowtKmJqF1WpWhKuVCd9SQ1aqpSAXpBEU7AQvKdpWqWTtfjQ6i5NOn1sQC8W3kvWkgubcgfNyh9czfXWQ/zYg6pu71Dm+rrtoYMhNb2gdbNKJxtsaJa/QpGsFZFp5Ys1TUpJDOiA/XaHyZGYZob+yDTPzAnmWJYwLzqa3zQN2WWyrYYEqh0xi+JhMEG0WRSESG1eTmqAGTm56+tKEitL/nXmdcPxUkRrMMYZ5zw5I+tFbWA7Z7nab2g2G3VfCTjraHKh7URYJzQOMa4sxDgx9CcJhlzwBrI14J2UlV50OKz3bK+uCXvxmQ9Fm8nWga7jjPFYK303GSCtPEgJspwL8yyoaGW3vPGQPrLtHQ+ur1uLXRBBlfngnBdhSqDKrKFqT3UOawEZFn1CQ9MEynbLOPYM50KcE6moBD3ckX9e+lWu3MlgC7HX1/dXYq/zF6VQWV5LMpfcd+dbGbP0ry7BlZgS0xxxueCsJSVPSvIdvXcUpJlsrcVbUcNdVanyBRFZGCpznFR52CKTyjXpGxoNDOs9oetwTbvsQ0Ae6FThaRnmghK1isCohqOUx1n3kV/cT96P7TePFl7s3/KGq9frh8C88TdT6/+KFtZAMCuSVju2C4qmHMBploZpLmW52gqtyGKzMN8uy8L6fk3T0DQChoQQhEQ7inNkLXkqsrfpOoqa4JVS6FqxTi21pKVa8QiAcZmtllL24kZd81QkLzSLmV9BqVx5xhQRkqlNcWuseiIHtamVhm6KWT8fpCIUJt+0hKbFemG/o24mMjVdoBhMuRgd0fJVUnTRrFUJyUL6LZY7pfH7sL2zmeuha5y595PBgDV4r3JfcyRNQstxzsqUrJ6MqHKtV7O4nDL9udd+z2qnKtkmUCyEIn2tvheQodP+kpB7O548qfSnM6fDiai8vJxkJmrTtrjtlhgjwygCNXUgEWCeVEs+SZ9MZrem5VvmIunzkq1RqmCNd/gQ6LZbaSd4v2TPeY6kmBXSVyFRZ9lvNzRNy6YNBC/BISq8iZRhSpJ1mnbDbrfH+oAPHaL9XtRMRpSFTan8wDVzGaP7/OIYOecwreqPuPegFrzYftTg+haA+4Pb8hxrVv+nmg1KLU0u32+diyqgY/r5TvellmW2gC2rMXjNHLKmcQtjXfyJ1V1yXqlD1dhO/LdkvISyqinJF1ipULOd76CNl5/njbd6QfBV7lrnvJIoWqWU1xku1VMMziv9qwBVNSuv5Rt1vkvtiex9x4RLspaWfrpv1/pXP7+CvW5RAv0OB/m3ePtxM9cbu/QPI4aXwLzh7hUSZMFsvQwsGmtF3rXqRCASZ/M0M88TTQg8f/aMGCdOh5cM/ShNYXQVopw66x3b51t+8slPaNuWjz/5mOvra5w6nMzTtPSocs70/aCzYQ1d22EakduWoUNHionbm1v5vBdo3XazWVDEcRyJc5QyTpV+k9oOZTU3T5VzWFn7pTCpIXlVFRYWuwFnadvA1X6Ld4GhP/Ppp78UU3LbYowjYzBFQQrnsF2L8UHXsGqNVMtvYzHG3wmmCsk8eDjt1x3rx7u9Y2Xh/b3/cICtj76XmZzFmXD3CRUWLpBjZhwn4jzRNi2bDz9kHkem/shhnskxqX+ewalgpnWOjz/6mI8//pgQPPurPV3XCXv8fGaqJFc98ftJ1l6bTcfTJ08wiJmcCTIGczgcuLm5AWC729CpFmG73WCdSJ/Nk0wPb3dbtrvdYq7gnOV4PPLLX/6S0+kE1N6aI5eZsWorlro+VGcSY+m6FlckGM/nI7/405/ThJanTz6ga7dgvfauwAaP6TqMD4jip9FAVmsgDFQr1jdt9zGoy+vle4Jr/Lh9rgf7xGZBPd50DFYM8TK8vu7RcnIUU3tLEojeOrJ3C6oXtXwyWU7CYIUVX5vIopXuloyzvvpKN6rM+hSTEoDTkk3qYOaCPpa18VzVd+u/tQSt7yUlqFs+hzD2nWYPBTwWDpj+rwI5Rntuzi49uMqsrX2yUr+TqYz1um/vlnvFXGQqszQ87u7xb8sDeKTbO5a53m67Q1W76BXnlMmznLhCbNUTw1kwfhHrtMmq9rkMLT558gTvYBx6Xr2MTCO4bkuzv8Z6z36/X9Zd0zRJQzdngf6NpavqT86T5sgwTQwFXr14Kaq+9WSniFDMJNQibx1dK4jj1fXVMtJ/PB4XUZzj8ahN4KtFQObZ8w/Y7sUGthgjPmIlk9Vd05b1fLZWAsB7C8nhrGe/2/LsyTVN03J9vadrt6RiiMVRjMUZMGqkh18zlwSZgEeXpPf6r734/Q4u+B4GFrwrwfUd6vG76y+9L1/0kazB1THySonSTGCt2LNKyQT7/Y7gDeezpz/dklNks9mwf/ZUFHe18bkgcUlJrQomNKFh026wGI5G5K8pcDwcmaf5ThO5Zi6DZME2tDRtw267o+1apmmibVtKKYyTyBI41eAQ26GGq+trNikxVyP1vPpfyrbOfwnwIfvAOyEqb7qWq/2OEFp2246maYlZJAFKkeAStdC09rYWZNAswVWPQ90yF0F1P1u9hwH2mw2uWkO8dSC96YHm9R8fqueXA3xxdutWWe1Fe1PeOZyxKxu+lIWrWMU6Qcs4Y5YRexDYPwSRzg4hiCtItdXRDyLZ1RCCpQlBpJ21/Ku+w8Ya0U3M68jKOE14lVGr4IZ1jqB0JmpvKyYRqEEuLAvtygmSmoikxBJg1TlSeI1JxFNLRR5YywH9Za0W7hbib6ryXz9k5Q0B9nhRjh8nc30v5Oh1lPAyi9Ve5p2H6x+XYRQVkUnjhEF14duOaRiIMTGNE13Ki1PjNE2LEYPVtUnTNATtLzWh4Wq3Z2oaxmEgzmJzGnyQpvSy3jF0mw3b7UbKQu+V/V443kr5dzwemcZpAUxubm6WtdU4TzRty9OnT2nbdnGtTClxcygMtz3WyGhN48XXuHMyozX3I6Pp8c6x3bRsO0EJS5yZUqYYBzQyfi8Rqk51FzxB3b/5Yl+Xe/v/tUN8+YeqY2DgsUtZw7tSFr7FtnaE1qX2nZLwtcd+/YvlnCkxYg34sOr4lbyKX9b3qVmEyuSABYpHfw6NNIabpln6WDXLySiMACFt07DbbVeGvGbFaZqElDvNyyTzPIu6k3GOfhikzaC9rRACvvjl+cezRyANs5B3vQtCwDUWkzJ5mlQzXm4YK981ZdXsvNjLdzKX/HyZqe5nrcvf7x6TcoFRlQu04/Fj8+9Yn+vtYKU3HRYpg1Y7n/uboHIBcmKKA9M4yYxRseDtkkUohXEcePHiJcY5pmlkHJX5oehb27bklGQEZJqZxlGfLxmrvl8t0SR4DcEHmiDAxRwjJcU7nzWEwPXVtThSOrEjMsaw2W1o2pa265aSMpcCKnDqvGTRugdTFomzGCPFWFF1StJqKDrmX3JmGhMxZZzPuLbBLtR185rH6v1s9VCA3QV+aylYEZaa9x53UNXtHcxcahbwhiD7uhrfWIsLdSF/70kF9QtucQbG84G+P8sfkid7K5Y6SRjrx+OJV/1EBvHsmmcFHqXB3LYtp+NpyVIVTaSUJXhqKWisWdZgXduxabf6+QbKJbEYaNuO3dUeMDx99ox+GHT0RSaBK6/Rea9a81koYCGItWyR0ZEYE9iCx5CNUdP1CZwnJVHGyqnQnwfGKdK0ia3frNZGToOruq6aNZgeKgsvj0fd5ZL89BmlgBGTwoo6PvYge1ut+KfA3wT+GWS//fvA/8t30Yp/COZjLfXkId8cWMtLXdwpFJz6y2vdFz3hPc6JxFg1N0gpka3Qf2pPaM6ZMY3ksgYXgLdG6UMwhmFRhVoRRZbZqVoSWmPv9KwWZkYdzaAsVwNrLcE3i0FC1Uyck5SLVSQU1uWkSBlIMBe1KUmlYLMoP1U3yYXqtJCCM1kNA3MdAbjYX/dBoMtj8FDmWo7JwqLm9UdXTtR74HTytpnrj4C/W0r5t4wxDbAF/lNEK/5vGGP+OqIV/3Zy1vfrh9eCzVz+8saDWO9M5eJEU96cXDDvwpM2BJr9nhRbwukV1nmRtI6JIQmDvG1ayjbDNHMehRI1jqsE9ZKdmoY4z4QQaNuW7WazmBp4K0RXCmtz1xZdU02qBCXroq7tliZz1aqv7P1u09FthRY1TCOzlpDC6J+pl6GcM9437PZXpDlyur1lGgZMLpySuqmkjIuZ7AM5xtoGxiK2QN5C8BYfxPdYgMg3X+S+KWutP6kZkdSjrCD+49/eRrfwCfAvA/8uQCllAiZjzPfTir+PRrx2kVyLw6+7xmXWBzjULd4YiOUiuOQ+2zQ0jSWnSHi1wXknilFppGhwdW2Lt4aZHoZRwIZxpD+dV0WnlGiaQJwnQgjs9/tlyDGoYUFBIPZlzFiDcp4mTlnsWvdXV3RNK6KjqholFCjJbG3X0XbS8wrjwDRLY3mcxkVaAN1LIQT2/opxHLh99YrT+UyJkdQPEBOt82ych0ZoWhX/q5x2V4PLy9rwob3+dVnr7mXs3qNN3Q+XwfX4M9fb4KF/DvgC+G+MMf/QGPM3VRz0rbTijTH/oTHmj40xf/zFl198y49n3nCTTbKXZIZlzH951OtDiAsrXilHdR1U+1g5pQURdJVFHqRnVUukRVtepdjWnlh9baVCmYu5LFbksb7XKkBa7oyU5JQWxd46Q1ZgQTFlaHKWyWQ1Dq8cSKdUrnXIUp5X5eBW1j1UY/HLy5fRkvc1MOji14cC7KEgk9u9v5o3FZOPc3ubstAD/zzwV0op/8AY80fcswv6Oq34O3LWf/7r5azfbruPVLCo2Mqf76fA2qupC2lZl/kQ6DYbojWM5wPn81lEM6OsTZqm4fnzllkFQfvzmVkbvnGOOg2ccTZjsWpVJP0l71YV36reVNdYcY7EJJ5b1R9YhDanxejBeKeGC2C9J5fM8XzifD4zzzO3x1vmeebq6opnH3wgI/82UEyjLA5hlZicKXrxaJqG3WZH16i53TxLYKt+IaDcRa/rvbJm3fvH9IGfa9Zai5Fci1YMibVETPqgxx9gbxNcfwr8aSnlH+jv/yMSXG+lFf/Dbg8vsGu/qTywAF+fdjf7ORXBRDPENE2yJihyIngf2LUdMWduX90sV31hVKRFmjqLqstCuq1+XYsmfJ2nQoCHnNeeVqPUpkVwU5E/E9Wgrm2JCjaM48gwDIzjyOEg3MPQtKK6FALYDNaLRr3zyju0CxnXO7/4lVljRLz0wkPLUJZ14Ort882l4UP3ldfuyeu/5vL3x719Y1lYSvkU+Lkx5p/Wu/4i8I9YteLhB9aKf/Bz3LmtRV8FMS75e/IHs94q8qVXTGkCCydQJNPMUqoNw0B/PjOO46K1YQxL0AQNoEVoBRagY9GBNw/NNWl4XaCFtURM6mVsjF2bvF7kqCsjv96cl/etQ5AVIK00pryMm6gWh3fLrYqg5iySb5Ou3XJOcqo7h9EByTvIq378h2QZ3lzk3bt3yVa/Kwvvb38F+G8VKfzHwL+HBOaPqhVfVWSdkyN/50qxXH2rUM3dA9o0Abvf4y1ars1iJnd4xTxPtFfX7HxD0fVTo+VUSmlhc1Rrn5wLOWaKETjbWku50IuowjBVp6MGZlTfZJkSVksg7wldUDmBLbvdjpQzo5qQ51IoBuJFINW2Qiwyso+2HCzgUoPJma5r2HQtwXnmaeT21UtkCFLk0BKF0gRoOwh+bSA/gNo+tN56/f6HHlkBjYcBk8e2va2F0P8J/OEDf/rWWvE/+Gbq9JEerAtgA4uM+j9wHJ11GB+IPmBYgYpxmpjGEdtKX0nAR7OQbL3qytd1VEUBy0X8Lq2210iOa0aBmm3EDAHjtR8mYIp1dUDSXcxzebWGNfWrLlt9rXUKWdaalTzs1M/LWi1NkxKTXaPoICqZVsvCB8Aj3gxk3H+cfIaLR5b7r/D4t3eQofHdt5KF9gPawL3/gFxE4joncBaz3WBLwqn37zyNTNNE3/fYtqMZJ5ye1FdXVzJh3G1UhCYxzzNVz7CKjTq7Zrmu6xBVWhnXr5zAXHRYUkf0jbLsjTUiA3B7uwSHcSuf8OrqCmMtT589JbSNtAA0+GJKDNPIOAz0w0Df95QYKUOPSYkGw84HonO4YnDFyLpyJ2pUPgRMCKBlMoqccn+y++2PxgP//q4s/K3cBK7O5Filot0qjFK3LM1hSsI4j9nvsQZCt6FpGobeMQ6TaMQ3LU3f43Ty9+nTp/pGkhqHYeDm5oY4TXjndQ0k/aumbfFe/JErchdUU9CpGi5GC1alPUkTGYbzwItXL0k5SemHIH1Pnz1jt5eZrzFO7Pp+kXbDQIyRvj8z9j2n04nT6USeJuL5BCnhM2y9J1iPK8LRbduOq/1T2qYhtC2mbaBpZHGVImQrk5ff2qfuzcXi+xRgP15wvV4xvfEhFeYtD/zt8vdy8dj6U0WulhLN6MpcJ5SND9jQ4JoW2zTggwheWre83yW9qd5Zy7RSiiBs1AqxLOhlfZ6tcmgXpVqlAElgFUoUi9akjicxJ+ZJ9BApaLYUlSZnpVntrVtYROKzVXGbAnU6uSoNZyHoCtHXkIvBZUM2Dqx8X4yT0ZPiFL2o68bV2bLiQq8Xpm/qRl7cU/f9e8ArhN90cF0er8v7HnzoBRp4cX/h4cACME4mcAtirJ3iLGIzqjZLaOQMLAXVTsMUz/anf46nYYd5dcOTssXe3tD9/+2dSYwsW3rXf98ZYsih7lD3ve52P7cbRBupVyBZCMk7gyUwyLBgYQuxQoIFSLaMBPKGNWwYFmwQXhgJMYhhwwYh8NoCDBtjWW6QabvVz6+Hd6tu1c3MiDjnsPjOiYjMqnrvjnVf1c2/lJVTZGVEZH75fecb/v/a0LbZ+wl5lD6n4aN+09Yn6zGBIcYQDVzudoTzc6y1NLktKuRhyJQikqeYy0yY856w2/H003M22432AA6CSY7Nsx1h9wOstTz/9DlNqyLfYoRaKnzvMFudNF57S/uoYtsGduee1Ht2m8DTLfQDPA/waQfOgncN3jb0bsHSn0L1EHGnPDAPQVa65pJcOBcVVBAyG1b+DPa/OGn2WZW2Kn2NviK3g5FmZZF7EzTdiNs/wpt+sG5uCnjh3zgRAWdIKdLHMBZrjav0C2OMeiagZK8MFe2Tj4jtI9LJU9adJZR5/A8AABmsSURBVJ2fUacNdTwfxzaGTJ/W586K0vbknBsbexOw7Tt2Q6/sUsslzWKpNapdzzAKjufxF19jjSfGjmfnl5ydn+Gsw/saEWF3seNyeK68Gv4C7xxN23B6ekrTNDgseYKfZm1xK8+mGjhfOfqNRaIy3Q6S2MSE6RM2Co2tqcyKwa547h5i/WNq+4gkJ8AyJzTM9MuWf9E+Ozo8DPWuq/Ic/FTeVJe8J/jiGNfeJi9+0tNein1WZ5JJtmd65uC1urGmv6uKqm5YLJc6S9UF0iaNNNTl/41kn85hrBm70W20e21QCRUp7zKrbsyeb1p3TUxPZkztq3DcFfJPyNrGyiZVLuWgxCboe1IWa0izgrCyCMdZKCoY43C+wns95rppdG7MFBrqaax/jApeKYyTg5ty9fF7jDvtm4thTTzu049hMYJ56nv+msTkRdq2HduXuq7j4cOHXHz/2/zw2XcI3UZlSr0yPWljqx1T9OTreS9huZyfn3NxcQFMfY113bBaLTHG5vf0DCFQNw3Nbjdb30l2tlneJyhPBj1cbi4Z4jD1CpqE6Z9ht8/oB21Adt5TVZG2bTHG41ytqX1jaNqW9fqE5eqEJ08+4OGjD2gWK6xXD196EuFljer9MJoXxZ02LthPIAB7v/jXGdX+7TSmyr33pKQ0a1VVMVx8MqbbS9HYWh22LImMUeQgry/mInYxRra73Ziqr32VPZ8dOeetderBvIrYee/Hfddj0TT7yHeYOTP6oc9dKeWSEDZI2qjecUwT773zxKiEnqWI7b2naRqapmWxXLJer3FVM/EgJj03pWb4YibzeSmn9w933rhgMjBrr4vzr26nbUMzg8xfhKK/VVLo5ftR1EOQifdir85lLL7yo0GXTo7ddsswqApl27Taf+hdFrJLeThzUErsGIgxjK5Xo6jc2ZEbaWPeRo1sGAvoSMKGARtj1vvS0E557R3RCr6qqasl3quAxOPHpywWa9p2gctsVJNRMy6ZXiJAv+b++21gd9645t6icBJ+Fsq2qsY4hXWC8lusViuapua8bRGZZHmGGLAkhuiQoPrIu53Oe61WKxbLBc45mjyDNfQ9Z0/PVB2lrlmfrHHWjZ6nKFGSVEWl6zuGvL4ro//eaXHXROUkDDEQ4kAYegYzE5KQiHc9VVU6NNRTWZuoqhqRyGK5Zr0+pa4WfPWrH/GVr/wYvmpZP3hI1S4Qk42rZM2LYbyUfdzkud5PI7vzxrWPvJ66YYL2uueKYYF6iJFK2tpZ4WhKipSi72G7UWmO9ZVOJms20OGGHPblYnTcJWIccqNvHMPLlOmlS21K3y+NpaGUn5tTY5dwUSRlrzUPkXMi3BhNklqXExiqK7ZYLHG+Vq9llWl3DAllOjdvDu+fgd154yqhWMnEFRyuwwqK8ZTv0nzBHkNku93SdR0pRZbLBd5qP6F1DpPXZy736KWcwfOVVzIa1UUlj2/quiiL0hXP1Pc93dCNJR/VGANXOarg6fuBfterZlgQTDSzNddAPyjZTCIyDCFze0SC64iuB1Q6Vd2PQbKYnyY4aqqqpl4sqLP+lvEuL5cSadDhS/J+H/F6uBfGNRrM7Mdxnugo3RXzZMF1LTghBrabLdvthpgSy/WKpnFTVo7cVDtLlsQYR+OSXHAufBglTR9J7PoOGVQsfCK7sdg8GewrR0o1kUjY6FS0iQYTlHRmCL2O9gvs+o6QAn3Xs91tNWR0gehCZpqqMKLSP8ZYrAPnKjWsuqFeLKnXa4xxkByqLQ0xp/fF26NxvQHceeMqKGMdiik8LHNTc+8ls5rLXpiYcnf8TKb1au0s8xA6i0uOlDNzumXa49lI05sRssjeXOXE5OlkhLEuJTn1rmFgmpHXzJH23i+hYndd32tW0HmdJGHy2qV/sTQ2yzjOPyNEGM+LjJnCq0uv6xIXL9AneNMm9zhavNPGddVw9tPtY00rHfQXHiDlb1KImmzoOhU4uHx+Qei31HVNbWo1Kq/1sNpM6e0QlEFq6HU+K5S6Wy7aDiHQb7cUGm3lQBQWiwU2e4mqqTDOqncyopwZKdKHIbdrTR5l9JK5Lhxj5NnFBZ9enuO85/HDJywWHlWJtBgDfT9w/uyCrk9KcDqqJqjgOEYwbkZdnceuRjbra87adH3/m3BfBXfauK5iqrUcGtd1HmxE2vc6pbNiu9sS+i3GGk21o19AV7kxM2it5fnlcy6ePWMIA5tNViaxWs/yzhGSDjuG7NH6XiVa66bWvc2knsZa7NblDo/iadTLJVE5IMmGNXqevMbbbLZc7M6o6prl4gFNm6VrRcPYYYg8f74lRkvfD3m8SqZLHm8Zz0eZxD9s7jw8cUfciHtmXNcXkw8NKpUi6dg/xNiAW8brjZkSCX1RG8nj92UiuKqUvHOUnZP9i3UWV2lxWsfzp3aqsi6bOoLmBqP7piFfglT46yPkVL6S2vRZ+1hDUGO0e2RP6idpyt5I2vvB2VuginCFx/NNdCq957Z3p41rv/0p7a275n2Fh5O/h9uW1iRnXW4XMpx7n41qR4ha0zLWsN1ttSi80LpWRaX1KUnjNIUYwTpL2za0bcvOO0IKDINliAPDZkBQ1qeUv9ViBZJRGmmBJIlhyPRppfgNmEEYgoaVfa8c9TFTRNd1m+fGHCnZUSYpBCgDNEoynOO9maRtPkHkIldpMbzXa6K3jRchBf2jKG11wR8G/i7wz3kVOus3jP32pynsu85jle3LtofFUjE6Th9j3PNcoAmJ+dSvsbo+ctFNtS/y9zGHcNaVjoyAdUoUSvZ0JRQt7z3VlqbbMUVtxJ0lXaKoUYqoyJ4WniPidJS/qF2CkJKMM2YxJowhz3oeJDL2uikODOyIV8bnGldK6beBPwYgIhb4DvAfUHq1V6Ozfgu4cT31ea8jE3amyYOVWavleo1zoq1JIYLAECIxDbiu5/l2S8itUOoMTE44WFJOmaeNcs2XPkTvPcu1Sq4ulguatiWlOI60+MpTNTWIJlhKN33pLCkhZclsxsxOtVzULNdL9aZ1izEOMYGEtmupIEQhBTWj5aQYR2bisg4Tkz3pEa+Flw0L/xTwf1JK/++16azfID6rI+OzX8iVsKek2+u25dHjU3a7hueXSsgZU6ILgdT3JBH8xSVV1+OsjqCIQQk9s0+63G54vttm+gEN76qmZrFcYqxhuVzSNm2mOtsSwkDd1CyWC6yz7IaO8FyTGqOQeVBatLkAhHWW0/aEDz48zcbjQAwmCpGBkAQnRhuFrcvp/mxcQ1C6xqRax0lU7tYejeu18bLG9XPAv8y3X5jOGvhrAF/72tdeZR9vF5o/UCNzFjvYac5p9BxpFJ0bQtB+xlwjmnMSFr0vyf9XQ0bB5YSJznKZqZsjajbQOosN+twY3s68cspZxJKO1xDUZcFyQ0wmH8PMiNgXr9s/YL0e8zzzNrLZuvSIl8MLG1fmLPxZ4JcPn3thOuufeBN01lf2a0xY3NShMT0/H0MpX5w0roGGMLDZbtjtdmw2G7ZZPrUPgZDXVdZ7vAjOV5CNCDEY5xGBPkQianQXz57RdR2V9ywXy9zDJ+Ol1LFiiAwxquyPtTTLJa6u8zR10KJ2ZvkdhoFt0zCEgaZuWCwWOG85OVkiptCz6borxB5rQxa+Szzf7Ig4+m7QUNCgRD3G5OBY58gAwpBycTsLlh/x0ngZz/Vngd9IKf1Bvv8O6KyvR+ktnHdolHXKYX3rpiRHQj3RNtOSbbZble3Jk8RDVK5C5/w4QaxEL4AIJo9siB1IOelx/uyCy4sLlssldd1gnFPK7VyfCikxFFGHbFziHO1yqSWAoAmLGCNEpc8ehoFNUxOGgeVyyYOHD5VJ12fqC2OwrsIYxxAM1vYYIwwDdP2OmLTORabhFuNAChdiNqyQRuM67L884sXxMr9JP88UEsIt01l/Fj5vzXV1oHK/XaqM0M87zXXWKo/d5/qRGUfyrY6j5KRC0SHWhIGy5posIxTK/Be5UJ1Spk1TL9TPL/2gbVKiPIZVVdO2C9q2xTmfDUeL1+1iSd20udteC9AiudtiVmwrx5DYb+2K+XhvPHfCnkmlGy83SRUefgizy3uCF1WWXAI/Dfz12cN/j3dMZ11waDh6PXXLTzNcM4q02WsLwcxms9FwMLMwOVfl1LjNoZaMPISq9hggJJqUcK7KhufUS2WP0A+DhpVR+/pi19MPAREVKjfG5Dpal3W/KhZti7OGR6ennD45pe96PvnkD3j69Iy6qTn94EOVix17ARMiAVUQEcASowEcxnqcM6rb3HUgPdvNjs1mh3MJVxllVZOcCCnnzmZfdu3P72tayjWJpPuIF6WzvgRODx77AV8AOuvrxvznt+fGBfvF5fL6IsIwDNr313U9MaVcM5qlqHMHvjGGhFK36UwXWQ5WvZr3FV2vk8KhJEDQMFAFi0PeP93HUqQOQY2jXSww1rFoGxZtS7fb8fTsDOQc6z0PHj6kbVv6vlfBiBhJaYDc3c64dhJM7i1MDJp8GYJ25ne9tkfZChFHkoQxKVtTyiKC45m87sy/mQ/wHuNOd2hcxVXDmmfvrissH3JuVFWlMjxSQ2wIwWR70HDNujxcSMcQSsYwMgylE7Z0nauQwmp9wiKvuXxVkaIOSQKkLMQtuTMiJRVnKF5Mctd83/dgDL6qcb7Ka73Edrfj7Ows729U40DQj9XQ95G+H/K+CZXXeS5rVRdMB0X32abySbnazvU6eE/t8M4b177xXH1uvs1NsJnmrKoqzb45R99Ftn5HCD2YC4aUVO0k88qH59ANagiLbc+u03qSjqQIxnoenX5I3a6o64r1ajmGk8WT9r0280ZUQDUhdF3PWTjXrvntjt1ukUXxHMv1Cc5rUqQPkafnz/jdb/8efbejrjx15bJB+jElH4MlJgFxLBYr6maBr+rc/qSe1ho3Jln2OkXg9Q3rPcadN66Cw3CwYD5uMs42pauvLeuxwgJFqgihHmtIxrjxtnUeY6zqOsSknitEkIC1Kh+U0HR93SQq77HWZ+Zf5bzQTObEE1guyouhE8XWOWzXlZ2cec1JdPzy8pKu2xGGihgrTcVLQJtpLEgFKKWa8xXeeWzhy5glPPQ92Nfg2rt9nfu5ySUdLRLuiXEVz3XTc4fGpvcnozPGYFHDalvNwMXWsFpZYuxZrE442VyivINKrVY/fcqziw1g2Ww7fvfbv6+phZzWLwJzYRh0PKWupn5D5yAlhswxCIzernTBA3TdQD9cjOGh0qzp0zF3CWvm0jKEyHbTqREaNVxrK6pKkyvtcsl6dUrTtCxXy7GkUKanr4R/R/t4bdwb49Lr658vWcOCwxByZM7N2TuVAlpi3QMSkd1uy67fIQjeO5w1NM33+PSH56Rk+P73f8DHH383Zx07htBPHRlJ/7+SiQqLRctyuaDwEaaYqCrPw4cPqOs6e01N0mx2G7a7LUaEZbugriuCoBwZoqGkGIcYTePvQgcIxgyIWOpa8JXWvVarE7705S/TtgvWJw/wvsolA/PC/P37A5JHfB7uhXEBM2+UroR90/MT5gZWJEqLF9OsoOAckBtfU66Leeuw1uB9ja81QZCAzUb1iru+YxgJaDSdbayMpDYh15qMEVUtSSl3WixxTgliRPuIR/q2kqUEwbmYadNCbvZNaI05EUICUZYoFbkrSRyDc0oEWtcN3vn9Bl6RK+fs8BTKjc8ccRPutHF9Vjh4uN0hCkeFbqCGYPPYPglEBvSLZHBWEHFqKNn42nbNh1/6ERbLE773g0/5wQ+fcnlxya7b0fc7HdPPlGmFH16zc4zrl1LIPTk54cd//Bs8fvyIuq5ZLhYI8OmnZ3z344+BpBpaXieg28UCZy0XFxecXzxnGAYkRb0gRFFhb18pMU1dN5ycPODDL32Fpl2wWq+xuSgtM8Pauz5MbBzx0rjTxvW6GLW7io8RsDIvMusksDUGa/YVFptmyePHT2gXymJ7dnbB2dkZu91WEwx5nD+EQdc/TmOvbrdjt93omEnucn/y5JTVyQkRYb1aUmXq67PzCz7++BNCDDhnsVYnpZcLZcnVmpxqJTsRnCk2oan3GME6j69qlqs1j0+f0LYLvF+M6fi59YzGVf4cDey1cOeNa8oApr3rw22uyyBe3XD/bspzXvk/H2yroWGM0DQtbbugyzJC2g0StO/woI9IW5i8UleHmMM6/SZLHgupqkZ5O3yVs5JTHSwmbQ7WonBmc0qJKImQQ9FyfCEErasl3d+rIyfXefT9c3E0rFfHnTeuOQ67NW4qHENZZ33WN0dIWbQtpJApydK0PjOe1eoRbRv44MMf4aOvfZ312RlPn37Ks/NzwhDYbDf0fa+88kNHjImm8bTNkhgCZ+dndN0AWIypcK6mXZzw+MmHOOf45Hs/pG4WmKGEqJAibLcDIqUXcgBUnM/kX4Iy/+h9xzBEYkgYsfi6oapbdOhfylmYH7JGrHJT29Pe2f68Dd573CvjKjg0sCtTyvNERirNpwmZFXmmUfhM8jKnEsiNupVT0YblYs365CGJTPrZZbG8BMaoOJ7SrUUN7awKNVh7ydimZLQv0buKtl3hvfYoWudR5zQRzIShtHOVcZmcxNCpR4bA2EE/tn6JYLLnSslcdVHzu9cUkNPeQ59jWEe7A+6pcb0q5mswvV++aapWiZAF6ooel7Y7VXXDgwePAMPTp+d0fSCE0i1vMSZqUVfIRVuLiBqetR5jPYUsRnJXvXUeETvxYFxThIqphJSTHej2ccwi6pBk8dLZ6yaZfjzU7q7NsB7xejga1wzjuk39GIjNWUWjNaWkqozWzJMeieXyhC9/5SPaxZqPP/6EzaYj5iyhc9rJIbYfPZ4YiyTBuRpftXhfj130xqhggvcVYiwhQogyqbIkFR1PqFzQMOg+iNFCdCIRQ8rrsaijLjI3sDJVPRdJnx3OlXXnjU+94El9lRfdD9x547opgXH4+NWxlOKdbv70J58wXWTmscpW1moIV9c7xFiGEHJSozTIzsb1mbwGMnVYjPS5IuP8WJkZK135V2M2mXY0kcXUZ9m+vUPbNw31VrN2MHlF4/kspBtuvydJkjtvXBOmdqZy/aKMUIL+4s+NMqZIH4rpJYq6fZoRbpZBQTEW76qs4pjYdb12u2eZ14R6PDBZNG+XBxeTerEZFZoaaktdV3ivHey6XivHoZ6PlLCiazW9HbBEIgkjkSi6X0aKoV5zHgTN2pfTd5DEeGtOZ5bmv8+408Z1Xfr9ugbeeRbxJmOb8xemlAgx0Q3qgYooeAmrUuZ3igT9bprcseEqYoTttkMErPWIsdloPMZEtkNgu+tG40IszFhyjfE0TZtHVGpNQuROjhKu6nxWQijj+QkTByT2etso3ZrJ6zsjZUp5OsZyFbnec73H0dwbw502roLJcMjXV2tacy+Wxrjp+m3n800p/2PtrijZwzR6LX3ajGupvWnnbKilF3f6DTik2p7+r7ZgaSdGuRRKtbLfMV2J+aYfmJRyAmQ6znm/4k2ZizHYlKNhvSnceeOap6iNub6mNd+2SAPptnBlLZLvW2PxzualzDQz1g8DIWjvnvMOQZMQ2glRsVyuefjwMSEMWTlSEwtDLuiKGOq6IaWISAf0iBhC0MHGhNC22ty7Wq1ZrU5wTukHuk49XpdrZ8XgkYRLAYv2Giofh3LcD0OYJq27nsH2GLcvxj4d++GNI14Hd964gNFgpoX/Pubea7p9c4gIOuLhrNlbHkQmYQZr7UgaY2bp86ZpWS1X9H3HZrvRIm5UIYWU12Hem0wvoFzuSoOW8kyYjCJ1TaNc8wBd1wNKbT30WXCc4mUhEUiixDrDyNkxybvGEFRlZQiIiZOguExh4Zhn4ei93gTuhXG9CF6mwXc/sXVNJfUNv+/N//yzPfH1/+Lld/LY4vR2IK9MBf0qbybyPeAS+P6tvent4gn389ju43H9WErpg7f5BrdqXAAi8t9TSj9xq296S7ivx3Zfj+tt40hUfMQRbwlH4zriiLeEd2Fc//QdvOdt4b4e2309rreKW19zHXHE+4JjWHjEEW8JR+M64oi3hFs1LhH5MyLy2yLyrayjfCchIj8qIr8mIv9bRH5TRH4hP/5YRP6ziPxOvn70rvf1VSAiVkT+p4j8x3z/D4nIr+fP7V+LCiEe8Tm4NeMSHVr6J6iI3jeBnxeRb97W+79hDMDfSil9E/iTwN/Ix1JE2L8B/Jd8/y7iF4Dfmt3/+8A/TCn9EeBT4K++k726Y7hNz/UngG+llP5vSqkD/hXwF27x/d8YUkrfTSn9Rr79DP0ifhU9nl/Nm/0q8BffzR6+OkTkI+DPAf8s3xfgp4B/mze5k8f1LnCbxvVV4Pdm938/P3anISJfB/448Ou8oAj7Fxz/CPjbTGOUp8DTpAJgcE8+t9vAMaHxGhCRFfDvgF9MKZ3Pn0vpmqGrLzhE5M8Dn6SU/se73pf7gNvsiv8O8KOz+x/lx+4kRMSjhvUvUkr/Pj/8hRFhf0X8JPCzIvIzQAOcAP8YeCgiLnuvO/253SZu03P9N+AbOfNUAT+HipbfOeR1yK8Av5VS+gezp74wIuyvgpTSL6eUPkopfR39fP5rSukvA78G/KW82Z07rneFWzOu/Kv3N4H/hCYA/k1K6Tdv6/3fMH4S+CvAT4nI/8qXn0FF2H9aRH4H+NP5/n3A3wF+SUS+ha7BfuUd78+dwLH96Ygj3hKOCY0jjnhLOBrXEUe8JRyN64gj3hKOxnXEEW8JR+M64oi3hKNxHXHEW8LRuI444i3h/wMpAZZ1WnzZsQAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" } }, { "output_type": "stream", "name": "stdout", "text": [ "[3] (0.4208575487136841) id: 2052 tags: {'gender': 'Men', 'masterCategory': 'Apparel', 'subCategory': 'Topwear', 'articleType': 'Tshirts', 'baseColour': 'Brown', 'season': 'Winter', 'year': 2010, 'usage': 'Casual', 'productDisplayName': \"Lee Men's Trademark T-shirt\"}\n" ] }, { "output_type": "display_data", "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" } }, { "output_type": "stream", "name": "stdout", "text": [ "[4] (0.4633525609970093) id: 20258 tags: {'gender': 'Men', 'masterCategory': 'Apparel', 'subCategory': 'Topwear', 'articleType': 'Tshirts', 'baseColour': 'Brown', 'season': 'Fall', 'year': 2011, 'usage': 'Casual', 'productDisplayName': 'Wrangler Men Polo Charcoal T-shirt'}\n" ] }, { "output_type": "display_data", "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" } } ] }, { "cell_type": "markdown", "source": [ "# Using AnnLite executor within Jina Flow\n", "\n", "In a jina flow, we can use the executor of [AnnLiteIndexer](https://hub.jina.ai/executor/pn1qofsj), which uses `AnnLite` for indexing documents. \n" ], "metadata": { "id": "alDzKQMyFj3Y" } }, { "cell_type": "code", "source": [ "from jina import Flow\n", "\n", "f = Flow().add(\n", " uses='jinahub://PQLiteIndexer/latest',\n", " uses_with={\n", " 'dim': 1000,\n", " 'metric': 'cosine',\n", " 'columns': [\n", " ('year', 'int'), \n", " ('baseColour', 'str'), \n", " ('masterCategory', 'str')\n", " ],\n", " 'include_metadata': True\n", " },\n", " uses_metas={'workspace': './workspace'}, \n", " install_requirements=True\n", ")\n", "\n", "f.plot()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 162 }, "id": "HKyVAQ3eH2ZH", "outputId": "d433a995-28cc-483b-ce7a-1e57d2d7aa27" }, "execution_count": null, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " Flow@2018[I]:flow visualization: https://mermaid.ink/svg/ICAgICAgICAgICAgJSV7aW5pdDp7ICAidGhlbWUiOiAiYmFzZSIsICAidGhlbWVWYXJpYWJsZXMiOiB7ICAgICAgInByaW1hcnlDb2xvciI6ICIjZmZmIiwgICAgICAicHJpbWFyeUJvcmRlckNvbG9yIjogIiNmZmYiLCAgICAgICJtYWluQmtnIjogIiMzMkM4Q0QiLCAgICAgICJjbHVzdGVyQmtnIjogIiNFRUVERTc4QyIsICAgICAgInNlY29uZGFyeUJvcmRlckNvbG9yIjogIm5vbmUiLCAgICAgICJ0ZXJ0aWFyeUJvcmRlckNvbG9yIjogIm5vbmUiLCAgICAgICJsaW5lQ29sb3IiOiAiI2E2ZDhkYSIgICAgICB9fX0lJSAgICAgICAgICAgIApmbG93Y2hhcnQgTFI7CnN1YmdyYXBoIGV4ZWN1dG9yMDsKZXhlY3V0b3IwL3BlYS0wW2ppbmFodWI6Ly9QUUxpdGVJbmRleGVyL2xhdGVzdF06OjpQRUE7CmVuZDsKZ2F0ZXdheXN0YXJ0W2dhdGV3YXldOjo6R0FURVdBWSAtLT4gZXhlY3V0b3IwOjo6UE9EOwpleGVjdXRvcjA6OjpQT0QgLS0+IGdhdGV3YXllbmRbZ2F0ZXdheV06OjpHQVRFV0FZOwpjbGFzc0RlZiBJTlNQRUNUIHN0cm9rZTojRjI5QzlGCmNsYXNzRGVmIEpPSU5fSU5TUEVDVCBzdHJva2U6I0YyOUM5RgpjbGFzc0RlZiBHQVRFV0FZIGZpbGw6bm9uZSxjb2xvcjojMDAwLHN0cm9rZTpub25lCmNsYXNzRGVmIElOU1BFQ1RfQVVYX1BBU1Mgc3Ryb2tlLWRhc2hhcnJheTogMiAyCmNsYXNzRGVmIEhFQURUQUlMIGZpbGw6IzMyQzhDRDFECgpjbGFzc0RlZiBFWFRFUk5BTCBmaWxsOiNmZmYsc3Ryb2tlOiMzMkM4Q0Q=\u001b[0m\n" ] }, { "output_type": "display_data", "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {} } ] }, { "cell_type": "code", "source": [ "# clear the workspace folder\n", "!rm -rf workspace/*\n", "\n", "with f:\n", " f.index(inputs=docs, show_progress=True)" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 1000 }, "id": "stLXVMxPImS9", "outputId": "40e08128-e6a1-48c0-f61c-2bc14b1768a8" }, "execution_count": null, "outputs": [ { "output_type": "display_data", "data": { "text/html": [ "
\n"
            ],
            "text/plain": [
              ""
            ]
          },
          "metadata": {}
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\r                                                                                                    \r\u001b[32m⠋\u001b[0m 0/2 waiting \u001b[33mexecutor0 gateway\u001b[0m to be ready...\u001b[35m        gateway@2181[D]:setting up sockets...\u001b[0m\n",
            "\u001b[35m        gateway@2181[D]:control over \u001b[33mipc:///tmp/tmp8ow78_ah\u001b[0m\u001b[0m\n",
            "\u001b[35m        gateway@2181[D]:input 0.0.0.0:\u001b[33m39731\u001b[0m\u001b[0m\n",
            "\u001b[35m        gateway@2181[D]:input \u001b[33mtcp://0.0.0.0:39731\u001b[0m (ROUTER_BIND) output \u001b[33mtcp://0.0.0.0:56489\u001b[0m (ROUTER_BIND) control over \u001b[33mipc:///tmp/tmp8ow78_ah\u001b[0m (PAIR_BIND)\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]:ready and listening\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:setting up sockets...\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:control over \u001b[33mtcp://0.0.0.0:43519\u001b[0m\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:input 0.0.0.0:\u001b[33m36279\u001b[0m\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:input \u001b[33mtcp://0.0.0.0:36279\u001b[0m (ROUTER_BIND) output \u001b[33mtcp://0.0.0.0:37367\u001b[0m (ROUTER_BIND) control over \u001b[33mtcp://0.0.0.0:43519\u001b[0m (PAIR_BIND)\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:recv ControlRequest (STATUS)  from \u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:skip executor: not data request\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]:ready and listening\u001b[0m\n",
            "           Flow@2018[I]:\u001b[32m🎉 Flow is ready to use!\u001b[0m\n",
            "\t🔗 Protocol: \t\t\u001b[1mGRPC\u001b[0m\n",
            "\t🏠 Local access:\t\u001b[4m\u001b[36m0.0.0.0:46359\u001b[0m\n",
            "\t🔒 Private network:\t\u001b[4m\u001b[36m172.28.0.2:46359\u001b[0m\n",
            "\t🌐 Public address:\t\u001b[4m\u001b[36m34.125.207.64:46359\u001b[0m\u001b[0m\n",
            "\u001b[35m           Flow@2018[D]:2 Pods (i.e. 2 Peas) are running in this Flow\u001b[0m\n",
            "\u001b[35m     GRPCClient@2018[D]:connected to 0.0.0.0:46359\u001b[0m\n",
            "\u001b[32m⠙\u001b[0m Working... \u001b[32m━━━━\u001b[0m\u001b[32m╸\u001b[0m\u001b[2m\u001b[32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[36m0:00:00\u001b[0m estimating... \u001b[35m        gateway@2181[D]:output 0.0.0.0:\u001b[33m36279\u001b[0m\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:recv DataRequest (/index) - (38d2d3cbbae9459792eb981f83dd58a5)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[32m⠸\u001b[0m Working... \u001b[32m━━━━\u001b[0m\u001b[32m╸\u001b[0m\u001b[2m\u001b[32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[36m0:00:00\u001b[0m estimating... "
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:54.225 | DEBUG    | pqlite.container:insert:227 - => 100 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[35m      executor0@2180[D]:output 0.0.0.0:\u001b[33m39731\u001b[0m\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:recv DataRequest (/index) - (8cba8c91bd3e4ec8a1ddbd5f063862b4)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[32m⠴\u001b[0m Working... \u001b[32m━━━━━━━━\u001b[0m\u001b[32m╸\u001b[0m\u001b[2m\u001b[32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[36m0:00:00\u001b[0m 10% ETA: 4 seconds "
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:54.438 | DEBUG    | pqlite.container:insert:227 - => 100 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[35m      executor0@2180[D]:recv DataRequest (/index) - (aa7999aaa6af4027a10419f50ac5cb1e)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[32m⠦\u001b[0m Working... \u001b[32m━━━━━━━━\u001b[0m\u001b[32m╸\u001b[0m\u001b[2m\u001b[32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[36m0:00:00\u001b[0m 10% ETA: 4 seconds "
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:54.535 | DEBUG    | pqlite.container:insert:227 - => 100 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[35m      executor0@2180[D]:recv DataRequest (/index) - (4bbd37454baf4fe881c2fb9597f7a080)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[32m⠧\u001b[0m Working... \u001b[32m━━━━━━━━━━━━\u001b[0m\u001b[32m╸\u001b[0m\u001b[2m\u001b[32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[36m0:00:00\u001b[0m 10% ETA: 6 seconds "
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:54.652 | DEBUG    | pqlite.container:insert:227 - => 100 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[35m      executor0@2180[D]:recv DataRequest (/index) - (c37bff10ea61478f852a00bc1e215f2d)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:54.742 | DEBUG    | pqlite.container:insert:227 - => 100 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\r                                                                                                    \r\u001b[32m⠇\u001b[0m Working... \u001b[32m━━━━━━━━━━━━━━━━\u001b[0m\u001b[32m╸\u001b[0m\u001b[2m\u001b[32m━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[36m0:00:00\u001b[0m 20% ETA: 3 seconds \u001b[35m      executor0@2180[D]:recv DataRequest (/index) - (3cea32c392c64ce9990d0d7b5a990a22)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:54.841 | DEBUG    | pqlite.container:insert:227 - => 100 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\r                                                                                                    \r\u001b[32m⠏\u001b[0m Working... \u001b[32m━━━━━━━━━━━━━━━━\u001b[0m\u001b[32m╸\u001b[0m\u001b[2m\u001b[32m━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[36m0:00:00\u001b[0m 20% ETA: 3 seconds \u001b[35m      executor0@2180[D]:recv DataRequest (/index) - (33d50be993214f6d938f4e37ea40a1b3)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:54.958 | DEBUG    | pqlite.container:insert:227 - => 100 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[35m      executor0@2180[D]:recv DataRequest (/index) - (855c73a74fe64d5987245f1bbbbfd70d)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[32m⠋\u001b[0m Working... \u001b[32m━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[32m╸\u001b[0m\u001b[2m\u001b[32m━━━━━━━━━━━━━━━━\u001b[0m \u001b[36m0:00:01\u001b[0m 40% ETA: 1 second "
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:55.049 | DEBUG    | pqlite.container:insert:227 - => 100 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[35m      executor0@2180[D]:recv DataRequest (/index) - (2061b4bdaee043bfb278cbabfcf11628)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[32m⠙\u001b[0m Working... \u001b[32m━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[32m╸\u001b[0m\u001b[2m\u001b[32m━━━━━━━━━━━━━━━━\u001b[0m \u001b[36m0:00:01\u001b[0m 40% ETA: 1 second "
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:55.181 | DEBUG    | pqlite.container:insert:227 - => 100 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[35m      executor0@2180[D]:recv DataRequest (/index) - (dcb0cd00293843908f66bedfe0a3c7c2)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[32m⠹\u001b[0m Working... \u001b[32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[32m╸\u001b[0m\u001b[2m\u001b[32m━━━━\u001b[0m \u001b[36m0:00:01\u001b[0m 70% ETA: 0 seconds "
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:55.270 | DEBUG    | pqlite.container:insert:227 - => 100 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[32m⠼\u001b[0m       DONE \u001b[33m━━━━\u001b[0m\u001b[33m╸\u001b[0m\u001b[2m\u001b[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[36m0:00:01\u001b[0m 100% ETA: 0 seconds \u001b[K44 steps done in 1 second\n",
            "\u001b[35m        gateway@2018[D]:waiting for ready or shutdown signal from runtime\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Cancel runtime\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Wait to shutdown\u001b[0m\n",
            "\u001b[35m        gateway@2181[D]:#sent: 10 #recv: 10 sent_size: 59.4 MB recv_size: 59.4 MB\u001b[0m\n",
            "\u001b[35m        gateway@2181[D]:Received message is empty.\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Joining the process\u001b[0m\n",
            "\u001b[35m        gateway@2181[D]: Process terminated\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Successfully joined the process\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]:terminated\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Joining the process\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Successfully joined the process\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]:waiting for ready or shutdown signal from runtime\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Cancel runtime\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]:Sending TERMINATE command for the 1th time\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:recv ControlRequest (TERMINATE)  from \u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:Handled #0 during flush of socket\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:Handled #0 during flush of socket\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:Handled #1 during flush of socket\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:Handled #0 during flush of socket\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]:#sent: 12 #recv: 12 sent_size: 59.4 MB recv_size: 59.4 MB\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Wait to shutdown\u001b[0m\n",
            "\u001b[35m      executor0@2180[D]: Process terminated\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Joining the process\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Successfully joined the process\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]:terminated\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Joining the process\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Successfully joined the process\u001b[0m\n",
            "\u001b[35m           Flow@2018[D]:Flow is closed!\u001b[0m\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "with f:\n",
        "    resp = f.search(inputs=query, \n",
        "                    return_results=True, \n",
        "                    parameters={\n",
        "                        'filter': {\n",
        "                            'year': {'$lte': before_year},\n",
        "                            'masterCategory': {'$eq': category},\n",
        "                            'baseColour': {'$eq': color}\n",
        "                        },\n",
        "                        'limit': 5\n",
        "                    })\n",
        "\n",
        "for m in resp[0].docs[0].matches:\n",
        "    m.set_image_blob_channel_axis(0, -1).set_image_blob_inv_normalization()\n",
        "\n",
        "resp[0].docs[0].matches.plot_image_sprites()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "r16T6XolI6-W",
        "outputId": "bc972718-71e5-4d9c-a6dd-79dc5ce331de"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\r                                                                                                    \r\u001b[32m⠋\u001b[0m 0/2 waiting \u001b[33mexecutor0 gateway\u001b[0m to be ready...\u001b[35m        gateway@2321[D]:setting up sockets...\u001b[0m\n",
            "\u001b[35m        gateway@2321[D]:control over \u001b[33mipc:///tmp/tmpm6f05h3z\u001b[0m\u001b[0m\n",
            "\u001b[35m        gateway@2321[D]:input 0.0.0.0:\u001b[33m49287\u001b[0m\u001b[0m\n",
            "\u001b[35m        gateway@2321[D]:input \u001b[33mtcp://0.0.0.0:49287\u001b[0m (ROUTER_BIND) output \u001b[33mtcp://0.0.0.0:39547\u001b[0m (ROUTER_BIND) control over \u001b[33mipc:///tmp/tmpm6f05h3z\u001b[0m (PAIR_BIND)\u001b[0m\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:06:59.485 | INFO     | pqlite.index:_rebuild_index:364 - Rebuild the index of cell-0 (1000 docs)...\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[35m        gateway@2018[D]:ready and listening\u001b[0m\n",
            "\u001b[32m⠇\u001b[0m 1/2 waiting \u001b[33mexecutor0\u001b[0m to be ready..."
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2021-12-16 03:07:00.323 | DEBUG    | pqlite.container:insert:227 - => 1000 new docs added\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[35m      executor0@2320[D]:setting up sockets...\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:control over \u001b[33mtcp://0.0.0.0:43519\u001b[0m\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:input 0.0.0.0:\u001b[33m36279\u001b[0m\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:input \u001b[33mtcp://0.0.0.0:36279\u001b[0m (ROUTER_BIND) output \u001b[33mtcp://0.0.0.0:37367\u001b[0m (ROUTER_BIND) control over \u001b[33mtcp://0.0.0.0:43519\u001b[0m (PAIR_BIND)\u001b[0m\n",
            "\u001b[32m⠏\u001b[0m 1/2 waiting \u001b[33mexecutor0\u001b[0m to be ready...\u001b[35m      executor0@2320[D]:recv ControlRequest (STATUS)  from \u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:skip executor: not data request\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]:ready and listening\u001b[0m\n",
            "           Flow@2018[I]:\u001b[32m🎉 Flow is ready to use!\u001b[0m\n",
            "\t🔗 Protocol: \t\t\u001b[1mGRPC\u001b[0m\n",
            "\t🏠 Local access:\t\u001b[4m\u001b[36m0.0.0.0:55429\u001b[0m\n",
            "\t🔒 Private network:\t\u001b[4m\u001b[36m172.28.0.2:55429\u001b[0m\n",
            "\t🌐 Public address:\t\u001b[4m\u001b[36m34.125.207.64:55429\u001b[0m\u001b[0m\n",
            "\u001b[35m           Flow@2018[D]:2 Pods (i.e. 2 Peas) are running in this Flow\u001b[0m\n",
            "\u001b[35m     GRPCClient@2018[D]:connected to 0.0.0.0:55429\u001b[0m\n",
            "\u001b[35m        gateway@2321[D]:output 0.0.0.0:\u001b[33m36279\u001b[0m\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:recv DataRequest (/search) - (4e23cef8b57b4504b695501df7daf8ba)  from gateway\u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:output 0.0.0.0:\u001b[33m49287\u001b[0m\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]:waiting for ready or shutdown signal from runtime\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Cancel runtime\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Wait to shutdown\u001b[0m\n",
            "\u001b[35m        gateway@2321[D]:#sent: 1 #recv: 1 sent_size: 363.9 KB recv_size: 363.9 KB\u001b[0m\n",
            "\u001b[35m        gateway@2321[D]:Received message is empty.\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Joining the process\u001b[0m\n",
            "\u001b[35m        gateway@2321[D]: Process terminated\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Successfully joined the process\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]:terminated\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Joining the process\u001b[0m\n",
            "\u001b[35m        gateway@2018[D]: Successfully joined the process\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]:waiting for ready or shutdown signal from runtime\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Cancel runtime\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]:Sending TERMINATE command for the 1th time\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:recv ControlRequest (TERMINATE)  from \u001b[32m▸\u001b[0mexecutor0/ZEDRuntime\u001b[32m▸\u001b[0m⚐\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:Handled #0 during flush of socket\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:Handled #0 during flush of socket\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:Handled #1 during flush of socket\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Wait to shutdown\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:Handled #0 during flush of socket\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]:#sent: 3 #recv: 3 sent_size: 366.4 KB recv_size: 364.1 KB\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Joining the process\u001b[0m\n",
            "\u001b[35m      executor0@2320[D]: Process terminated\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Successfully joined the process\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]:terminated\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Joining the process\u001b[0m\n",
            "\u001b[35m      executor0@2018[D]: Successfully joined the process\u001b[0m\n",
            "\u001b[35m           Flow@2018[D]:Flow is closed!\u001b[0m\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "
" ] }, "metadata": {} } ] } ] } ================================================ FILE: pyproject.toml ================================================ [build-system] requires = [ "setuptools>=42", "wheel", "cython", "numpy>=1.10.0", "pybind11>=2.9.0", ] build-backend = "setuptools.build_meta" ================================================ FILE: requirements.txt ================================================ click cython docarray>=0.13.16,<0.30.0 docarray[common]>=0.13.16,<0.30.0 filesplit loguru numpy rocksdict>=0.3.9 scikit-learn ================================================ FILE: scripts/black.sh ================================================ #!/bin/bash pip install black==22.3.0 arrVar=() echo we ignore non-*.py files excluded_files=( docs/conf.py ) for changed_file in $CHANGED_FILES; do if [[ ${changed_file} == *.py ]] && ! [[ " ${excluded_files[@]} " =~ " ${changed_file} " ]]; then echo checking ${changed_file} arrVar+=(${changed_file}) fi done if [ ${#arrVar[@]} -ne 0 ]; then black -S --check "${arrVar[@]}" fi ================================================ FILE: scripts/get-all-test-paths.sh ================================================ #!/usr/bin/env bash set -ex BATCH_SIZE=5 declare -a array1=( "tests/test_*.py" ) declare -a array2=( "tests/docarray/test_*.py" ) declare -a array3=( "tests/executor/test_*.py" ) dest=( "${array1[@]}" "${array2[@]}" "${array3[@]}" ) printf '%s\n' "${dest[@]}" | jq -R . | jq -cs . ================================================ FILE: scripts/get-last-release-note.py ================================================ ## under jina root dir # python scripts/get-last-release-note.py ## result in root/tmp.md with open('CHANGELOG.md') as fp: n = [] for v in fp: if v.startswith('## Release Note'): n.clear() n.append(v) with open('tmp.md', 'w') as fp: fp.writelines(n) ================================================ FILE: scripts/release.sh ================================================ #!/usr/bin/env bash # Requirements # brew install hub # npm install -g git-release-notes # pip install twine wheel set -ex INIT_FILE='annlite/__init__.py' VER_TAG='__version__ = ' RELEASENOTE='./node_modules/.bin/git-release-notes' function escape_slashes { sed 's/\//\\\//g' } function update_ver_line { local OLD_LINE_PATTERN=$1 local NEW_LINE=$2 local FILE=$3 local NEW=$(echo "${NEW_LINE}" | escape_slashes) if [ "$(uname)" == "Darwin" ]; then sed -i '' '/'"${OLD_LINE_PATTERN}"'/s/.*/'"${NEW}"'/' "${FILE}"; else sed -i '/'"${OLD_LINE_PATTERN}"'/s/.*/'"${NEW}"'/' "${FILE}"; fi head -n10 ${FILE} } function clean_build { rm -rf dist rm -rf annlite/*.so rm -rf *.egg-info rm -rf build } function pub_pypi { # publish to pypi twine upload dist/* # twine upload wheelhouse/* clean_build } function git_commit { git config --local user.email "dev-bot@jina.ai" git config --local user.name "Jina Dev Bot" git tag "v$RELEASE_VER" -m "$(cat ./CHANGELOG.tmp)" git add $INIT_FILE ./CHANGELOG.md git commit -m "chore(version): the next version will be $NEXT_VER" -m "build($RELEASE_ACTOR): $RELEASE_REASON" } function make_release_note { ${RELEASENOTE} ${LAST_VER}..HEAD .github/release-template.ejs > ./CHANGELOG.tmp head -n10 ./CHANGELOG.tmp printf '\n%s\n\n%s\n%s\n\n%s\n\n%s\n\n' "$(cat ./CHANGELOG.md)" "" "## Release Note (\`${RELEASE_VER}\`)" "> Release time: $(date +'%Y-%m-%d %H:%M:%S')" "$(cat ./CHANGELOG.tmp)" > ./CHANGELOG.md } BRANCH=$(git rev-parse --abbrev-ref HEAD) if [[ "$BRANCH" != "main" ]]; then printf "You are not at main branch, exit\n"; exit 1; fi LAST_UPDATE=`git show --no-notes --format=format:"%H" $BRANCH | head -n 1` LAST_COMMIT=`git show --no-notes --format=format:"%H" origin/$BRANCH | head -n 1` if [ $LAST_COMMIT != $LAST_UPDATE ]; then printf "Your local $BRANCH is behind the remote master, exit\n" exit 1; fi # release the current version export RELEASE_VER=$(sed -n '/^__version__/p' $INIT_FILE | cut -d \' -f2) LAST_VER=$(git tag -l | sort -V | tail -n1) printf "last version: \e[1;32m$LAST_VER\e[0m\n" if [[ $1 == "final" ]]; then printf "this will be a final release: \e[1;33m$RELEASE_VER\e[0m\n" NEXT_VER=$(echo $RELEASE_VER | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{$NF=sprintf("%0*d", length($NF), ($NF+1)); print}') printf "bump master version to: \e[1;32m$NEXT_VER\e[0m\n" make_release_note pub_pypi VER_TAG_NEXT=$VER_TAG\'${NEXT_VER}\' update_ver_line "$VER_TAG" "$VER_TAG_NEXT" "$INIT_FILE" RELEASE_REASON="$2" RELEASE_ACTOR="$3" git_commit elif [[ $1 == 'rc' ]]; then printf "this will be a release candidate: \e[1;33m$RELEASE_VER\e[0m\n" DOT_RELEASE_VER=$(echo $RELEASE_VER | sed "s/rc/\./") NEXT_VER=$(echo $DOT_RELEASE_VER | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{$NF=sprintf("%0*d", length($NF), ($NF+1)); print}') NEXT_VER=$(echo $NEXT_VER | sed "s/\.\([^.]*\)$/rc\1/") printf "bump master version to: \e[1;32m$NEXT_VER\e[0m, this will be the next version\n" make_release_note pub_pypi RELEASE_REASON="$2" RELEASE_ACTOR="$3" git_commit else # as a prerelease, pypi update only, no back commit etc. COMMITS_SINCE_LAST_VER=$(git rev-list $LAST_VER..HEAD --count) NEXT_VER=$RELEASE_VER".dev"$COMMITS_SINCE_LAST_VER printf "this will be a developmental release: \e[1;33m$NEXT_VER\e[0m\n" pub_pypi fi ================================================ FILE: scripts/update-version.sh ================================================ #!/usr/bin/env bash # Requirements # brew install hub # npm install -g git-release-notes # pip install twine wheel set -ex INIT_FILE='annlite/__init__.py' VER_TAG='__version__ = ' RELEASENOTE='./node_modules/.bin/git-release-notes' function escape_slashes { sed 's/\//\\\//g' } function update_ver_line { local OLD_LINE_PATTERN=$1 local NEW_LINE=$2 local FILE=$3 local NEW=$(echo "${NEW_LINE}" | escape_slashes) if [ "$(uname)" == "Darwin" ]; then sed -i '' '/'"${OLD_LINE_PATTERN}"'/s/.*/'"${NEW}"'/' "${FILE}"; else sed -i '/'"${OLD_LINE_PATTERN}"'/s/.*/'"${NEW}"'/' "${FILE}"; fi head -n10 ${FILE} } BRANCH=$(git rev-parse --abbrev-ref HEAD) if [[ "$BRANCH" != "main" ]]; then printf "You are not at main branch, exit\n"; exit 1; fi LAST_UPDATE=`git show --no-notes --format=format:"%H" $BRANCH | head -n 1` LAST_COMMIT=`git show --no-notes --format=format:"%H" origin/$BRANCH | head -n 1` if [ $LAST_COMMIT != $LAST_UPDATE ]; then printf "Your local $BRANCH is behind the remote master, exit\n" exit 1; fi # update the current version export RELEASE_VER=$(sed -n '/^__version__/p' $INIT_FILE | cut -d \' -f2) LAST_VER=$(git tag -l | sort -V | tail -n1) printf "last version: \e[1;32m$LAST_VER\e[0m\n" if [[ $1 == "final" ]]; then printf "this will be a final release: \e[1;33m$RELEASE_VER\e[0m\n" NEXT_VER=$(echo $RELEASE_VER | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{$NF=sprintf("%0*d", length($NF), ($NF+1)); print}') printf "bump master version to: \e[1;32m$NEXT_VER\e[0m\n" VER_TAG_NEXT=$VER_TAG\'${NEXT_VER}\' update_ver_line "$VER_TAG" "$VER_TAG_NEXT" "$INIT_FILE" elif [[ $1 == 'rc' ]]; then printf "this will be a release candidate: \e[1;33m$RELEASE_VER\e[0m\n" DOT_RELEASE_VER=$(echo $RELEASE_VER | sed "s/rc/\./") NEXT_VER=$(echo $DOT_RELEASE_VER | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{$NF=sprintf("%0*d", length($NF), ($NF+1)); print}') NEXT_VER=$(echo $NEXT_VER | sed "s/\.\([^.]*\)$/rc\1/") printf "bump master version to: \e[1;32m$NEXT_VER\e[0m, this will be the next version\n" VER_TAG_NEXT=$VER_TAG\'${NEXT_VER}\' update_ver_line "$VER_TAG" "$VER_TAG_NEXT" "$INIT_FILE" else # as a prerelease, pypi update only, no back commit etc. COMMITS_SINCE_LAST_VER=$(git rev-list $LAST_VER..HEAD --count) NEXT_VER=$RELEASE_VER".dev"$COMMITS_SINCE_LAST_VER printf "this will be a developmental release: \e[1;33m$NEXT_VER\e[0m\n" VER_TAG_NEXT=$VER_TAG\'${NEXT_VER}\' update_ver_line "$VER_TAG" "$VER_TAG_NEXT" "$INIT_FILE" fi ================================================ FILE: setup.py ================================================ import os import platform import sys from distutils.sysconfig import get_python_inc import numpy as np import pybind11 import setuptools from Cython.Build import cythonize from setuptools import Extension, find_packages, setup from setuptools.command.build_ext import build_ext if sys.version_info < (3, 7, 0): raise OSError(f'annlite requires Python 3.7+, but yours is {sys.version}') include_dirs = [pybind11.get_include(), np.get_include()] libraries = [] extra_objects = [] try: pkg_name = 'annlite' libinfo_py = os.path.join(pkg_name, '__init__.py') libinfo_content = open(libinfo_py, 'r', encoding='utf8').readlines() version_line = [l.strip() for l in libinfo_content if l.startswith('__version__')][ 0 ] exec(version_line) # produce __version__ except FileNotFoundError: __version__ = '0.0.0' try: with open('README.md', encoding='utf8') as fp: _long_description = fp.read() except FileNotFoundError: _long_description = '' try: with open('requirements.txt') as f: base_deps = f.read().splitlines() # remove blank lines and comments base_deps = [ x.strip() for x in base_deps if ((x.strip()[0] != '#') and (len(x.strip()) > 3) and '-e git://' not in x) ] except FileNotFoundError: base_deps = [] COMPILER_DIRECTIVES = { 'language_level': -3, 'embedsignature': True, 'annotation_typing': False, } ext_modules = [ Extension( 'annlite.hnsw_bind', ['./bindings/hnsw_bindings.cpp'], include_dirs=include_dirs + ['./include/hnswlib'], libraries=libraries, language='c++', extra_objects=extra_objects, ) ] + cythonize( [ Extension( 'annlite.pq_bind', ['./bindings/pq_bindings.pyx'], include_dirs=include_dirs + [get_python_inc(plat_specific=True)], libraries=libraries, language='c++', extra_objects=extra_objects, ), ], compiler_directives=COMPILER_DIRECTIVES, ) # # ext_modules = cythonize([ # Extension( # 'pqlite.pq_bind', # ['./bindings/pq_bindings.pyx'], # include_dirs=include_dirs + [get_python_inc(plat_specific=True)], # libraries=libraries, # language='c++', # extra_objects=extra_objects, # ), # ], compiler_directives=COMPILER_DIRECTIVES) # As of Python 3.6, CCompiler has a `has_flag` method. # cf http://bugs.python.org/issue26689 def has_flag(compiler, flagname): """Return a boolean indicating whether a flag name is supported on the specified compiler. """ import tempfile with tempfile.NamedTemporaryFile('w', suffix='.cpp') as f: f.write('int main (int argc, char **argv) { return 0; }') try: compiler.compile([f.name], extra_postargs=[flagname]) except setuptools.distutils.errors.CompileError: return False return True def cpp_flag(compiler): """Return the -std=c++[11/14] compiler flag. The c++14 is prefered over c++11 (when it is available). """ if has_flag(compiler, '-std=c++14'): return '-std=c++14' elif has_flag(compiler, '-std=c++11'): return '-std=c++11' else: raise RuntimeError( 'Unsupported compiler -- at least C++11 support ' 'is needed!' ) class BuildExt(build_ext): """A custom build extension for adding compiler-specific options.""" c_opts = { 'msvc': ['/EHsc', '/openmp', '/O2'], 'unix': ['-O3', '-march=native'], # , '-w' } link_opts = { 'unix': [], 'msvc': [], } if sys.platform == 'darwin': if platform.processor() in ('arm64', 'arm'): c_opts['unix'].remove('-march=native') # thanks to @https://github.com/drkeoni # https://github.com/nmslib/nmslib/issues/476#issuecomment-876094529 c_opts['unix'].append('-mcpu=apple-a14') c_opts['unix'] += ['-stdlib=libc++', '-mmacosx-version-min=10.7'] link_opts['unix'] += ['-stdlib=libc++', '-mmacosx-version-min=10.7'] else: c_opts['unix'].append('-fopenmp') link_opts['unix'].extend(['-fopenmp', '-pthread']) def build_extensions(self): ct = self.compiler.compiler_type opts = self.c_opts.get(ct, []) if ct == 'unix': opts.append('-DVERSION_INFO="%s"' % self.distribution.get_version()) opts.append(cpp_flag(self.compiler)) # if has_flag(self.compiler, '-fvisibility=hidden'): # opts.append('-fvisibility=hidden') elif ct == 'msvc': opts.append('/DVERSION_INFO=\\"%s\\"' % self.distribution.get_version()) for ext in self.extensions: ext.extra_compile_args.extend(opts) ext.extra_link_args.extend(self.link_opts.get(ct, [])) build_ext.build_extensions(self) extras = {} extras['test'] = [ 'pytest', 'black', 'pytest-timeout', 'pytest-mock', 'pytest-cov', 'pytest-repeat', 'pytest-reraise', 'pytest-custom_exit_code', 'jina', ] # for e in ext_modules: # e.cython_directives = COMPILER_DIRECTIVES setup( name='annlite', version=__version__, description='Fast and Light Approximate Nearest Neighbor Search Library integrated with the Jina Ecosystem', long_description=_long_description, long_description_content_type='text/markdown', author='Jina AI', author_email='team@jina.ai', url='https://github.com/jina-ai/annlite', download_url='https://github.com/jina-ai/pqlite/tags', license='Apache License 2.0', extras_require=extras, ext_modules=ext_modules, cmdclass={'build_ext': BuildExt}, package_data={'bindings': ['*.pyx', '*.pxd', '*.pxi']}, install_requires=base_deps, setup_requires=['setuptools>=18.0', 'wheel', 'cython', 'build'], classifiers=[ 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Topic :: Scientific/Engineering :: Artificial Intelligence', ], python_requires='>=3.7', packages=find_packages( exclude=[ '*.tests', '*.tests.*', 'tests.*', 'tests', 'test', 'docs', 'src', 'executor', ] ), zip_safe=False, keywords='product-quantization approximate-nearest-neighbor hnsw', ) ================================================ FILE: tests/__init__.py ================================================ ================================================ FILE: tests/conftest.py ================================================ import tempfile import numpy as np import pytest from docarray import Document, DocumentArray @pytest.fixture(scope='session') def docs(): return DocumentArray( [ Document(id='doc1', embedding=np.array([1, 0, 0, 0])), Document(id='doc2', embedding=np.array([0, 1, 0, 0])), Document(id='doc3', embedding=np.array([0, 0, 1, 0])), Document(id='doc4', embedding=np.array([0, 0, 0, 1])), Document(id='doc5', embedding=np.array([1, 0, 1, 0])), Document(id='doc6', embedding=np.array([0, 1, 0, 1])), ] ) @pytest.fixture(scope='session') def update_docs(): return DocumentArray( [ Document(id='doc1', embedding=np.array([0, 0, 0, 1])), ] ) @pytest.fixture(autouse=True) def tmpfile(tmpdir): tmpfile = f'annlite_test_{next(tempfile._get_candidate_names())}.db' return tmpdir / tmpfile @pytest.fixture(autouse=True) def test_disable_telemetry(monkeypatch): monkeypatch.setenv('JINA_OPTOUT_TELEMETRY', 'True') ================================================ FILE: tests/docarray/__init__.py ================================================ ================================================ FILE: tests/docarray/test_add.py ================================================ import pytest from docarray import DocumentArray def test_add(docs): annlite_doc = DocumentArray( storage='annlite', config={ 'n_dim': 4, }, ) annlite_doc.extend(docs) assert len(annlite_doc) == len(annlite_doc[:, 'embedding']) assert len(annlite_doc[:, 'embedding']) == 6 def test_add_conflict_id(docs, update_docs): annlite_doc = DocumentArray( storage='annlite', config={ 'n_dim': 4, }, ) annlite_doc.extend(docs) from sqlite3 import IntegrityError with pytest.raises(IntegrityError): annlite_doc.extend(update_docs) ================================================ FILE: tests/docarray/test_del.py ================================================ import pytest from docarray import Document, DocumentArray @pytest.mark.parametrize('deleted_elmnts', [[0, 1], ['r0', 'r1']]) def test_delete_success(deleted_elmnts): annlite_doc = DocumentArray( storage='annlite', config={ 'n_dim': 3, }, ) with annlite_doc: annlite_doc.extend( [ Document(id='r0', embedding=[0, 0, 0]), Document(id='r1', embedding=[1, 1, 1]), Document(id='r2', embedding=[2, 2, 2]), Document(id='r3', embedding=[3, 3, 3]), Document(id='r4', embedding=[4, 4, 4]), Document(id='r5', embedding=[5, 5, 5]), Document(id='r6', embedding=[6, 6, 6]), Document(id='r7', embedding=[7, 7, 7]), ] ) expected_ids_after_del = ['r2', 'r3', 'r4', 'r5', 'r6', 'r7'] with annlite_doc: del annlite_doc[deleted_elmnts] assert len(annlite_doc._offset2ids.ids) == 6 assert len(annlite_doc[:, 'embedding']) == 6 for id in expected_ids_after_del: assert id == annlite_doc[id].id @pytest.mark.parametrize('expected_failed_deleted_elmnts', [['r2', 'r3']]) def test_delete_not_found(expected_failed_deleted_elmnts): annlite_doc = DocumentArray( storage='annlite', config={ 'n_dim': 3, }, ) with annlite_doc: annlite_doc.extend( [ Document(id='r0', embedding=[0, 0, 0]), Document(id='r1', embedding=[1, 1, 1]), ] ) with pytest.raises(ValueError): with annlite_doc: for deleted_elmnts in expected_failed_deleted_elmnts: del annlite_doc[deleted_elmnts] ================================================ FILE: tests/docarray/test_find.py ================================================ import numpy as np from docarray import Document, DocumentArray def test_find(): nrof_docs = 1000 num_candidates = 100 annlite_doc = DocumentArray( storage='annlite', config={ 'n_dim': 3, }, ) with annlite_doc: annlite_doc.extend( [ Document(id=f'r{i}', embedding=np.ones((3,)) * i) for i in range(nrof_docs) ], ) np_query = np.array([2, 1, 3]) annlite_doc.find(np_query, limit=10, num_candidates=num_candidates) ================================================ FILE: tests/docarray/test_get.py ================================================ import numpy as np import pytest from docarray import Document, DocumentArray @pytest.mark.parametrize('nrof_docs', [10, 100, 10_000, 10_100, 20_000, 20_100]) def test_success_get_bulk_data(nrof_docs): annlite_doc = DocumentArray( storage='annlite', config={ 'n_dim': 3, }, ) with annlite_doc: annlite_doc.extend( [ Document(id=f'r{i}', embedding=np.ones((3,)) * i) for i in range(nrof_docs) ] ) assert len(annlite_doc[:, 'id']) == nrof_docs def test_error_get_bulk_data_id_not_exist(): nrof_docs = 10 annlite_doc = DocumentArray( storage='annlite', config={ 'n_dim': 3, }, ) with annlite_doc: annlite_doc.extend( [ Document(id=f'r{i}', embedding=np.ones((3,)) * i) for i in range(nrof_docs) ] ) with pytest.raises(KeyError) as e: annlite_doc[['r1', 'r11', 'r21'], 'id'] ================================================ FILE: tests/docarray/test_save_load.py ================================================ import numpy as np import pytest from docarray import Document, DocumentArray def test_save_load(tmpfile): N = 100 save_da = DocumentArray( storage='annlite', config={'n_dim': 768, 'data_path': tmpfile} ) for i in range(N): save_da.append(Document(id=str(i), embedding=np.random.rand(768))) # need release the resource save_da._annlite.close() load_da = DocumentArray( storage='annlite', config={'n_dim': 768, 'data_path': tmpfile} ) assert len(load_da) == N for i in range(N, N + N): load_da.append(Document(id=str(i), embedding=np.random.rand(768))) assert len(load_da) == N + N ================================================ FILE: tests/executor/__init__.py ================================================ ================================================ FILE: tests/executor/test_executor.py ================================================ import os import time import uuid from unittest.mock import patch import hubble import numpy as np import pytest from jina import Document, DocumentArray, Executor, Flow from annlite.executor import AnnLiteIndexer N = 1000 # number of data points Nt = 2000 Nu = 999 # number of data update Nq = 10 D = 128 # dimentionality / number of features token = 'ed17d158d95d3f53f60eed445d783c80' def gen_docs(num): res = DocumentArray() k = np.random.random((num, D)).astype(np.float32) for i in range(num): doc = Document(id=f'{i}', embedding=k[i]) res.append(doc) return res def docs_with_tags(N): prices = [10.0, 25.0, 50.0, 100.0] categories = ['comics', 'movies', 'audiobook'] X = np.random.random((N, D)).astype(np.float32) docs = [ Document( id=f'{i}', embedding=X[i], tags={ 'price': np.random.choice(prices), 'category': np.random.choice(categories), }, ) for i in range(N) ] da = DocumentArray(docs) return da def delete_artifact(tmpname): client = hubble.Client(token=token, max_retries=None, jsonify=True) art_list = client.list_artifacts(filter={'metaData.name': tmpname}) for art in art_list['data']: client.delete_artifact(id=art['_id']) def test_index(tmpfile): docs = gen_docs(N) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, }, workspace=tmpfile, ) with f: result = f.post(on='/index', inputs=docs, return_results=True) assert len(result) == N def test_update(tmpfile): docs = gen_docs(N) docs_update = gen_docs(Nu) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, }, workspace=tmpfile, ) with f: f.post(on='/index', inputs=docs) time.sleep(2) update_res = f.post(on='/update', inputs=docs_update, return_results=True) assert len(update_res) == Nu status = f.post(on='/status', return_results=True)[0] assert int(status.tags['total_docs']) == N assert int(status.tags['index_size']) == N def test_search(tmpfile): docs = gen_docs(N) docs_query = gen_docs(Nq) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, }, workspace=tmpfile, ) with f: f.post(on='/index', inputs=docs) time.sleep(2) query_res = f.post(on='/search', inputs=docs_query, return_results=True) assert len(query_res) == Nq for i in range(len(query_res[0].matches) - 1): assert ( query_res[0].matches[i].scores['cosine'].value <= query_res[0].matches[i + 1].scores['cosine'].value ) @pytest.mark.parametrize( 'columns', [[('price', 'float'), ('category', 'str')], {'price': 'float', 'category': 'str'}], ) def test_search_with_filtering(tmpfile, columns): docs = docs_with_tags(N) docs_query = gen_docs(1) f = Flow().add( uses=AnnLiteIndexer, uses_with={'dim': D, 'columns': columns}, workspace=tmpfile ) with f: f.post(on='/index', inputs=docs) time.sleep(2) query_res = f.post( on='/search', inputs=docs_query, return_results=True, parameters={'filter': {'price': {'$lt': 50.0}}, 'include_metadata': True}, ) assert all([m.tags['price'] < 50 for m in query_res[0].matches]) def test_delete(tmpfile): docs = gen_docs(N) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'dim': D, }, workspace=tmpfile, ) with f: f.post(on='/index', inputs=docs) time.sleep(2) status = f.post(on='/status', return_results=True)[0] assert int(status.tags['total_docs']) == N assert int(status.tags['index_size']) == N f.post(on='/delete', parameters={'ids': [d.id for d in docs[:5]]}) status = f.post(on='/status', return_results=True)[0] assert int(status.tags['total_docs']) == N - 5 assert int(status.tags['index_size']) == N - 5 docs_query = gen_docs(Nq) query_res = f.post(on='/search', inputs=docs_query, return_results=True) def test_status(tmpfile): docs = gen_docs(N) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'dim': D, }, workspace=tmpfile, ) with f: f.post(on='/index', inputs=docs) time.sleep(2) status = f.post(on='/status', return_results=True)[0] assert int(status.tags['total_docs']) == N assert int(status.tags['index_size']) == N def test_clear(tmpfile): docs = gen_docs(N) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'dim': D, }, workspace=tmpfile, ) with f: f.post(on='/index', inputs=docs) f.post(on='/clear') status = f.post(on='/status', return_results=True)[0] assert int(status.tags['total_docs']) == 0 assert int(status.tags['index_size']) == 0 def test_remote_storage(tmpfile): docs = gen_docs(N) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, }, workspace=tmpfile, shards=1, ) tmpname = uuid.uuid4().hex with f: f.post(on='/index', inputs=docs) time.sleep(2) f.post(on='/backup', parameters={'target_name': tmpname, 'token': token}) time.sleep(2) f = Flow().add( uses=AnnLiteIndexer, uses_with={'n_dim': D}, workspace=tmpfile, shards=1, ) with f: f.post(on='/restore', parameters={'source_name': tmpname, 'token': token}) status = f.post(on='/status', return_results=True)[0] delete_artifact(tmpname) assert int(status.tags['total_docs']) == N assert int(status.tags['index_size']) == N def test_local_storage(tmpfile): docs = gen_docs(N) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, }, workspace=tmpfile, shards=1, ) with f: f.post(on='/index', inputs=docs) time.sleep(2) f.post(on='/backup') time.sleep(2) f = Flow().add( uses=AnnLiteIndexer, uses_with={'n_dim': D}, workspace=tmpfile, shards=1, ) with f: f.post(on='/restore') status = f.post(on='/status', return_results=True)[0] assert int(status.tags['total_docs']) == N assert int(status.tags['index_size']) == N def test_remote_storage_with_shards(tmpfile): docs = gen_docs(N) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, }, workspace=tmpfile, shards=3, polling={ '/index': 'ANY', '/search': 'ALL', '/backup': 'ALL', '/status': 'ALL', 'backup': 'ALL', 'restore': 'ALL', }, ) tmpname = uuid.uuid4().hex with f: f.post(on='/index', inputs=docs) time.sleep(2) f.post( on='/backup', parameters={'target_name': tmpname, 'token': token}, ) time.sleep(2) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, }, workspace=tmpfile, shards=3, polling={ '/index': 'ANY', '/search': 'ALL', '/backup': 'ALL', '/status': 'ALL', 'backup': 'ALL', 'restore': 'ALL', }, ) with f: f.post(on='/restore', parameters={'source_name': tmpname, 'token': token}) status = f.post(on='/status', return_results=True) delete_artifact(tmpname) total_docs = 0 index_size = 0 for stat in status: total_docs += stat.tags['total_docs'] index_size += stat.tags['index_size'] assert total_docs == N assert index_size == N def test_local_storage_with_shards(tmpfile): docs = gen_docs(N) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, }, workspace=tmpfile, shards=3, polling={ '/index': 'ANY', '/search': 'ALL', '/backup': 'ALL', '/status': 'ALL', 'backup': 'ALL', 'restore': 'ALL', }, ) with f: f.post(on='/index', inputs=docs) time.sleep(2) f.post(on='/backup') time.sleep(2) f = Flow().add( uses=AnnLiteIndexer, uses_with={'n_dim': D}, workspace=tmpfile, shards=3, polling={ '/index': 'ANY', '/search': 'ALL', '/backup': 'ALL', '/status': 'ALL', 'backup': 'ALL', 'restore': 'ALL', }, ) with f: f.post(on='/restore') status = f.post(on='/status', return_results=True) total_docs = 0 index_size = 0 for stat in status: total_docs += stat.tags['total_docs'] index_size += stat.tags['index_size'] assert total_docs == N assert index_size == N def test_local_storage_with_delete(tmpfile): docs = gen_docs(N) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, }, workspace=tmpfile, shards=1, ) with f: f.post(on='/index', inputs=docs) time.sleep(2) f.post(on='/backup') time.sleep(2) f = Flow().add( uses=AnnLiteIndexer, uses_with={'n_dim': D}, workspace=tmpfile, shards=1, ) with f: f.post(on='/restore') f.post(on='/delete', parameters={'ids': ['0']}) status = f.post(on='/status', return_results=True)[0] assert int(status.tags['total_docs']) == N - 1 assert int(status.tags['index_size']) == N - 1 def test_local_storage_delete_update(tmpfile): docs = gen_docs(N) f = Flow().add( uses=AnnLiteIndexer, uses_with={ 'n_dim': D, }, workspace=tmpfile, shards=1, ) with f: f.post(on='/index', inputs=docs) time.sleep(2) f.post(on='/backup') f.post(on='/delete', parameters={'ids': ['0']}) time.sleep(2) f.post(on='/backup') new_doc = gen_docs(N)[1:] f.post(on='/update', inputs=new_doc) status = f.post(on='/status', return_results=True)[0] assert int(status.tags['total_docs']) == N - 1 assert int(status.tags['index_size']) == N - 1 ================================================ FILE: tests/test_codec.py ================================================ import numpy as np import pytest from annlite.core.codec.pq import PQCodec n_examples = 1000 n_features = 128 n_queries = 5 n_cells = 10 n_clusters = 256 n_subvectors = 32 d_subvector = int(n_features / n_subvectors) top_k = 100 @pytest.fixture def build_data(): Xt = np.random.random((n_examples, n_features)).astype(np.float32) return Xt @pytest.fixture def build_pq_codec(build_data): Xt = build_data pq_codec = PQCodec(dim=n_features, n_subvectors=n_subvectors, n_clusters=n_clusters) pq_codec.fit(Xt) return pq_codec def minibatch_generator(Xtr, batch_size): num = 0 pos_begin_batch = 0 n_examples = len(Xtr) while True: Xtr_batch = Xtr[pos_begin_batch : pos_begin_batch + batch_size] yield Xtr_batch num += len(Xtr_batch) pos_begin_batch += batch_size if num + batch_size >= n_examples: break @pytest.fixture def build_pq_codec_online(build_data): Xt = build_data pq_codec_minibatch = PQCodec( dim=n_features, n_subvectors=n_subvectors, n_clusters=n_clusters ) n_epochs = 3 n_batch = 300 for i in range(n_epochs): minibatch_generator_ = minibatch_generator(Xt, n_batch) for batch in minibatch_generator_: pq_codec_minibatch.partial_fit(batch) pq_codec_minibatch.build_codebook() return pq_codec_minibatch def test_partial_and_total_fit_same_codebook_shape( build_pq_codec, build_pq_codec_online ): pq_codec = build_pq_codec pq_codec_minibatch = build_pq_codec_online assert pq_codec.codebooks.shape == pq_codec_minibatch.codebooks.shape ================================================ FILE: tests/test_crud.py ================================================ import random import numpy as np import pytest from docarray import Document, DocumentArray from annlite import AnnLite N = 1000 Nt = 5 D = 128 @pytest.fixture def annlite_with_data(tmpfile): columns = [('x', float)] index = AnnLite(n_dim=D, columns=columns, data_path=tmpfile) X = np.random.random((N, D)).astype(np.float32) docs = DocumentArray( [ Document(id=f'{i}', embedding=X[i], tags={'x': random.random()}) for i in range(N) ] ) index.index(docs) return index @pytest.mark.parametrize('filter', [None, {'x': {'$lt': 0.5}}]) @pytest.mark.parametrize('limit', [-1, 1, 3, 5]) def test_get(annlite_with_data, filter, limit): index = annlite_with_data docs = index.get_docs(filter=filter, limit=limit) if limit > 0: assert len(docs) == limit elif limit == -1 and filter is None: assert len(docs) == N if filter: for doc in docs: assert doc.tags['x'] < 0.5 def test_update_legal(annlite_with_data): index = annlite_with_data updated_X = np.random.random((Nt, D)).astype(np.float32) updated_docs = DocumentArray( [ Document(id=f'{i}', embedding=updated_X[i], tags={'x': random.random()}) for i in range(Nt) ] ) index.update(updated_docs) index.search(updated_docs) for i in range(Nt): np.testing.assert_array_almost_equal( updated_docs[i].embedding, updated_docs[i].matches[0].embedding, decimal=5 ) def test_update_illegal(annlite_with_data): index = annlite_with_data updated_X = np.random.random((Nt, D)).astype(np.float32) updated_docs = DocumentArray( [ Document( id=f'{i}_wrong', embedding=updated_X[i], tags={'x': random.random()} ) for i in range(Nt) ] ) with pytest.raises(Exception): index.update( updated_docs, raise_errors_on_not_found=True, insert_if_not_found=False ) with pytest.warns(RuntimeWarning): index.update( updated_docs, raise_errors_on_not_found=False, insert_if_not_found=False ) index.update(updated_docs, raise_errors_on_not_found=True, insert_if_not_found=True) def test_delete_legal(annlite_with_data): index = annlite_with_data deleted_docs = DocumentArray([Document(id=f'{i}') for i in range(Nt)]) index.delete(deleted_docs) assert index.stat['total_docs'] == N - Nt def test_delete_illegal(annlite_with_data): index = annlite_with_data deleted_docs = DocumentArray([Document(id=f'{i}_wrong') for i in range(Nt)]) with pytest.raises(Exception): index.delete(deleted_docs, raise_errors_on_not_found=True) ================================================ FILE: tests/test_dump.py ================================================ import numpy as np import pytest from docarray import Document, DocumentArray from annlite import AnnLite np.random.seed(123456) D = 50 N = 1000 @pytest.fixture def index_data(): index_data = DocumentArray() for i in range(N): index_data.append(Document(id=str(i))) half_embedding = np.random.random((N, D // 2)) index_data.embeddings = np.concatenate([half_embedding, half_embedding], axis=1) return index_data def test_dump_load(tmpfile, index_data): query = index_data[0:1] index = AnnLite(D, data_path=tmpfile) index.index(index_data) index.search(query, limit=10) gt = [m.id for m in query['@m']] index.dump() index.close() new_index = AnnLite(D, data_path=tmpfile) new_index.search(query, limit=10) new_gt = [m.id for m in query['@m']] assert len(set(gt) & set(new_gt)) / len(gt) == 1.0 new_index.close() new_index = AnnLite(D, n_components=D // 2, data_path=tmpfile) new_index.search(query, limit=10) new_gt = [m.id for m in query['@m']] assert len(set(gt) & set(new_gt)) / len(gt) > 0.6 ================================================ FILE: tests/test_enums.py ================================================ import pytest from annlite.enums import Metric def test_metric(): m = Metric.EUCLIDEAN assert m.name == 'EUCLIDEAN' assert m.value == 1 ================================================ FILE: tests/test_filter.py ================================================ import random import tempfile import numpy as np import pytest from docarray import Document, DocumentArray from annlite import AnnLite from annlite.filter import Filter def test_empty_filter(): f = Filter() where_clause, parameters = f.parse_where_clause() assert where_clause == '' assert parameters == () def test_simple_filter(): f = Filter({'brand': {'$lt': 1}}) where_clause, parameters = f.parse_where_clause() assert where_clause == '(brand < ?)' assert parameters == (1,) def test_logic_operator(): f = Filter({'$and': {'brand': {'$lt': 1}, 'price': {'$gte': 50}}}) where_clause, parameters = f.parse_where_clause() assert where_clause == '(brand < ?) AND (price >= ?)' assert parameters == (1, 50) f = Filter({'brand': {'$lt': 1}, 'price': {'$gte': 50}}) where_clause, parameters = f.parse_where_clause() assert where_clause == '(brand < ?) AND (price >= ?)' assert parameters == (1, 50) f = Filter({'$or': {'brand': {'$lt': 1}, 'price': {'$gte': 50}}}) where_clause, parameters = f.parse_where_clause() assert where_clause == '(brand < ?) OR (price >= ?)' assert parameters == (1, 50) def test_membership_operator(): f = Filter({'$and': {'brand': {'$in': ['Nike', 'Gucci']}, 'price': {'$gte': 50}}}) where_clause, parameters = f.parse_where_clause() assert where_clause == '(brand IN(?, ?)) AND (price >= ?)' assert parameters == ('Nike', 'Gucci', 50) f = Filter({'$or': {'brand': {'$nin': ['Nike', 'Gucci']}, 'price': {'$gte': 50}}}) where_clause, parameters = f.parse_where_clause() assert where_clause == '(brand NOT IN(?, ?)) OR (price >= ?)' assert parameters == ('Nike', 'Gucci', 50) def test_cases(): express = { '$and': { 'price': {'$gte': 0, '$lte': 54}, 'rating': {'$gte': 1}, 'year': {'$gte': 2007, '$lte': 2010}, } } f = Filter(express) where_clause, parameters = f.parse_where_clause() assert ( where_clause == '(price >= ?) AND (price <= ?) AND (rating >= ?) AND (year >= ?) AND (year <= ?)' ) assert parameters == (0, 54, 1, 2007, 2010) express = { '$and': { 'price': {'$or': [{'price': {'$gte': 0}}, {'price': {'$lte': 54}}]}, 'rating': {'$gte': 1}, 'year': {'$gte': 2007, '$lte': 2010}, } } f = Filter(express) where_clause, parameters = f.parse_where_clause() assert ( where_clause == '((price >= ?) OR (price <= ?)) AND (rating >= ?) AND (year >= ?) AND (year <= ?)' ) assert parameters == (0, 54, 1, 2007, 2010) express = { '$and': { '$or': [{'price': {'$gte': 0}}, {'price': {'$lte': 54}}], 'rating': {'$gte': 1}, 'year': {'$gte': 2007, '$lte': 2010}, } } f = Filter(express) where_clause, parameters = f.parse_where_clause() assert ( where_clause == '((price >= ?) OR (price <= ?)) AND (rating >= ?) AND (year >= ?) AND (year <= ?)' ) assert parameters == (0, 54, 1, 2007, 2010) def test_error_filter(): f = Filter({'$may': {'brand': {'$lt': 1}, 'price': {'$gte': 50}}}) with pytest.raises(ValueError): f.parse_where_clause() @pytest.mark.parametrize( 'columns', [[('x', float)], [('x', 'float')], {'x': 'float'}, {'x': float}] ) def test_filter_with_columns(tmpfile, columns): N = 100 D = 2 limit = 3 index = AnnLite(D, columns=columns, data_path=tmpfile, include_metadata=True) X = np.random.random((N, D)).astype(np.float32) docs = DocumentArray( [ Document(id=f'{i}', embedding=X[i], tags={'x': random.random()}) for i in range(N) ] ) index.index(docs) matches = index.filter( filter={'x': {'$lt': 0.5}}, limit=limit, include_metadata=True ) assert len(matches) == limit for m in matches: assert m.tags['x'] < 0.5 @pytest.mark.parametrize('filterable_attrs', [{'x': 'float'}, {'x': float}]) def test_filter_with_dict(tmpfile, filterable_attrs): N = 100 D = 2 limit = 3 index = AnnLite( D, filterable_attrs=filterable_attrs, data_path=tmpfile, include_metadata=True, ) X = np.random.random((N, D)).astype(np.float32) docs = DocumentArray( [ Document(id=f'{i}', embedding=X[i], tags={'x': random.random()}) for i in range(N) ] ) index.index(docs) matches = index.filter( filter={'x': {'$lt': 0.5}}, limit=limit, include_metadata=True ) assert len(matches) == limit for m in matches: assert m.tags['x'] < 0.5 @pytest.mark.parametrize('limit', [1, 5]) @pytest.mark.parametrize('offset', [1, 5]) @pytest.mark.parametrize('order_by', ['x', 'y']) @pytest.mark.parametrize('ascending', [True, False]) def test_filter_with_limit_offset(tmpfile, limit, offset, order_by, ascending): N = 100 D = 2 index = AnnLite( D, filterable_attrs={'x': 'float', 'y': 'float'}, data_path=tmpfile, include_metadata=True, ) X = np.random.random((N, D)).astype(np.float32) docs = DocumentArray( [ Document( id=f'{i}', embedding=X[i], tags={'x': random.random(), 'y': random.random()}, ) for i in range(N) ] ) index.index(docs) matches = index.filter( filter={'x': {'$lt': 0.5}}, limit=limit, offset=offset, order_by=order_by, ascending=ascending, include_metadata=True, ) assert len(matches) == limit for i in range(len(matches) - 1): m = matches[i] assert m.tags['x'] < 0.5 if ascending: assert m.tags[order_by] <= matches[i + 1].tags[order_by] else: assert m.tags[order_by] >= matches[i + 1].tags[order_by] @pytest.mark.parametrize('limit', [1, 5]) def test_filter_with_wrong_columns(tmpfile, limit): N = 100 D = 128 index = AnnLite( D, columns=[('price', float)], data_path=tmpfile, ) X = np.random.random((N, D)).astype(np.float32) docs = DocumentArray( [ Document(id=f'{i}', embedding=X[i], tags={'price': random.random()}) for i in range(N) ] ) index.index(docs) matches = index.filter( filter={'price': {'$lte': 50}}, limit=limit, include_metadata=True, ) assert len(matches) == limit import sqlite3 with pytest.raises(sqlite3.OperationalError): matches = index.filter( filter={'price_': {'$lte': 50}}, include_metadata=True, ) matches = index.filter( filter={'price': {'$lte': 50}}, limit=limit, include_metadata=True, ) assert len(matches) == limit ================================================ FILE: tests/test_hnsw_load_save.py ================================================ import os import numpy as np import pytest from annlite.core.index.hnsw import HnswIndex n_examples = 100 n_features = 10 @pytest.fixture def build_data(): Xt = np.random.random((n_examples, n_features)).astype(np.float32) return Xt @pytest.fixture def build_hnsw(build_data): Xt = build_data hnsw = HnswIndex(dim=n_features) hnsw.add_with_ids(x=Xt, ids=list(range(len(Xt)))) return hnsw def test_save_and_load(tmpdir, build_hnsw): hnsw = build_hnsw hnsw.dump(os.path.join(tmpdir, 'hnsw.pkl')) assert os.path.exists(os.path.join(tmpdir, 'hnsw.pkl')) is True hnsw_ = HnswIndex(dim=n_features, index_file=os.path.join(tmpdir, 'hnsw.pkl')) assert hnsw_.size == hnsw.size def test_loading_from_wrong_path(tmpfile): with pytest.raises(FileNotFoundError): HnswIndex(dim=n_features, index_file=os.path.join(tmpfile, 'hnsw_wrong.pkl')) ================================================ FILE: tests/test_index.py ================================================ import operator import os import random import uuid from unittest.mock import patch import hubble import numpy as np import pytest from docarray import Document, DocumentArray from annlite import AnnLite N = 1000 # number of data points Nq = 5 Nt = 2000 D = 128 # dimensionality / number of features # pq params below ----------- n_clusters = 256 n_subvectors = 8 d_subvector = int(D / n_subvectors) numeric_operators = { '$gte': operator.ge, '$gt': operator.gt, '$lte': operator.le, '$lt': operator.lt, '$eq': operator.eq, '$neq': operator.ne, } categorical_operators = {'$eq': operator.eq, '$neq': operator.ne} token = 'ed17d158d95d3f53f60eed445d783c80' @pytest.fixture def annlite_index(tmpfile): Xt = np.random.random((Nt, D)).astype( np.float32 ) # 2,000 128-dim vectors for training index = AnnLite(n_dim=D, data_path=tmpfile) return index @pytest.fixture def annlite_with_data(tmpfile): columns = [('x', float)] index = AnnLite(n_dim=D, columns=columns, data_path=tmpfile) X = np.random.random((N, D)).astype( np.float32 ) # 10,000 128-dim vectors to be indexed docs = DocumentArray( [ Document(id=f'{i}', embedding=X[i], tags={'x': random.random()}) for i in range(N) ] ) index.index(docs) return index @pytest.fixture def heterogenenous_da(tmpfile): prices = [10.0, 25.0, 50.0, 100.0] categories = ['comics', 'movies', 'audiobook'] columns = [('price', float), ('category', str)] index = AnnLite(n_dim=D, columns=columns, data_path=tmpfile) X = np.random.random((N, D)).astype( np.float32 ) # 10,000 128-dim vectors to be indexed docs = [ Document( id=f'{i}', embedding=X[i], tags={ 'price': np.random.choice(prices), 'category': np.random.choice(categories), }, ) for i in range(N) ] da = DocumentArray(docs) return da @pytest.fixture def annlite_with_heterogeneous_tags(tmpfile, heterogenenous_da): columns = [('price', float), ('category', str)] index = AnnLite(n_dim=D, columns=columns, data_path=tmpfile) index.index(heterogenenous_da) return index def test_index(annlite_index): X = np.random.random((N, D)).astype( np.float32 ) # 10,000 128-dim vectors to be indexed docs = DocumentArray([Document(id=f'{i}', embedding=X[i]) for i in range(N)]) annlite_index.index(docs) @pytest.mark.parametrize('dtype', [np.int64, np.float32, np.float64]) def test_dtype(annlite_index, dtype): X = np.random.random((N, D)).astype(dtype) # 10,000 128-dim vectors to be indexed docs = DocumentArray([Document(id=f'{i}', embedding=X[i]) for i in range(N)]) annlite_index.index(docs) def test_delete(annlite_with_data): annlite_with_data.delete(['0', '1']) def test_update(annlite_with_data): X = np.random.random((5, D)).astype(np.float32) # 5 128-dim vectors to be indexed docs = DocumentArray([Document(id=f'{i}', embedding=X[i]) for i in range(5)]) annlite_with_data.update(docs) def test_query(annlite_with_data): X = np.random.random((Nq, D)).astype(np.float32) # a 128-dim query vector query = DocumentArray([Document(embedding=X[i]) for i in range(5)]) annlite_with_data.search(query) for i in range(len(query[0].matches) - 1): assert ( query[0].matches[i].scores['euclidean'].value <= query[0].matches[i + 1].scores['euclidean'].value ) def test_index_query_with_filtering_sorted_results(annlite_with_data): X = np.random.random((Nq, D)).astype(np.float32) query = DocumentArray([Document(embedding=X[i]) for i in range(5)]) annlite_with_data.search(query, filter={'x': {'$gt': 0.6}}, include_metadata=True) for i in range(len(query[0].matches) - 1): assert ( query[0].matches[i].scores['euclidean'].value <= query[0].matches[i + 1].scores['euclidean'].value ) assert query[0].matches[i].tags['x'] > 0.6 @pytest.mark.parametrize('operator', list(numeric_operators.keys())) def test_query_search_filter_float_type(annlite_with_heterogeneous_tags, operator): X = np.random.random((Nq, D)).astype(np.float32) query_da = DocumentArray([Document(embedding=X[i]) for i in range(Nq)]) thresholds = [20, 50, 100, 400] for threshold in thresholds: annlite_with_heterogeneous_tags.search( query_da, filter={'price': {operator: threshold}}, include_metadata=True ) for query in query_da: assert all( [ numeric_operators[operator](m.tags['price'], threshold) for m in query.matches ] ) @pytest.mark.parametrize('operator', list(numeric_operators.keys())) def test_query_search_numpy_filter_float_type( annlite_with_heterogeneous_tags, heterogenenous_da, operator ): X = np.random.random((Nq, D)).astype(np.float32) query_np = np.array([X[i] for i in range(Nq)]) da = heterogenenous_da thresholds = [20, 50, 100, 400] for threshold in thresholds: dists, doc_ids = annlite_with_heterogeneous_tags.search_numpy( query_np, filter={'price': {operator: threshold}}, include_metadata=True ) for doc_ids_query_k in doc_ids: assert all( [ numeric_operators[operator]( da[int(doc_id)].tags['price'], threshold ) for doc_id in doc_ids_query_k ] ) @pytest.mark.parametrize('operator', list(categorical_operators.keys())) def test_search_filter_str(annlite_with_heterogeneous_tags, operator): X = np.random.random((Nq, D)).astype(np.float32) query_da = DocumentArray([Document(embedding=X[i]) for i in range(Nq)]) categories = ['comics', 'movies', 'audiobook'] for category in categories: annlite_with_heterogeneous_tags.search( query_da, filter={'category': {operator: category}}, include_metadata=True ) for query in query_da: assert all( [ numeric_operators[operator](m.tags['category'], category) for m in query.matches ] ) @pytest.mark.parametrize('operator', list(categorical_operators.keys())) def test_search_numpy_filter_str( annlite_with_heterogeneous_tags, heterogenenous_da, operator ): X = np.random.random((Nq, D)).astype(np.float32) query_np = np.array([X[i] for i in range(Nq)]) da = heterogenenous_da categories = ['comics', 'movies', 'audiobook'] for category in categories: dists, doc_ids = annlite_with_heterogeneous_tags.search_numpy( query_np, filter={'category': {operator: category}}, include_metadata=True ) for doc_ids_query_k in doc_ids: assert all( [ numeric_operators[operator]( da[int(doc_id)].tags['category'], category ) for doc_id in doc_ids_query_k ] ) def test_search_numpy_membership_filter( annlite_with_heterogeneous_tags, heterogenenous_da ): X = np.random.random((Nq, D)).astype(np.float32) query_np = np.array([X[i] for i in range(Nq)]) da = heterogenenous_da dists, doc_ids = annlite_with_heterogeneous_tags.search_numpy( query_np, filter={'category': {'$in': ['comics', 'audiobook']}}, include_metadata=True, ) for doc_ids_query_k in doc_ids: assert len(doc_ids) assert all( [ da[int(doc_id)].tags['category'] in ['comics', 'audiobook'] for doc_id in doc_ids_query_k ] ) dists, doc_ids = annlite_with_heterogeneous_tags.search_numpy( query_np, filter={'category': {'$nin': ['comics', 'audiobook']}}, include_metadata=True, ) for doc_ids_query_k in doc_ids: assert len(doc_ids) assert all( [ da[int(doc_id)].tags['category'] not in ['comics', 'audiobook'] for doc_id in doc_ids_query_k ] ) def delete_artifact(tmpname): client = hubble.Client(token=token, max_retries=None, jsonify=True) art_list = client.list_artifacts(filter={'metaData.name': tmpname}) for art in art_list['data']: client.delete_artifact(id=art['_id']) def test_local_backup_restore(tmpdir): X = np.random.random((N, D)) docs = DocumentArray([Document(id=f'{i}', embedding=X[i]) for i in range(N)]) index = AnnLite(n_dim=D, data_path=tmpdir / 'workspace' / '0') index.index(docs) tmpname = uuid.uuid4().hex index.backup() index.close() index = AnnLite(n_dim=D, data_path=tmpdir / 'workspace' / '0') index.restore() status = index.stat assert int(status['total_docs']) == N assert int(status['index_size']) == N @pytest.mark.skip(reason='This test requires a running hubble instance') def test_remote_backup_restore(tmpdir): X = np.random.random((N, D)) docs = DocumentArray([Document(id=f'{i}', embedding=X[i]) for i in range(N)]) index = AnnLite(n_dim=D, data_path=tmpdir / 'workspace' / '0') index.index(docs) tmpname = uuid.uuid4().hex index.backup(target_name='test_remote_backup_restore', token=token) index = AnnLite(n_dim=D, data_path=tmpdir / 'workspace' / '0') index.restore(source_name='test_remote_backup_restore', token=token) delete_artifact(tmpname) status = index.stat assert int(status['total_docs']) == N assert int(status['index_size']) == N ================================================ FILE: tests/test_pq_bind.py ================================================ import numpy as np import pytest from annlite.core.codec.pq import PQCodec from annlite.pq_bind import dist_pqcodes_to_codebooks, precompute_adc_table n_examples = 2000 n_features = 128 n_queries = 5 n_cells = 10 n_clusters = 256 n_subvectors = 32 d_subvector = int(n_features / n_subvectors) top_k = 100 @pytest.fixture def build_data(): Xt = np.random.random((n_examples, n_features)).astype(np.float32) return Xt @pytest.fixture def build_pq_codec(build_data): Xt = build_data pq_codec = PQCodec(dim=n_features, n_subvectors=n_subvectors, n_clusters=n_clusters) pq_codec.fit(Xt) return pq_codec def test_pq_adc_table_shape(build_pq_codec): pq_codec = build_pq_codec assert pq_codec.codebooks.shape == (n_subvectors, n_clusters, d_subvector) def test_pq_adc_table_computation(build_data): def numpy_adc_table(query, n_subvectors, n_clusters, d_subvector, codebooks): dtable = np.empty((n_subvectors, n_clusters), dtype=np.float32) for m in range(n_subvectors): query_sub = query[m * d_subvector : (m + 1) * d_subvector] dtable[m, :] = np.linalg.norm(codebooks[m] - query_sub, axis=1) ** 2 return dtable query = build_data[0] codebooks = np.random.random((n_subvectors, n_clusters, d_subvector)).astype( np.float32 ) np_distance_table = numpy_adc_table( query, n_subvectors, n_clusters, d_subvector, codebooks ) distance_table_cy = precompute_adc_table(query, d_subvector, n_clusters, codebooks) np_distance_table_cy = np.asarray(distance_table_cy) np.testing.assert_array_almost_equal( np_distance_table, np_distance_table_cy, decimal=5 ) def test_pq_adc_table_computation_interface(build_pq_codec, build_data): pq_codec = build_pq_codec query = build_data[0] np_distance_table = pq_codec.precompute_adc(query).dtable distance_table_cy = precompute_adc_table( query, pq_codec.d_subvector, pq_codec.n_clusters, pq_codec.codebooks ) np_distance_table_cy = np.asarray(distance_table_cy) np.testing.assert_array_almost_equal( np_distance_table, np_distance_table_cy, decimal=5 ) ================================================ FILE: tests/test_pq_index.py ================================================ from time import time import numpy as np import pytest from docarray import Document, DocumentArray from loguru import logger from annlite import AnnLite, pq_bind from annlite.core.codec.pq import PQCodec from annlite.core.index.pq_index import PQIndex from annlite.math import euclidean, l2_normalize N = 1000 # number of data points Nq = 5 Nt = 2000 D = 64 # dimensionality / number of features @pytest.fixture def random_docs(): X = np.random.random((N, D)).astype( np.float32 ) # 10,000 64-dim vectors to be indexed docs = DocumentArray( [Document(id=f'{i}', embedding=X[i], tags={'x': str(i)}) for i in range(N)] ) return docs def test_pq_index_dist_mat(random_docs): X = random_docs.embeddings N, D = X.shape pq_class = PQCodec(dim=D) pq_class.fit(X) test_cases = X[:10] # -------------- true pq_bind_re = [] for i in range(len(test_cases)): query = test_cases[i] dtable = pq_bind.precompute_adc_table( query, pq_class.d_subvector, pq_class.n_clusters, pq_class.codebooks ) pq_bind_re.append(dtable) pq_bind_re = np.stack(pq_bind_re) # -------------- test pq_dist_mat = pq_class.get_dist_mat(test_cases) assert np.allclose(pq_bind_re, pq_dist_mat) def test_hnsw_pq_load_empty(tmpfile, random_docs): pq_index = AnnLite( D, data_path=tmpfile, n_subvectors=8, ) with pytest.raises(RuntimeError): # unable to add or search before a actual training of PQ pq_index.index(random_docs) with pytest.raises(RuntimeError): # unable to add or search before a actual training of PQ pq_index.search(random_docs) def test_hnsw_pq_load(tmpfile, random_docs): pq_index = AnnLite( D, data_path=tmpfile, n_subvectors=8, ) pq_index.train(random_docs.embeddings) pq_index.index(random_docs) assert all([x.pq_codec.is_trained for x in pq_index._vec_indexes]) assert all([x._index.pq_enable for x in pq_index._vec_indexes]) @pytest.mark.parametrize('n_clusters', [256, 512, 768]) def test_hnsw_pq_search_multi_clusters(tmpdir, n_clusters, random_docs): total_test = 10 topk = 50 X = random_docs.embeddings N, Dim = X.shape computed_dist = euclidean(X, X) computed_labels = np.argsort(computed_dist, axis=1)[:, :topk] query = DocumentArray([Document(embedding=X[i]) for i in range(total_test)]) test_query = DocumentArray([Document(embedding=X[i]) for i in range(total_test)]) # HNSW search with float---------------------------------- no_pq_index = AnnLite(D, data_path=tmpdir / 'no_pq_index', metric='EUCLIDEAN') hnsw_s = time() no_pq_index.index(random_docs) hnsw_d = time() - hnsw_s print('Index time', hnsw_d) hnsw_s = time() no_pq_index.search(query, limit=topk) hnsw_d = time() - hnsw_s print(hnsw_d) no_pq_index.close() # HNSW search with quantization--------------------------- pq_index = AnnLite( D, data_path=tmpdir / 'pq_index', n_subvectors=8, n_clusters=n_clusters, metric='EUCLIDEAN', ) pq_index.train(X) hnsw_pq_s = time() pq_index.index(random_docs) hnsw_pq_d = time() - hnsw_pq_s print('Index time', hnsw_pq_d) hnsw_pq_s = time() pq_index.search(test_query, limit=topk) hnsw_pq_d = time() - hnsw_pq_s # ---------------------------------- # PQ linear search---------------------------------- _pq_codec = pq_index._pq_codec ids = np.array([int(doc.id) for doc in random_docs]) linear_pq_index = PQIndex(Dim, _pq_codec) linear_pq_index.add_with_ids(X, ids) search_x = test_query.embeddings pq_dists = [] linear_results = [] pq_s = time() for i in range(total_test): pq_dist, linear_result = linear_pq_index.search(search_x[i], limit=topk) pq_dists.append(pq_dist) linear_results.append(linear_result) pq_d = time() - pq_s # ---------------------------------- precision = [] original_precision = [] pq_precision = [] for i in range(total_test): real_ground_truth = set([str(i) for i in computed_labels[i]]) ground_truth = set([m.id for m in query[i].matches]) pq_result = set([m.id for m in test_query[i].matches]) linear_pq_result = set([str(i_id) for i_id in linear_results[i]]) original_precision.append(len(real_ground_truth & ground_truth) / topk) pq_precision.append(len(real_ground_truth & linear_pq_result) / topk) precision.append(len(real_ground_truth & pq_result) / topk) logger.info(f'Total test {total_test}') logger.info( f'PQ backend top-{topk} precision: {np.mean(pq_precision)}, time {pq_d}' ) logger.info( f'HNSW backend top-{topk} precision: {np.mean(original_precision)}, time {hnsw_d}' ) logger.info( f'HNSW PQ backend top-{topk} precision: {np.mean(precision)}, time {hnsw_pq_d}' ) # TODO: fix the precision issue # assert np.mean(precision) > 0.9 ================================================ FILE: tests/test_projector.py ================================================ import numpy as np import pytest from annlite.core.codec.projector import ProjectorCodec n_examples = 1000 n_features = 512 n_components = 128 batch_size = 200 @pytest.fixture def build_data(): Xt = np.random.random((n_examples, n_features)).astype(np.float32) return Xt @pytest.fixture def build_projector(build_data): Xt = build_data projector_list = [] for is_incremental in [True, False]: projector = ProjectorCodec( dim=n_features, n_components=n_components, ) if is_incremental: for i in range(0, len(Xt), batch_size): projector.partial_fit(Xt[i : i + batch_size]) i += batch_size else: projector.fit(Xt) projector_list.append(projector) return projector_list def test_encode_decode(build_data, build_projector): Xt = build_data projector_list = build_projector for projector in projector_list: transformed_vecs = projector.encode(Xt) assert transformed_vecs.shape == (Xt.shape[0], n_components) original_vecs = projector.decode(transformed_vecs) assert original_vecs.shape == Xt.shape def test_save_and_load(tmpdir, build_data, build_projector): import os from pathlib import Path Xt = build_data projector_list = build_projector for projector in projector_list: projector.dump(Path(os.path.join(tmpdir, 'projector.pkl'))) assert os.path.exists(os.path.join(tmpdir, 'projector.pkl')) is True projector_ = ProjectorCodec.load(Path(os.path.join(tmpdir, 'projector.pkl'))) assert projector.components.shape == projector_.components.shape before = projector.encode(Xt) after = projector_.encode(Xt) np.testing.assert_array_almost_equal(before, after, decimal=5) ================================================ FILE: tests/test_projector_index.py ================================================ import numpy as np import pytest from docarray import Document, DocumentArray from annlite.index import AnnLite n_examples = 1000 n_features = 512 n_components = 128 batch_size = 200 @pytest.fixture def build_data(): Xt = np.random.random((n_examples, n_features)).astype(np.float32) return Xt @pytest.fixture def build_projector_annlite(tmpfile): index = AnnLite(n_dim=n_features, data_path=tmpfile) return index @pytest.fixture def projector_annlite_with_data(build_data, build_projector_annlite): Xt = build_data indexer = build_projector_annlite docs = DocumentArray( [Document(id=f'{i}', embedding=Xt[i]) for i in range(n_examples)] ) indexer.index(docs) return indexer def test_delete(projector_annlite_with_data): indexer = projector_annlite_with_data indexer.delete(['0', '1']) def test_update(projector_annlite_with_data): indexer = projector_annlite_with_data X = np.random.random((5, n_features)).astype(np.float32) docs = DocumentArray([Document(id=f'{i}', embedding=X[i]) for i in range(5)]) indexer.update(docs) def test_query(projector_annlite_with_data): indexer = projector_annlite_with_data X = np.random.random((5, n_features)).astype(np.float32) # a 128-dim query vector query = DocumentArray([Document(embedding=X[i]) for i in range(5)]) indexer.search(query) for i in range(len(query[0].matches) - 1): assert ( query[0].matches[i].scores['euclidean'].value <= query[0].matches[i + 1].scores['euclidean'].value ) ================================================ FILE: tests/test_store.py ================================================ import pytest from annlite.storage.kv import DocStorage def test_get(tmpfile, docs): storage = DocStorage(tmpfile) storage.insert(docs) doc = storage.get('doc1')[0] assert doc.id == 'doc1' assert (doc.embedding == [1, 0, 0, 0]).all() docs = storage.get('doc7') assert len(docs) == 0 def test_update(tmpfile, docs, update_docs): storage = DocStorage(tmpfile) storage.insert(docs) storage.update(update_docs) doc = storage.get('doc1')[0] assert (doc.embedding == [0, 0, 0, 1]).all() def test_delete(tmpfile, docs): storage = DocStorage(tmpfile) storage.insert(docs) storage.delete(['doc1']) docs = storage.get('doc1') assert len(docs) == 0 def test_clear(tmpfile, docs): storage = DocStorage(tmpfile) storage.insert(docs) assert storage.size == 6 storage.clear() assert storage.size == 0 def test_batched_iterator(tmpfile, docs): storage = DocStorage(tmpfile) storage.insert(docs) for docs in storage.batched_iterator(batch_size=3): assert len(docs) == 3 @pytest.mark.parametrize('protocol', ['pickle', 'protobuf']) def test_searalize(tmpfile, protocol, docs): storage = DocStorage(tmpfile, serialize_config={'protocol': protocol}) storage.insert(docs) doc = storage.get('doc1')[0] assert doc.id == 'doc1' assert (doc.embedding == [1, 0, 0, 0]).all() docs = storage.get('doc7') assert len(docs) == 0 ================================================ FILE: tests/test_table.py ================================================ import pytest from docarray import Document, DocumentArray from annlite.storage.table import CellTable, MetaTable @pytest.fixture def dummy_cell_table(): table = CellTable(name='dummy', in_memory=True, lazy_create=True) table.add_column('name', 'TEXT', create_index=True) table.add_column('price', 'FLOAT', create_index=True) table.add_column('category', 'TEXT') table.create_table() return table @pytest.fixture def sample_docs(): return DocumentArray( [ Document( id='0', tags={'name': 'orange', 'price': 1.2, 'category': 'fruit'} ), Document(id='1', tags={'name': 'banana', 'price': 2, 'category': 'fruit'}), Document(id='2', tags={'name': 'poly', 'price': 5.1, 'category': 'animal'}), Document(id='3', tags={'name': 'bread'}), ] ) @pytest.fixture def table_with_data(dummy_cell_table, sample_docs): dummy_cell_table.insert(sample_docs) return dummy_cell_table def test_create_cell_table(): table = CellTable(name='cell_table_x', lazy_create=True) table.add_column('x', 'float') table.create_table() assert table.existed() def test_schema(dummy_cell_table): schema = dummy_cell_table.schema assert len(schema.split('\n')) == 5 def test_query(table_with_data): result = table_with_data.query( where_clause='(category = ?) AND (price < ?)', where_params=('fruit', 3) ) assert len(result) == 2 assert result[0] == 0 def test_get_docid_by_offset(table_with_data): doc_id = table_with_data.get_docid_by_offset(0) assert doc_id == '0' doc_id = table_with_data.get_docid_by_offset(4) assert doc_id is None def test_exist(table_with_data): assert table_with_data.exist('3') def test_delete(table_with_data): table_with_data.delete(['3']) assert not table_with_data.exist('3') table_with_data.delete_by_offset(2) assert not table_with_data.exist('2') def test_count(table_with_data): count = table_with_data.count( where_clause='(category = ?) AND (price > ?)', where_params=('fruit', 5) ) assert count == 0 count = table_with_data.count( where_clause='(category = ?) AND (price > ?) and (price < ?)', where_params=('fruit', 1, 1.5), ) assert count == 1 count = table_with_data.count( where_clause='(category = ?) AND (price < ?)', where_params=('fruit', 3) ) assert count == 2 count = table_with_data.count( where_clause='(category IN (?, ?)) AND (price < ?)', where_params=('fruit', 'animal', 3), ) assert count == 2 count = table_with_data.count( where_clause='(category IN (?)) AND (price < ?)', where_params=('fruit', 1) ) assert count == 0 def test_create_meta_table(tmpdir): import datetime table = MetaTable('meta_test', data_path=tmpdir) addr = table.get_latest_commit() assert addr is None table.add_address('0', 0, 1) table.add_address('2', 1, 5) time_since = datetime.datetime.utcnow() table.add_address('0', 1, 2) assert table.get_address('0') == (1, 2) assert table.get_address('2') == (1, 5) assert len(list(table.iter_addresses())) == 2 addresses = list(table.iter_addresses(time_since=time_since)) assert addresses == [('0', 1, 2)] addr = table.get_latest_commit() assert addr[:3] == ('0', 1, 2) assert addr[-1] >= time_since time_since = datetime.datetime.utcnow() table.delete_address('0') addresses = list(table.iter_addresses(time_since=time_since)) assert addresses == []